使用 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 之上有一个面向数据包的层。
我需要编写一个可以处理多个连接的 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 之上有一个面向数据包的层。