如何知道客户端是否已在套接字中终止

时间:2010-04-07 14:25:45

标签: c sockets

假设在编写此代码后我有一个连接的套接字..

if ((sd = accept(socket_d, (struct sockaddr *)&client_addr, &alen)) < 0)
{
    perror("accept failed\n");
    exit(1);
}

如何在服务器端知道客户端已退出。

我的整个程序实际上做了以下事情..

  • 接受来自客户端的连接
  • 启动一个新线程,该线程从该特定客户端读取消息,然后将此消息广播到所有连接的客户端。

如果你想看到整个代码......在这整个代码中。我还在努力解决另外一个问题,即每当我用Ctrl + C杀死一个客户端时,我的服务器突然终止..如果有人能提出问题是什么,那将是很好的

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

/*CONSTANTS*/
#define DEFAULT_PORT 10000 
#define LISTEN_QUEUE_LIMIT 6
#define TOTAL_CLIENTS 10
#define CHAR_BUFFER 256

/*GLOBAL VARIABLE*/
int current_client = 0;
int connected_clients[TOTAL_CLIENTS];
extern int errno;

void *client_handler(void * socket_d);

int main(int argc, char *argv[])
{
    struct sockaddr_in server_addr;/* structure to hold server's address*/
    int    socket_d;             /* listening socket descriptor       */
    int    port;           /* protocol port number              */
    int    option_value;   /* needed for setsockopt             */
    pthread_t tid[TOTAL_CLIENTS];
    port = (argc > 1)?atoi(argv[1]):DEFAULT_PORT;

    /* Socket Server address structure */
    memset((char *)&server_addr, 0, sizeof(server_addr)); 
    server_addr.sin_family = AF_INET;               /* set family to Internet */
    server_addr.sin_addr.s_addr = INADDR_ANY;       /* set the local IP address */
    server_addr.sin_port = htons((u_short)port);    /* Set port */

    /* Create socket */
    if ( (socket_d = socket(PF_INET, SOCK_STREAM, 0)) < 0) {
        fprintf(stderr, "socket creation failed\n");
        exit(1);
    }

    /* Make listening socket's port reusable */
    if (setsockopt(socket_d, SOL_SOCKET, SO_REUSEADDR, (char *)&option_value, 
                sizeof(option_value)) < 0) {
        fprintf(stderr, "setsockopt failure\n");
        exit(1);
    }

    /* Bind a local address to the socket */
    if (bind(socket_d, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
        fprintf(stderr, "bind failed\n");
        exit(1);
    }

    /* Specify size of request queue */
    if (listen(socket_d, LISTEN_QUEUE_LIMIT) < 0) {
        fprintf(stderr, "listen failed\n");
        exit(1);
    }

    memset(connected_clients,0,sizeof(int)*TOTAL_CLIENTS);

    for (;;)
    {
        struct sockaddr_in client_addr;    /* structure to hold client's address*/
        int    alen = sizeof(client_addr); /* length of address                 */
        int    sd;                /* connected socket descriptor */

        if ((sd = accept(socket_d, (struct sockaddr *)&client_addr, &alen)) < 0)
        {
            perror("accept failed\n");
            exit(1);
        }
        else printf("\n I got a connection from (%s , %d)\n",inet_ntoa(client_addr.sin_addr),ntohs(client_addr.sin_port));

        if (pthread_create(&tid[current_client],NULL,(void *)client_handler,(void *)sd) != 0)
        {
            perror("pthread_create error");
            continue;
        }
        connected_clients[current_client]=sd;
        current_client++; /*Incrementing Client number*/
    }

    return 0;
}

void *client_handler(void *connected_socket)
{
    int sd;
    sd = (int)connected_socket;
    for ( ; ; ) 
    {
        ssize_t n;
        char buffer[CHAR_BUFFER];
        for ( ; ; )
        {
            if (n = read(sd, buffer, sizeof(char)*CHAR_BUFFER) == -1)
            {
                perror("Error reading from client");
                pthread_exit(1);
            }
            int i=0;
            for (i=0;i<current_client;i++)
            {
                if (write(connected_clients[i],buffer,sizeof(char)*CHAR_BUFFER) == -1)
                    perror("Error sending messages to a client while multicasting");
            }
        }
    }
}

我的客户方是这个(在回答我的问题时可能无关紧要)

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h> 
#include <string.h>
#include <stdlib.h>

void error(char *msg)
{
    perror(msg);
    exit(0);
}

void *listen_for_message(void * fd)
{
    int sockfd = (int)fd;
    int n;
    char buffer[256];
    bzero(buffer,256);
    printf("YOUR MESSAGE: ");
    fflush(stdout);
    while (1)
    {
        n = read(sockfd,buffer,256);
        if (n < 0) 
            error("ERROR reading from socket");
        if (n == 0) pthread_exit(1);
        printf("\nMESSAGE BROADCAST: %sYOUR MESSAGE: ",buffer);
        fflush(stdout);
    }
}

int main(int argc, char *argv[])
{
    int sockfd, portno, n;
    struct sockaddr_in serv_addr;
    struct hostent *server;
    pthread_t read_message;
    char buffer[256];
    if (argc < 3) {
        fprintf(stderr,"usage %s hostname port\n", argv[0]);
        exit(0);
    }
    portno = atoi(argv[2]);
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0) 
        error("ERROR opening socket");
    server = gethostbyname(argv[1]);
    if (server == NULL) {
        fprintf(stderr,"ERROR, no such host\n");
        exit(0);
    }
    bzero((char *) &serv_addr, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    bcopy((char *)server->h_addr, 
            (char *)&serv_addr.sin_addr.s_addr,
            server->h_length);
    serv_addr.sin_port = htons(portno);
    if (connect(sockfd,&serv_addr,sizeof(serv_addr)) < 0) 
        error("ERROR connecting");
    bzero(buffer,256);
    if (pthread_create(&read_message,NULL,(void *)listen_for_message,(void *)sockfd) !=0 )
    {
        perror("error creating thread");
    }
    while (1)
    {
        fgets(buffer,255,stdin);
        n = write(sockfd,buffer,256);
        if (n < 0) 
            error("ERROR writing to socket");
        bzero(buffer,256);
    }
    return 0;
}

2 个答案:

答案 0 :(得分:14)

接受连接后,套接字上的recv()将在特殊情况下返回0或-1。

摘自recv(3) man page

  

成功完成后,recv()   应返回消息的长度   以字节为单位如果没有可用的消息   收到和同行有   执行有序关机,recv()   应返回0.否则,-1应为   返回并且errno设置为表示   错误。

因此,如果您的客户端正常退出,您将在recv()处获得0。如果连接以某种方式丢失,您也可能得到-1并检查适当的errno会告诉您连接是否丢失了其他一些错误。请在recv(3) man page了解详情。

编辑:

我看到你正在使用read()。仍然适用与recv()相同的规则。

尝试write()给您的客户时,您的服务器也可能失败。如果您的客户端断开连接write()将返回-1,并且errno可能会设置为EPIPE。此外,如果您不阻止/忽略此信号,SIGPIPE信号将发送给您进程并杀死他。而且你没有看到,这就是当客户端按下Ctrl-C时服务器终止的原因。 Ctrl-C终止客户端,因此关闭客户端套接字并使服务器的write()失败。

有关其他可能出错的详细解释,请参阅mark4o的答案。

答案 1 :(得分:10)

如果客户端程序退出,则客户端上的操作系统将关闭其套接字的末尾。当你收到TCP RST时(例如,因为你试图在客户端关闭后发送数据),当你调用recv()时它将返回0,或者带有errno ECONNRESET的-1。如果整个客户端计算机出现故障,或者网络断开连接,那么在这种情况下,如果服务器没有尝试发送任何内容,您可能不会收到任何内容;如果这对于检测很重要,您可以定期发送一些数据,或者使用SO_KEEPALIVE设置setsockopt()套接字选项,以强制它在长时间(小时)不活动后发送没有数据的数据包。如果没有收到确认,recv()将返回-1,错误号为ETIMEDOUT,如果有更多具体信息,则返回另一个错误。

此外,如果您尝试在已断开连接的套接字上发送数据,则默认情况下SIGPIPE信号将终止您的程序。通过将SIGPIPE信号操作设置为SIG_IGN(忽略),或者在支持它的系统(Linux)上使用send()MSG_NOSIGNAL标志,可以避免这种情况。