• 记一次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] = '' 的操作,所以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
    ",
    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] = '';
    if (wlen < 0) {
        err_exit("write error");
    }

     printf("server write: %s, [%d]bytes ",
     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网络编程》
  • 相关阅读:
    校验参考相关备份
    API接口设计
    redis 基础配置
    Apollo 统一配置中心
    http返回状态码记录
    ngnix实战
    OAuth2三方授权
    OAuth2授权协议记录
    KMP算法
    分治法
  • 原文地址:https://www.cnblogs.com/dennis-wong/p/12121767.html
Copyright © 2020-2023  润新知