Как узнать, завершился ли клиент в сокетах - PullRequest
9 голосов
/ 07 апреля 2010

Предположим, у меня есть подключенный сокет после написания этого кода ..

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 ]

14 голосов
/ 07 апреля 2010

После принятия соединения ваш recv() в сокете вернет 0 или -1 в особых случаях.

Выдержка из справочной страницы recv (3) :

После успешного завершения recv () должен вернуть длину сообщения в байтах. Если нет доступных сообщений быть принятым, и сверстник имеет выполнил упорядоченное завершение, recv () вернет 0. В противном случае -1 будет возвращен и ошибочно установлен для указания ошибка.

Итак, если ваш клиент вышел изящно, вы получите 0 от recv() в какой-то момент. Если соединение было каким-то образом потеряно, вы также можете получить -1, и проверка на наличие соответствующего errno сообщит вам, если соединение было потеряно из-за какой-либо другой ошибки. Более подробную информацию смотрите на странице руководства recv (3) .

Edit:

Я вижу, что вы используете read(). Тем не менее, применяются те же правила, что и для recv().

Ваш сервер также может перестать работать при попытке write() к вашим клиентам. Если ваш клиент отключится, write() вернет -1 и значение errno, вероятно, будет установлено на EPIPE. Также, сигнал SIGPIPE будет отправлен вам обработать и убить его, если вы не заблокируете / игнорируете этот сигнал. И вы не так, как я вижу, и именно поэтому ваш сервер завершает работу, когда клиент нажимает Ctrl-C. Ctrl-C завершает работу клиента, поэтому закрывает клиентский сокет и приводит к сбою write() вашего сервера.

См. Ответ mark4o для хорошего подробного объяснения того, что еще может пойти не так.

10 голосов
/ 07 апреля 2010

Если клиентская программа завершает работу, ОС на клиенте закроет свой конец сокета. Когда вы вызываете recv(), он вернет 0 или -1 с ошибкой ECONNRESET, если получен TCP RST (например, потому что вы пытались отправить данные после закрытия клиента). Если весь клиентский компьютер выходит из строя или сеть отключается, то в этом случае вы можете ничего не получать, если сервер ничего не пытается отправить; если это важно обнаружить, вы можете либо периодически отправлять некоторые данные, либо установить опцию сокета SO_KEEPALIVE, используя setsockopt(), чтобы заставить его отправлять пакет без данных после длительные периоды (часы) бездействия. Если подтверждение не получено, recv() вернет -1 с ошибкой ETIMEDOUT или другой ошибкой, если доступна более конкретная информация.

Кроме того, если вы попытаетесь отправить данные по разъему, который был отключен, по умолчанию сигнал SIGPIPE завершит вашу программу. Этого можно избежать, установив для сигнала SIGPIPE действие SIG_IGN (игнорировать) или используя send() с флагом MSG_NOSIGNAL в системах, которые его поддерживают (Linux).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...