如何防止Socket服务器断开连接

时间:2018-10-08 11:28:39

标签: c sockets gcc8

我编写了有关套接字的代码,当客户端发送消息或客户端执行CTRL + C时,我无法阻止服务器关闭 服务器关闭。

Server.c:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/time.h>

#define PORT 8888
#define BACKLOG 10
#define LOOP(i, n) for((i) = 0; (i) < (n); (i)++)
#define BUFFSIZE 6500

int create_fd( struct sockaddr_in *server );
int bind_fd  ( struct sockaddr_in *server, int servSocket );
int list_fd( int servSocket );
int accept_fd( struct sockaddr_in *server, int servSocket  );
int select_fd ( fd_set *readfds, int max_sd );
ssize_t send_fd( int new_fd, const char *const msg );

int main( void ){
    const char msg[BUFFSIZE] = "Server is On\n";
    char recive[BUFFSIZE];
    int servSocket ,clientSocket ,i ,sd;
    int client_socket_max[30] = { 0 } , max_clients = 30;
    int max_sd;
    ssize_t valread;
    struct sockaddr_in server;
    for (i = 0; i < max_clients; i++)
    {
        client_socket_max[i] = 0;
    }

    //set of socket descriptors
    fd_set readfds;

    size_t addr_size = sizeof(server);

    //create
    servSocket = create_fd( &server );

    //bind the socket to localhost port 8888
    bind_fd(&server, servSocket );

    //listen the socket to localhost port 8888
    printf("\tServer is On\nListener on port %d \n", PORT);
    list_fd( servSocket );

    //accept the incoming connection
    printf( "\n\tWaiting for connections ...\n" );
    while(1){
        //clear the socket set
        FD_ZERO(&readfds);
        //add master socket to set
        FD_SET(servSocket, &readfds);
        max_sd = servSocket;
        //add child sockets to set
        LOOP( i, max_clients ){
            //socket descriptor
            sd = client_socket_max[i];
            //if valid socket descriptor then add to read list
            if(sd > 0){
                FD_SET( sd , &readfds);
            }
            //highest file descriptor number, need it for the select function
            if(sd > max_sd){
                max_sd = sd;
            }
        }
        //wait for an activity on one of the sockets , timeout is NULL , so wait indefinitely
        select_fd( &readfds, max_sd );
        //select( max_sd + 1 , &readfds , NULL , NULL , NULL);

        //If something happened on the master socket , then its an incoming connection
        if ( FD_ISSET( servSocket, &readfds ) ){
               clientSocket = accept_fd( &server, servSocket );

            //inform user of socket number - used in send and receive commands
            printf("New connection , socket fd is %d , ip is : %s , port : %d \n" , clientSocket , inet_ntoa(server.sin_addr) , ntohs(server.sin_port));

            //send new connection greeting msg
            send_fd( clientSocket, msg );
            /*if( send(clientSocket, msg, strlen(msg), 0) != (long int)strlen(msg) ){
                perror("send");
            }*/

            puts("Welcome msg sent successfully");
            //add new socket to array of sockets
            LOOP( i, max_clients ){
                //if position is empty
                if( client_socket_max[i] == 0 ){
                    client_socket_max[i] = clientSocket;
                    printf("Adding to list of sockets as %d\n" , i);
                    break;
                }
            }
        }
        //else its some IO operation on some other socket :)
        LOOP( i, max_clients ){
            sd = client_socket_max[i];
            if (FD_ISSET( sd , &readfds)){
                //Check if it was for closing , and also read the incoming msg
                if (( valread = read( sd , recive, 1024)) == 0){
                    //Somebody disconnected , get his details and print
                    getpeername(sd , (struct sockaddr*)&server , (socklen_t*)&addr_size);
                    printf("Host disconnected , ip %s , port %d \n" , inet_ntoa(server.sin_addr) , ntohs(server.sin_port));

                    //Close the socket and mark as 0 in list for reuse
                    close( sd );
                    client_socket_max[i] = 0;
                }else{//Echo back the msg that came in
                    //set the string terminating NULL byte on the end of the data read
                    recive[valread] = '\0';
                    send(sd , recive , strlen(recive) , 0 );
                }
            }
        }
    }
    close( servSocket );
}

int create_fd( struct sockaddr_in *server ){
    int opt = 1;
    int servSocket = socket(AF_INET , SOCK_STREAM , 0);
    if (servSocket == -1 ){
        printf("Error, socket()\n");
        fprintf(stderr, "socket: %s (%d)\n", strerror(errno), errno);
        exit ( EXIT_FAILURE );
    }
    //set master socket to allow multiple connections , this is just a good habit, it will work without this
    if( setsockopt(servSocket, SOL_SOCKET, SO_REUSEADDR, (char *)&opt, sizeof(opt)) < 0 ){
        perror("setsockopt");
        exit(EXIT_FAILURE);
    }
        //type of socket created
    server->sin_family      = AF_INET;
    server->sin_addr.s_addr = INADDR_ANY;
    server->sin_port        = htons( PORT );

    //printf("socket() \tOK\n");
    return servSocket;
}

int bind_fd  ( struct sockaddr_in *server, int servSocket ){
    int bindfd = bind( servSocket, (struct sockaddr *)server, sizeof(*server) );
    if (bindfd == -1 ){
        printf("Error, bind(), check line 34\n");
        fprintf(stderr, "bind: %s (%d)\n", strerror(errno), errno);
        exit ( EXIT_FAILURE );
    }else{
        //printf("bind() \t\tOK\n");
        return bindfd;
    }
}

int list_fd( int servSocket ){
    int listfd = listen(servSocket, BACKLOG);
    if (listfd == -1 ){
        printf("Error, listen()\n");
        fprintf(stderr, "listen: %s (%d)\n", strerror(errno), errno);
        exit ( EXIT_FAILURE );
    }else{
        //printf("listen() \tOK\n");
        return listfd;
    }
}

int accept_fd( struct sockaddr_in *server, int servSocket  ){
    int new_fd;
    socklen_t addr_size = sizeof( server );
    new_fd = accept(servSocket, (struct sockaddr *)server, (socklen_t*)&addr_size);
    if (new_fd == -1 ){
        printf("Error, accept()\n");
        fprintf(stderr, "accept: %s (%d)\n", strerror(errno), errno);
        exit ( EXIT_FAILURE );
    }else{
        //printf("accept() \tOK\n");
        return new_fd;
    }
}

int select_fd ( fd_set *readfds, int max_sd ){
    int activity = select( max_sd + 1 , readfds , NULL , NULL , NULL);
    if ((activity < 0) && (errno!=EINTR)){
        printf("select error");
        exit ( EXIT_FAILURE );
    }else{
        return activity;
    }
}

ssize_t send_fd( int new_fd, const char *const msg ){
    size_t len = strlen(msg);
    ssize_t sendfd;
    sendfd = send( new_fd, msg, len, 0 );
    if (sendfd == -1 ){
        printf("Error, write()\n");
        fprintf(stderr, "recv: %s (%d)\n", strerror(errno), errno);
        exit ( EXIT_FAILURE );
    }else{
        printf("write() \tOK\n");
        printf("Client sent:\t%s", msg );
        return sendfd;
    }
}

Client.c:

#include <stdio.h>
#include <string.h>   //strlen
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>   //close
#include <arpa/inet.h>    //close
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/time.h> //FD_SET, FD_ISSET, FD_ZERO macros

#define PORT 8888
#define BUFFSIZE 6500

int create_fd( struct sockaddr_in server );
int connect_fd( const int servSocket, struct sockaddr_in *server );
int opt = 1;


int main ( void ){
    struct sockaddr_in server;
    char recivemsg[BUFFSIZE];
    char sendmsg[BUFFSIZE];
    memset( recivemsg, 0, sizeof(*recivemsg) );
    memset( sendmsg, 0, sizeof(*sendmsg) );

    int servSocket = socket(AF_INET , SOCK_STREAM , 0);
    if (servSocket == -1 ){
        printf("Error, socket()\n");
        fprintf(stderr, "socket: %s (%d)\n", strerror(errno), errno);
        exit ( EXIT_FAILURE );
    }
    //set master socket to allow multiple connections , this is just a good habit, it will work without this
    if( setsockopt(servSocket, SOL_SOCKET, SO_REUSEADDR, (char *)&opt, sizeof(opt)) < 0 ){
        perror("setsockopt");
        exit(EXIT_FAILURE);
    }
        //type of socket created
    server.sin_family      = AF_INET;
    server.sin_addr.s_addr = INADDR_ANY;
    server.sin_port        = htons( PORT );

    if ( ( connect( servSocket, ( struct sockaddr* )&server, sizeof( server ) ) ) < 0 ){
        printf("Error, connect()\n");
        fprintf(stderr, "connect: %s (%d)\n", strerror(errno), errno);
        exit( EXIT_FAILURE );
    }

    if ( recv( servSocket, recivemsg, sizeof ( recivemsg ), 0 ) < 0 ){
        printf("Error, recv()\n");
        fprintf(stderr, "recv: %s (%d)\n", strerror(errno), errno);
        exit( EXIT_FAILURE );
    }

    printf("Send a msg to the Server:> ");
    fgets(sendmsg, BUFFSIZE, stdin );
    if ( send( servSocket, sendmsg, sizeof ( sendmsg ), 0 ) < 0 ){
        printf("Error, send()\n");
        fprintf(stderr, "send: %s (%d)\n", strerror(errno), errno);
        exit( EXIT_FAILURE );
    }

    close( servSocket );
}

1 个答案:

答案 0 :(得分:1)

代码中存在一些问题,可能会导致服务器程序过早退出。

最重要的是,您不要检查来自readsend呼叫的错误。

如果read调用失败,它将返回-1,您无需检查(这将导致您使用-1作为数组receive的索引,这超出范围并导致undefined behavior)。这继而导致您以断开的连接调用send,这将导致操作系统向您的进程发送SIGPIPE信号。

SIGPIPE的默认行为是终止该过程。

处理此问题的常用方法是忽略 SIGPIPE信号,因为这样send会返回一个错误(它返回-1和{{ 1}}设置为errno)。

处理错误(来自EPIPEread的常见方法是简单地send连接结束。那是因为大多数错误根本无法恢复。