【问题标题】:Socket Server Hangs套接字服务器挂起
【发布时间】:2014-04-12 21:45:39
【问题描述】:

这个套接字服务器有一些错误,很可能与 pthread 相关。如果这继续接受客户端连接,一段时间后它将开始挂起。这似乎不是内存泄漏,因为程序占用的内存保持不变,但是当我与 telnet 客户端连接时,它只会挂起并且不执行任何操作。

它仍然会到达 puts("处理程序分配"); 部分,但它什么也不做。有什么想法会导致这种情况吗?

/*
    C socket server example, handles multiple clients using threads
*/

#include<stdio.h>
#include<string.h>    //strlen
#include<stdlib.h>    //strlen
#include<sys/socket.h>
#include<arpa/inet.h> //inet_addr
#include<unistd.h>    //write
#include<pthread.h> //for threading , link with lpthread

//the thread function
void *connection_handler(void *);

int main(int argc , char *argv[])
{
    int socket_desc , client_sock , c , *new_sock;
    struct sockaddr_in server , client;

    //Create socket
    socket_desc = socket(AF_INET , SOCK_STREAM , 0);
    if (socket_desc == -1)
    {
        printf("Could not create socket");
    }
    puts("Socket created");

    //Prepare the sockaddr_in structure
    server.sin_family = AF_INET;
    server.sin_addr.s_addr = INADDR_ANY;
    server.sin_port = htons( 8888 );

    //Bind
    if( bind(socket_desc,(struct sockaddr *)&server , sizeof(server)) < 0)
    {
        //print the error message
        perror("bind failed. Error");
        return 1;
    }
    puts("bind done");

    //Listen
    listen(socket_desc , 3);

    //Accept and incoming connection
    puts("Waiting for incoming connections...");
    c = sizeof(struct sockaddr_in);


    //Accept and incoming connection
    puts("Waiting for incoming connections...");
    c = sizeof(struct sockaddr_in);
    while( (client_sock = accept(socket_desc, (struct sockaddr *)&client, (socklen_t*)&c)) )
    {
        puts("Connection accepted");

        pthread_t sniffer_thread;
        new_sock = malloc(1);
        *new_sock = client_sock;

        if( pthread_create( &sniffer_thread , NULL ,  connection_handler , (void*) new_sock) < 0)
        {
            perror("could not create thread");
            return 1;
        }

        //Now join the thread , so that we dont terminate before the thread
        //pthread_join( sniffer_thread , NULL);
        puts("Handler assigned");
    }

    if (client_sock < 0)
    {
        perror("accept failed");
        return 1;
    }

    return 0;
}

/*
 * This will handle connection for each client
 * */
void *connection_handler(void *socket_desc)
{
    //Get the socket descriptor
    int sock = *(int*)socket_desc;
    int read_size;
    char *message , client_message[2000];

    //Send some messages to the client
    message = "Greetings! I am your connection handler\n";
    write(sock , message , strlen(message));

    message = "Now type something and i shall repeat what you type \n";
    write(sock , message , strlen(message));

    //Receive a message from client
    while( (read_size = recv(sock , client_message , 2000 , 0)) > 0 )
    {
        //Send the message back to client
        write(sock , client_message , strlen(client_message));
    }

    if(read_size == 0)
    {
        puts("Client disconnected");
        fflush(stdout);
    }
    else if(read_size == -1)
    {
        perror("recv failed");
    }

    //Free the socket pointer
    free(socket_desc);

    return 0;
}

【问题讨论】:

  • 你只为一个 int 分配了一个字节。这不好。 (当它明显是 C 时,为什么要标记为 C++?)
  • 你为什么注释掉加入?系统上的僵尸线程会发生什么?
  • 您的 while/accept 循环没有意义。 accept() 不返回零,所以循环直到它返回是徒劳的,但它确实返回 -1,而且你没有在正确的地方检查它。返回消息时应使用 read_size 而不是 strlen()。
  • 不要分配int,只需将套接字转换为void*并将其作为threadproc参数传递,然后将threadproc的void*参数转换回int。跨度>

标签: c sockets


【解决方案1】:
  1. 不要假设写入套接字会写入所有字节。始终检查返回值。而不是 write 使用您自己的 sendbuf() 函数,该函数在循环中写入套接字并仅在发送缓冲区的所有字节时才返回。`
  2. 您必须将每个创建的线程都加入一次,否则会泄漏线程句柄。如果您不想加入一个线程,那么您有 2 个选项:
    1. 通过指定 pthread 的属性参数将其创建为分离的,您可以在其中告诉pthread_create() 将线程创建为分离的。搜索使用 pthread_attr_setdetachstate() 函数的教程,了解如何执行此操作。
    2. 创建线程后调用pthread_detach() 告诉pthread 库您不想加入线程。
  3. 如果服务器和客户端都使用阻塞套接字,则由于您轮流接收和发送,因此如果客户端尝试发送足够大的缓冲区并导致死锁,则发送缓冲区可能会在两端填满。使用以下解决方案之一来帮助这一点:
    1. 在您的服务器中使用setsockopt() 来设置发送/接收超时,使用SO_SNDTIMEOSO_RCVTIMEO 套接字选项,如果需要,可以选择使用SO_SNDBUF SO_RCVBUF 选项调整发送/接收缓冲区大小,但我不会'不要无故设置后两个选项。
    2. 至少有一个对等方(最好是服务器)应该使用 asnyc 套接字同时接收和发送。
  4. 在向客户端发回消息时使用read_size 而不是strlen(client_message)。假设接收到的块是零终止的,即使客户端发送了一个零终止的消息也是错误的,因为您可能会收到它作为碎片。

【讨论】:

  • 我不能同意(3)。它只是一个回显服务器,而且他不是同时阅读和写作:他是在阅读然后写作。同样 (3) 与 (1) 相矛盾。
  • @EJP 这只是我糟糕的英语,我认为轮流接收和发送,但由于某种原因,我的母语以更接近我定义我的观点 3 的方式来说明这一点。谢谢为了指出这一点,我更正它。
  • Echo 服务器的存在时间比异步 I/O 长了几十年。 Ergo 异步 I/O 不是编写正确的回显服务器所必需的。 Re (1) 你会发现所有已知的实现都以阻塞模式写入所有提供的数据。几年前在 news:comp.protocols.tcp-ip 中对此进行了讨论,所有实现者都同意。
  • @EJP 如果客户端尝试写入整个缓冲区(可能有更多 send/write 调用)直到发送整个缓冲区,那么如果缓冲区大于 1 的总和。)发送缓冲区客户端 sock 2.) 服务器 sock 的 recv 缓冲区 3.) 服务器 sock 的发送缓冲区,那么可能会发生所有 3 个缓冲区都填满,然后服务器和客户端都阻塞发送。当然,如果服务器和客户端都轮流接收/发送,那么阻塞套接字会起作用,但我通常在服务器端更加偏执。
  • @EJP 重新考虑编写阻塞套接字实现通常更简单,并且使用 setsockopt()SO_SNDTIMEO, SO_RCVTIMEO, SO_SNDBUF, SO_RCVBUF 参数会导致完全可以接受/可调整和安全的实现,但 OP 当前的实现不包括这个设置。
猜你喜欢
  • 1970-01-01
  • 2018-05-07
  • 2015-09-10
  • 2018-01-07
  • 2015-06-21
  • 1970-01-01
  • 1970-01-01
  • 2016-06-05
  • 1970-01-01
相关资源
最近更新 更多