Loading

记一次echo server出现的问题

1. 我做了什么

最近在学习如何用select函数实现echo server。期间遇到了一个关于缓冲区的问题,在这里分享给大家。
在使用read/recv, write/send类函数进行数据传输时,需要程序员手动创建缓冲区。通常(各种书籍中),大家都会将缓冲区的大小设置为1024或其整数倍。但是我在编写echo server时就遇到了一些问题。以下是echo server的图示。缓冲区大小是BUFFLEN=1024。
 
客户端这里write的长度是strlen(buffer),read的长度是BUFFLEN-1,这块我是参考《TCP/IP网络编程》这本书的。因为随后还有一个buffer[BUFFLEN] = '\0' 的操作,所以read的长度是BUFFLEN-1(为了凑成一个字符串)。(谁能知道就是这里出了问题)。同时,服务端这里write和read的长度都是BUFFLEN。
 

2. 出现了什么问题

事情是这样的,当我开心参考着书上的代码,在电脑上敲了一遍,一顿编译之后,发现只有第一次能够echo成功,后续客户端的输入并不能被有效的echo回客户端,如下图所示。
客户端:
 
服务端:
我们可以看到服务端这块的读写还是正常的,但是客户端在第二次的read中,仅仅读了1个字节。我查阅了书中相关的内容,发现我的服务端代码和书上稍有不同。
 
//服务端代码
char buffer[BUFFLEN];
memset(buffer, 0, BUFFLEN);
int strlen = read(i, buffer, BUFFLEN);
printf("server read: %s, [%d]bytes\n",
buffer, strlen);
if (strlen == 0) {
    FD_CLR(i, &rdset);
    close(i);
} else {
//不同的地方:
//int wlen = write(i, buffer, strlen); 原书的代码
int wlen = write(i, buffer, BUFFLEN);
buffer[strlen] = '\0';
if (wlen < 0) {
    err_exit("write error");
}

 printf("server write: %s, [%d]bytes\n",
 buffer, wlen);

我很奇怪为什么仅仅write的长度不同就会导致这么严重的问题。通过查阅write, read的手册,还有抓包,我也没有找到问题的根源在哪里,唯一可以确定的是服务端应该是没有问题的,因为通过wireshark,我发现正常情况下的服务端和异常情况下的服务端的tcp报文的数量和顺序都是相同的。尝试了一些方案,比如 每次I/O前memset清空用户态缓冲区等等都没什么用。这个问题持续了有一天,周一早上我想着会不会是write和read操作的缓冲区长度不一致所导致的。

 

[图挂了]

服务端write 
 
[图挂了]
 
客户端read
 
我尝试修改客户端的read读取的字节数为BUFFLEN竟发现这个问题被解决了。果然就是我预想的那样!!!
 
修改后的read
 
之所以会出现这个问题,是因为内核协议栈(或者是page cache?这个还需要进一步探讨)在客户和服务两端都存在相应的内核缓冲区,服务端会write到客户端的buffer中,而客户端的read会清空相应大小的缓冲区内容。试想如果服务端写入了1024字节,但是客户端只读取1023个字节,则此次操作缓存区内会剩余一个字节内容。下一次客户端的read就会将这一字节读到用户态缓冲中。
 
在客户端的输出中会出现"recv: 1 byte"这样的内容就是这个原因。
 
那么如何避免这种情况的出现呢?很明显,read/write, send/recv这些都是配对的操作。程序员要保证的就是write写入到内核缓冲区的内容必须能够被匹配的read读取完,不要在缓冲区中遗留陈旧的数据。
 

3. 遗留问题

肯定不会是我申请的用户缓冲区的问题,到底是那块的缓冲区出现问题,我也不太清楚。

4. 参考

[1]《TCP/IP网络编程》
posted @ 2019-12-30 21:13  成蹊0xc000  阅读(252)  评论(0编辑  收藏  举报