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

Abh*_*ogi 9 c sockets

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

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

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

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

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

如果你想看到整个代码......在这整个代码中.我还在努力解决另一个问题,每当我用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");
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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

#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;
}
Run Code Online (Sandbox Code Playgroud)

paj*_*ton 14

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

摘自recv(3)手册页:

成功完成后,recv()将以字节为单位返回消息的长度.如果没有可接收的消息且对等体已执行有序关闭,则recv()将返回0.否则,将返回-1并设置errno以指示错误.

因此,如果您的客户端优雅地退出,您将recv()在某个时刻获得0 .如果连接以某种方式丢失,您也可能得到-1并检查适当的errno会告诉您连接是否丢失了其他一些错误.有关详细信息,请参阅recv(3)手册页.

编辑:

我看到你正在使用read().仍然是与recv()申请相同的规则.

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

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


mar*_*k4o 10

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

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