使用 select() 的 TCP 服务器

TCP server using select()

我需要编写一个可以处理多个连接的 TCP 服务器;我按照 this guide 编写了以下程序:

static void _handle_requests(char* cmd,int sessionfd){
    //TODO: extend
    printf("RECEIVED: %s\n",cmd);
    if (!strcmp(cmd,BAR)){
        barrier_hit(&nodebar,sessionfd);

    }else if (!strcmp(cmd, BYE)){


    }else if (!strcmp(cmd, HI)){

    }
}

void handle_requests(void){
    listen(in_sock_fd,QUEUELEN);
    fd_set read_set, active_set;
    FD_ZERO(&active_set);
    FD_SET(in_sock_fd, &active_set);
    int numfd = 0;
    char cmd[INBUFLEN];
    for (;;){
        read_set = active_set;

        numfd = select(FD_SETSIZE,&read_set,NULL,NULL,NULL);
        for (int i = 0;i < FD_SETSIZE; ++i){
            if (FD_ISSET(i,&read_set)){ 
                if (i == in_sock_fd){
                    //new connection
                    struct sockaddr_in cliaddr;
                    socklen_t socklen = sizeof cliaddr;
                    int newfd = accept(in_sock_fd,(struct sockaddr*)&cliaddr, &socklen);
                    FD_SET(newfd,&active_set);  
                }else{
                    //already active connection
                    read(i,cmd,INBUFLEN);
                    _handle_requests(cmd,i);
                }
            }


        }
    }
}

..和一个客户端连接()到服务器并对套接字文件描述符进行两次连续的 write()调用。

n = write(sm_sockfd, "hi", 3);

    if (n < 0) {
        perror("SM: ERROR writing to socket");
        return 1;
    }

//...later

 n = write(sm_sockfd, "barrier", 8);


    if (n < 0) {
        perror("SM: 'barrier msg' failed");
        exit(1);
    }

问题是,服务器只接收第一条消息 ("hi");之后,select 呼叫挂起。既然客户端的写入("barrier")成功了,那个会话文件描述符不应该准备好读取吗?我有没有犯什么明显的错误?

谢谢;很抱歉,如果这是显而易见的,我完全不熟悉 C 的网络库,而且该项目很快就要到期了!

这样做:

int nbrRead = read(i,cmd,INBUFLEN);

并打印出nbrRead的值。你会看到你一下子收到了所有东西。 TCP 是一种流式传输协议,如果您进行 3 次或更多次连续发送,则很有可能会同时收到它们。

还要确保 INBUFLEN 足够大,2048 对您的示例来说绰绰有余。

您对 TCP 套接字的工作原理有误解。 TCP 中没有消息边界,即如果你先发送 "hi" 然后 "barrier",你不能期望相应的接收到 return "hi" 和 "barrier".他们有可能 return "hibarrier"。从理论上讲,他们也有可能(尽管非常罕见)return "h"、"i"、"b"、"a"、"r"、"r", "i", "e", "r".

您确实需要考虑如何分隔消息。一种可能性是在消息之前以网络字节顺序(4 个字节)将消息的长度作为 32 位整数发送。然后当你收到消息时,你首先读取4个字节,然后读取消息长度指示的字节数。

请注意 TCP 可能 return 部分读取,因此您需要以某种方式处理这些读取。一种可能性是有一个缓冲区来保存读取的字节,然后在读取更多字节时附加到此缓冲区,并在缓冲区的前四个字节(即消息长度)指示您有完整消息。

如果您想要保留数据包边界的顺序数据包协议,您可能需要考虑 SCTP。然而,目前操作系统内核还没有广泛支持它,所以我要做的是使用 32 位长度的技巧在 TCP 之上有一个面向数据包的层。