• Socket:读写处理及连接断开的检测


    作为进程间通信及网络通信的一种重要技术,在实际的开发中,socket编程是经常被用到的。关于socket编程的一般步骤,这里不再赘述,相关资料和文章很多,google/baidu即可。

    本文主要是探讨如何更好地进行socket读写处理,以及如何检测连接断开。

    首先,有以下几点需要注意:

    1. 对于全双工的socket,同时读写是没问题的。比如,一个socket程序有两个线程,一个线程对socket进行读操作(recv/read),一个线程对socket进行写操作(send/write),这里是不需要进行互斥操作或做临界区保护的。
    2. 在Unix系统下, 对一个对端已经关闭的socket调用两次write,第二次将会生成SIGPIPE信号,该信号的默认处理动作是终止进程。为了防止在这种情况下导致进程退出,我们需要屏蔽该信号的默认处理动作。有两种方法,a) 在程序开头调用signal(SIGPIPE, SIG_IGN),忽略SIGPIPE信号的默认动作;b) 采用send函数的MSG_NOSIGNAL标志位,忽略SIGPIPE信号。当然,虽然我们忽略了SIGPIPE,但errno还是会被设置为EPIPE的。因此,我们可以根据这点,按照我们的情况来进行相应的处理了。
    3. 对文件描述符进行select/poll操作,a) 如果select/poll返回值大于0,此时进行recv/read,recv/read返回0,表明连接关闭; b)  recv/read返回-1,并且errno为ECONNRESET、EBADF、EPIPE、ENOTSOCK之一时,也表明连接关闭。
    4. 对文件描述符进行send/write操作,如果send/write返回-1,并且errno为ECONNRESET、EBADF、EPIPE、ENOTSOCK之一时,也表明连接关闭。


    下面是一个demo程序,server端代码(server.c)为:

    #define _GNU_SOURCE
    #include <stdio.h>
    #include <sys/socket.h>
    #include <sys/un.h>
    #include <sys/types.h>
    #include <unistd.h>
    #include <string.h>
    #include <poll.h>
    #include <pthread.h>
    #include <errno.h>
    
    #define UNIX_PATH_MAX 108
    #define SOCK_PATH "/tmp/test.sock"
    
    int sock_write(int fd, char* buf, int len)
    {
        int err_num, res;
        char err_str[128];
        int disconnect = 0;
    
        while(!disconnect){
            /* Use send with a MSG_NOSIGNAL to ignore a SIGPIPE signal which 
             * would cause the process exit. */
            res = send(fd, buf, len, MSG_NOSIGNAL | MSG_DONTWAIT);
            if( -1 == res ){
                err_num = errno;            
                printf("send error:%s!
    ", strerror_r(err_num, err_str, sizeof(err_str)));
    
                switch(err_num){
                    case ECONNRESET:
                    case EBADF:
                    case EPIPE: 
                    case ENOTSOCK:
                        disconnect = 1;
                        break;
                    //case EWOULDBLOCK:
                    case EAGAIN:                    
                        usleep(10000);
                        break;
                    case EINTR:
                        break;
                    default:
                        break;
                }            
            }else if( res > 0 ){
                if( res < len ){
                    /* Incomplete information. */
                    buf += res;
                    len -= res;
                }else if( res == len ){
                    /* Message has sended successfully. */
                    break;
                }    
            }
        }
        return disconnect;
    }
    
    void* write_handle(void* fd)
    {
        int len, connection_fd;
        char buffer[256];
        char* end_flag = "
    ";
    
        connection_fd = *(int*)fd;
        len = snprintf(buffer, 256, "buffer data ends with a end flag%s", end_flag);
        buffer[len] = 0;
    
        while(0 == sock_write(connection_fd, buffer, len)){
            sleep(1);
        }
    }
    
    int main(void)
    {
        struct sockaddr_un address;
        int socket_fd, connection_fd;
        socklen_t address_length;
    
        socket_fd = socket(PF_UNIX, SOCK_STREAM, 0);
        if(socket_fd < 0)     {
             printf("%s:socket() failed
    ", SOCK_PATH);
             return -1;
         }
         unlink(SOCK_PATH);
         memset(&address, 0, sizeof(struct sockaddr_un));
         address.sun_family = AF_UNIX;
         snprintf(address.sun_path, UNIX_PATH_MAX, SOCK_PATH);
         if(bind(socket_fd, (struct sockaddr *) &address, sizeof(struct sockaddr_un)) != 0)
         {
             printf("%s:bind() failed
    ", SOCK_PATH);
             return -1;
         }
         if(listen(socket_fd, 0) != 0)
         {
             printf("%s:listen() failed
    ", SOCK_PATH);
             return -1;
         }
         while((connection_fd = accept(socket_fd, (struct sockaddr *) &address,&address_length)) > -1)
         {
            pthread_t w_thread;
            struct pollfd pfd;
            char buffer[512];
            int nbytes;
            int cnt;
            int res;
            int err_num;
            int disconnect=0;
    #ifdef DEBUG
            char str[128];
    #endif
    
            printf("
    
    %s:accept a connection!
    ", SOCK_PATH);
    
            pthread_create(&w_thread, NULL, write_handle, &connection_fd);
    
            pfd.fd = connection_fd;
            pfd.events = POLLIN | POLLHUP | POLLRDNORM;
            pfd.revents = 0;
    
            while(1){
                res = poll(&pfd, 1, 500);
                if(res >= 0){
                    // if result > 0, this means that there is either data available on the
                    // socket, or the socket has been closed
                    cnt = recv(connection_fd, buffer, sizeof(buffer), MSG_PEEK | MSG_DONTWAIT);
                    if( 0 == cnt ){
                        if(res > 0){
                        // if recv returns zero, that means the connection has been closed.
        #ifdef DEBUG
                            printf("connection disconnect 1!
    ");
        #endif
                            disconnect = 1; 
                            break;
                        }
                    }else if( -1 == cnt ){
                        err_num = errno;
        #ifdef DEBUG
                        printf("recv error:%s!
    ", strerror_r(errno, str, sizeof(str)));
        #endif
                        switch(err_num){
                            case ECONNRESET:
                            case EBADF:
                            case EPIPE: 
                            case ENOTSOCK:
                                disconnect = 1; 
                                break;
                            default:
                                break;
                        }
                        if( disconnect ){
        #ifdef DEBUG
                            printf("connection disconnect 2!
    ");
        #endif
                            break;
                        }
                    }else if( cnt > 0 ){
                        /* discard everything received from client.*/
                        while((nbytes = recv(connection_fd, buffer, sizeof(buffer)-1, MSG_DONTWAIT)) > 0){
                            buffer[nbytes] = 0;
        #ifdef DEBUG
                            printf("buffer:%s
    ", buffer);
        #endif
                        }
        #ifdef DEBUG
                        if( 0 == nbytes ){
                            printf("All received!
    ");
                        }
        #endif
                    }
                }
        #ifdef DEBUG
                else if(res == -1){
                    /* This case shouldn't happen, we sleep 5 seconds here in case and retry it. */
                    printf("Error: poll return -1!!!
    ");
                    sleep(5);
                }
        #endif
            }
            close(connection_fd);
            pthread_cancel(w_thread);
        }
    
        close(socket_fd);
        unlink(SOCK_PATH);
        return 0;
    }

    client端代码(client.c)为:

    #define _GNU_SOURCE
    #include <stdio.h>
    #include <sys/socket.h>
    #include <sys/un.h>
    #include <unistd.h>
    #include <string.h>
    #include <pthread.h>
    #include <poll.h>
    #include <errno.h> 
    
    #define UNIX_PATH_MAX 108
    #define SOCK_PATH "/tmp/test.sock"
    
    int main(void)
    {
        struct sockaddr_un address;
        int  socket_fd, res;
        char buffer[256];
        pthread_t w_thread;
        struct pollfd pfd;
        int err_num;
        int disconnect; 
    
        while(1){
            socket_fd = socket(PF_UNIX, SOCK_STREAM, 0);
            if(socket_fd < 0)
            {
                 printf("%s:socket() failed
    ", SOCK_PATH);
                 sleep(5);
                 continue;
            }
            memset(&address, 0, sizeof(struct sockaddr_un));
            address.sun_family = AF_UNIX;
            snprintf(address.sun_path, UNIX_PATH_MAX, SOCK_PATH);
            if(connect(socket_fd, (struct sockaddr *) &address, sizeof(struct sockaddr_un)) != 0)
            {
                printf("%s:connect() failed
    ", SOCK_PATH);
                close(socket_fd);
                socket_fd = -1;
                sleep(5);
                continue;
            } 
    #ifdef DEBUG
            printf("connect success!
    "); 
    #endif
    #if 1
            pfd.fd = socket_fd;
            pfd.events = POLLIN | POLLHUP | POLLRDNORM;
            pfd.revents = 0;
            disconnect = 0;
            while(1){
                 // use the poll system call to be notified about socket status changes
                 res = poll(&pfd, 1, 60000);
                 if(res >= 0){
                    // if result > 0, this means that there is either data available on the
                    // socket, or the socket has been closed
                    char buffer[512];
                    int cnt, nbytes;
                    cnt = recv(socket_fd, buffer, sizeof(buffer)-1, MSG_PEEK | MSG_DONTWAIT);
                    if( -1 == cnt){
                        err_num = errno;
                        switch(err_num){
                        case ECONNRESET:
                        case EBADF:
                        case EPIPE: 
                        case ENOTSOCK:
                            disconnect = 1;                            
                            break;
                        default:
                            break;
                        }
                        if(disconnect){
                            break;
                        }
                    }
                    else if( 0 == cnt){
                        if(res > 0){
                 // if recv returns zero, that means the connection has been closed:
                 disconnect = 1;                            
                            break;
                        }
                    }
                    else if( cnt > 0 ){
                        while((nbytes = recv(socket_fd, buffer, sizeof(buffer)-1, MSG_DONTWAIT)) > 0){
                            buffer[nbytes] = 0;
                            printf("buffer:%s
    ", buffer);
                        }
                    }
                }
            }
    #endif
            close(socket_fd);
            socket_fd = -1;
    #ifdef DEBUG
            printf("server disconnect!
    ");
    #endif
            /* here sleep 5 seconds and re-connect. */
            sleep(5);
        }
    
        return 0;
    }
  • 相关阅读:
    Mybatis 的 xml 文件语法错误,启动项目时控制台一直循环解析但是不打印错误
    在一个由 'L' , 'R' 和 'X' 三个字符组成的字符串(例如"RXXLRXRXL")中进行移动操作。一次移动操作指用一个"LX"替换一个"XL",或者用一个"XR"替换一个"RX"。现给定起始字符串start和结束字符串end,请编写代码,当且仅当存在一系列移动操作使得start可以转换成end时, 返回True。
    【Important】数据库索引原理
    服务化的演变和负载均衡
    【问题集】redis.clients.jedis.exceptions.JedisDataException: ERR value is not an integer or out of range
    【Spring】Spring中用到的设计模式
    【设计模式】责任链模式
    【!Important】Zookeeper用来做什么的,有几种类型的节点
    【!Important】如何保证线程执行的先后顺序
    【!Important】Java线程死锁查看分析方法
  • 原文地址:https://www.cnblogs.com/lijingcheng/p/4454913.html
Copyright © 2020-2023  润新知