• socket 踩坑日记



    近日参加阿里云的比赛,其中需要写一个分布式数据库,起初网络模块使用了 rest_rpc,然后各种优化代码的性能,都达不到上亿请求吞吐量的请求下吞吐量的要求,(太慢了),因此必须从scoket写起,针对比赛的特殊场景创建协议,降低网络库中的对象的申请和释放,降低走网络上发送的数据量。

    记录一下socket过程中遇到的坑,首先服务端和客户端的参考代码如下,可以再次基础上简单改造(代码来自http://c.biancheng.net/view/2128.html

    代码实例

    ser.cc

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    int main(){
        //创建套接字
        int serv_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    
        //将套接字和IP、端口绑定
        struct sockaddr_in serv_addr;
        memset(&serv_addr, 0, sizeof(serv_addr));  //每个字节都用0填充
        serv_addr.sin_family = AF_INET;  //使用IPv4地址
        serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");  //具体的IP地址
        serv_addr.sin_port = htons(1234);  //端口
        bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
    
        //进入监听状态,等待用户发起请求
        listen(serv_sock, 20);
    
        //接收客户端请求
        struct sockaddr_in clnt_addr;
        socklen_t clnt_addr_size = sizeof(clnt_addr);
        int clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_addr, &clnt_addr_size);
    
        //向客户端发送数据
        char str[] = "http://c.biancheng.net/socket/";
        write(clnt_sock, str, sizeof(str));
       
        //关闭套接字
        close(clnt_sock);
        close(serv_sock);
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    cli.cc

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    int main(){
        //创建套接字
        int sock = socket(AF_INET, SOCK_STREAM, 0);
        //向服务器(特定的IP和端口)发起请求
        struct sockaddr_in serv_addr;
        memset(&serv_addr, 0, sizeof(serv_addr));  //每个字节都用0填充
        serv_addr.sin_family = AF_INET;  //使用IPv4地址
        serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");  //具体的IP地址
        serv_addr.sin_port = htons(1234);  //端口
        connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
       
        //读取服务器传回的数据
        char buffer[40];
        read(sock, buffer, sizeof(buffer)-1);
       
        printf("Message form server: %s\n", buffer);
       
        //关闭套接字
        close(sock);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    服务端与客户端reuse

    在上述代码中,很容易简单的模拟出(比如加入getchar、sleep等操作)服务器客户端分别进入TIME_WAIT状态的情形。
    服务端进入TIME_WAIT状态
    服务端进入TIME_WAIT状态
    客户端进入TIME_WAIT状态
    在这里插入图片描述
    对于服务端进入了timewait状态,这时候,如果重启服务器,bind同一个ip:port,就会出错,错误码errno=EADDRINUSE(错误码98)

    • 解决方式
      对于服务端,可以设置SO_REUSEADDR解决,参考SO_REUSEADDR;对于客户端,这个问题也是需要解决的(防止用尽port),可以设置tcp_tw_reuse解决。参考tcp_tw_reuse

    客户端connect失败

    如果没有启动服务端,则客户端会设置错误码errno=ECONNREFUSED(错误码111),进一步,如果没检验端口直接对着该connect返回值调用read,会设置错误码errno=ENOTCONN(错误码107)

    对read的分析

    • read对于读取的字节数是没有保证的,只要能读取到大于1的字节数,就会立即返回!(也可能返回0,因为对端关闭了,这个后面说)

    如果在read的过程中,对端关闭了,那本端是否能观察呢?分为多种情况

    1. read buffer中没有数据,那么read会直接返回0,不会设置错误码
    2. 当返回值是-1时,需要检查errno是否是EINTR(4),如果是EINTR(4)的话则忽略本次异常。这种情况目前我没有遇到。

    对write的分析

    • 当write的时候,或者write之前,或者write之后,对端意外退出了,怎么办呢

    write有一个复杂的操作在这里分析一下。参考网络编程中的 SIGPIPE 信号(http://senlinzhan.github.io/2017/03/02/sigpipe/)

    一个解决方式是,忽略SIGPIPE信号,signal(SIGPIPE, SIG_IGN); // 忽略 SIGPIPE 信号,然后自己handle错误码。即写入对端时,会把设置errno=EPIPE(错误码32)

    还有一个特殊情况,当write调用的过程中,对端被kill掉了。此时write系统调用也会立即返回,并且设置错误码errno=ECONNRESET(错误码104),紧接着的下一次调用会把设置errno=EPIPE(错误码32)。(忽略SIGPIPE)的前提下。

    write、read的返回值即使大于0也需要检查

    如标题,write和read可能并没有按照预取写入或者读取对应大小的数据。因此需要检查返回值。比如

    int writen_bytes = write(fd, buf, 100);
    check(writen_bytes == 100);
    
    • 1
    • 2

    EAGAIN 和 EINTR

    这里目前不是很确定Linux网络编程中EAGAIN错误和EINTR错误

    [tcp] nodelay & quickack

    nodelay & quickack
    对于点对点连接,同步读->写->读->写->读->写这种交互,应该可以加速

  • 相关阅读:
    你不知道的JS 之 this& this指向
    【web3py】批量创建eth账号
    web前端面试高频考点——Vue的高级特性(动态组件、异步加载、keep-alive、mixin、Vuex、Vue-Router)
    MySQL 45 讲 | 13 为什么表数据删掉一半,表文件大小不变?
    java-net-php-python-jsp校园招聘管理系统计算机毕业设计程序
    孙卫琴的《精通JPA与Hibernate》的读书笔记:JPA的事件处理API的用法
    如何制作有专业水准的的电子杂志:专家教你秘籍
    牛客网:NC129 阶乘末尾0的数量
    NodeJs 性能提升方案
    代码出炉结构乱?Maven整理省心办。
  • 原文地址:https://blog.csdn.net/weixin_44520881/article/details/126688384