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;
}
原文地址:https://www.cnblogs.com/lijingcheng/p/4454913.html