【问题标题】:How to know if the client has terminated in sockets如何知道客户端是否已在套接字中终止
【发布时间】:2010-04-07 14:25:44
【问题描述】:

假设,写完这段代码后我有一个连接的套接字..

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;
}

【问题讨论】:

  • 你能包含更多的代码吗?这是什么插座?服务器端是什么意思?
  • 我已经添加了完整的服务器端代码。我刚开始学习套接字编程,所以服务器端不是那么健壮......欢迎任何建议。

标签: c sockets


【解决方案1】:

接受连接后,套接字上的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 的回答,以获取有关其他可能出错的详细说明。

【讨论】:

  • 不,那只是服务器端关闭了套接字。
  • 怎么回事?在您accept() 之后,您从它读取的套接字就像从任何其他套接字一样。
  • 在向套接字写入时也要注意errno == EPIPE
  • @WhirlWind:这是不正确的。套接字没有任何“服务器端”的内部概念
【解决方案2】:

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

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

【讨论】:

  • +1 用于详细解释更多特殊情况。
  • 即使这已经很老了,关于如果客户端断开连接我的进程为什么会终止的解释只是拯救了我的一天!非常感谢!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-10-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多