zoukankan      html  css  js  c++  java
  • C 中级

    引言 - 问题由来

      刚开始学习网络编程时候, 常听到一个词, 先开启 "端口复用 SO_REUSEADDR". 那时很一知半解,

    就知道该那么写了. 心里一直有些奇怪, 语义不通呀为啥这么翻译.  后面随着相声听多了, 就明白了些

    道理. 

      倒排索引为啥叫倒排索引?  https://www.zhihu.com/question/23202010

      (这个梗告诉 wo, 索引和反向索引要比正排索引和倒排索引容易理解好多, 信达雅 : )

     随后逛网络帖子恰好看见布道师陈硕介绍 SO_REUSEPORT 时候, 他说的实用起来应该很爽.

      Linux 4.5/4.6 中对 SO_REUSEPORT 的改进  https://zhuanlan.zhihu.com/p/25533528

    文章简单的通过数据结构来表明  SO_REUSEPORT 开启后, 会将 sock 结构放入  port 为 key 的

    hash 结构中. 一联想, 就发现到 epoll 多线程解决方案,  通过 epoll + thread + listen fd  epoll 搞.

    是不是很有意思. 

      后面开始搜集 SO_REUSEPORT 资料, 看到这个

      浅析套接字中SO_REUSEPORT和SO_REUSEADDR的区别  https://blog.csdn.net/Yaokai_AssultMaster/article/details/68951150

    从中提炼几个简单信息. 我们以 linux 行为为基准, 顺带引述 winds 行为. 

    linux -:

      1) . 端口复用 SO_REUSEPORT, 可以顶地址复用 SO_REUSEADDR.

      2) . 都有 userID 安全检查

    winds -:

      1). 只有 SO_REUSEADDR, 轻微像 SO_REUSEPORT 支持多端口绑定.

           但只有最后一个绑定的 socket 能够接收数据.  最后一个 closesocket 后, 最后"第二个"顶. 

         (猜测采用的是 list 结构, 每次 add 到 head, del 到 head. ) 

      2). 没有 userID 安全检查, 依赖它独有安全的选项 SO_EXCLUSIVEADDRUSE

    通过上面信息, 不妨写个通用的复用代码 socket_set_reuse 用起来会很舒服.

    // socket_set_reuse - 开启端口和地址复用
    inline int socket_set_enable(socket_t s, int optname) {
        int ov = 1;
        return setsockopt(s, SOL_SOCKET, optname, (void *)&ov, sizeof ov);
    }
    
    inline int socket_set_reuse(socket_t s) {
        return socket_set_enable(s, SO_REUSEPORT);
    }

    其中 winds 平台构造了如下定义 

    #ifdef _MSC_VER
    
    #define SO_REUSEPORT            SO_REUSEADDR
    
    typedef SOCKET socket_t;#endif

    上面翻译文章中提供链接挺好

      stackoverflow SO_REUSEADDR 和 SO_REUSEPORT differ 回答很有水准

      https://stackoverflow.com/questions/14388706/socket-options-so-reuseaddr-and-so-reuseport-how-do-they-differ-do-they-mean-t/14388707#14388707  

    扯了这么多, 后面会构造代码来验证和表现结果.

    正文 - 实验验证

      首先从 linux 入手, 写一段 SO_REUSEPORT 验证代码  port.c 

    #include <time.h>
    #include <errno.h>
    #include <fcntl.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <signal.h>
    #include <pthread.h>
    #include <sys/types.h>
    
    #include <netdb.h>
    #include <unistd.h>
    #include <arpa/inet.h>
    #include <sys/socket.h>
    #include <netinet/tcp.h>
    
    //
    // CERR - 打印错误信息
    // IF   - 条件判断异常退出的辅助宏
    //
    #define CERR(fmt, ...)                                                   
    fprintf(stderr, "[%s:%s:%d][%d:%s]" fmt "
    ",                            
        __FILE__, __func__, __LINE__, errno, strerror(errno), ##__VA_ARGS__)
    
    #define IF(cond)                                                         
    if ((cond)) do {                                                         
        CERR(#cond);                                                         
        exit(EXIT_FAILURE);                                                  
    } while(0)
    
    // accept_example - SO_REUSEPORT accept example 
    void accept_example(void);
    
    // times_buf - 时间串缓存
    char * times_buf(char buf[BUFSIZ]);
    
    //
    // SO_REUSEPORT :)
    //
    int main(int argc, char * argv[]) {
        // start 10 pthread run accept_example
        pthread_attr_t attr;
        pthread_attr_init(&attr);
        pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
        for (int i = 0; i < 10; ++i) {
            pthread_t tid;
            IF(pthread_create(&tid, &attr, (void * (*)(void *))accept_example, NULL));
        }
        pthread_attr_destroy(&attr);
    
        // main accept block
        accept_example();
        return 0;
    }
    
    // UINT_PORT - 监听端口
    #define UINT_PORT (8088)
    
    // socket_set_enable - 开始 socket 开关
    inline static int socket_set_enable(int s, int optname) {
        int ov = 1;
        return setsockopt(s, SOL_SOCKET, optname, (void *)&ov, sizeof ov);
    }
    
    // accept_example - SO_REUSEPORT accept example 
    void 
    accept_example(void) {
        // 构造 TCP socket
        int s = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
        IF(s == ~0);
    
        // 开启地址复用
        IF(socket_set_enable(s, SO_REUSEADDR));
        IF(socket_set_enable(s, SO_REUSEPORT));
    
        // 构造地址
        struct sockaddr_in addr = {
            AF_INET,
            htons(UINT_PORT),
            { INADDR_ANY },
        };
        socklen_t sen = sizeof addr;
        const struct sockaddr * pddr = (const struct sockaddr *)&addr;
    
        // 绑定地址
        IF(bind(s, pddr, sen));
    
        // 开始监听
        IF(listen(s, SOMAXCONN));
    
        // 等待链接过来, 最多 3 次
        for (int i = 0; i < 3; ++i) {
            char v[BUFSIZ];
            struct sockaddr_in cddr;
            socklen_t cen = sizeof cddr;
    
            printf("[%ld] accept start ... %s
    ", pthread_self(), times_buf(v));
            int c = accept(s, (struct sockaddr *)&cddr, &cen);
            if (s == ~0) {
                CERR("accept s = %d is error", s);
                break;
            }
    
            // 连接成功打印链接消息
            printf("[%ld] [%s:%d] accept success ... %s
    ",
                pthread_self(),
                inet_ntoa(cddr.sin_addr), ntohs(cddr.sin_port),
                times_buf(v));
            
            close(c);
        }
    
        close(s);
    }
    
    // times_buf - 时间串缓存
    char * 
    times_buf(char buf[BUFSIZ]) {
        struct tm m;
        struct timespec s;
    
        timespec_get(&s, TIME_UTC);
        localtime_r(&s.tv_sec, &m);
    
        snprintf(buf, BUFSIZ, "%04d-%02d-%02d %02d:%02d:%02d %03ld",
                 m.tm_year + 1900, m.tm_mon + 1, m.tm_mday,
                 m.tm_hour, m.tm_min, m.tm_sec, 
                 s.tv_nsec / 1000000);
        return buf;
    }

    立即尝试跑一跑

    gcc -g -Wall -o port.out port.c -lpthread
    ./port.out

    看看结果

    顺带写个 winds 实验版本 reuseport_test.c (跨平台)

    #include <times.h>
    #include <socket.h>
    #include <thread.h>
    
    // UINT_PORT - 监听端口
    #define UINT_PORT (8088)
    
    // accept_example - SO_REUSEPORT accept example 
    void accept_example(int id);
    
    // reuseport_test - 端口复用测试
    void reuseport_test(void) {
        // start 10 pthread run accept_example
        for (int i = 1; i <= 10; ++i) {
            IF(pthread_async(accept_example, i));
        }
    
        msleep(609);
    
        // main accept block
        accept_example(0);
    }
    
    // accept_example - SO_REUSEPORT accept example 
    void 
    accept_example(int id) {
        // 构造 TCP socket
        socket_t s = socket_stream();
        IF(s == INVALID_SOCKET);
    
        // 开启地址复用
        IF(socket_set_reuse(s));
    
        // 构造地址
        sockaddr_t addr = {{ AF_INET, htons(UINT_PORT) }};
    
        // 绑定地址
        IF(socket_bind(s, addr));
    
        // 开始监听
        IF(socket_listen(s));
    
        // 等待链接过来, 最多 3 次
        for (int i = 0; i < 3; ++i) {
            times_t v;
            sockaddr_t cddr;
    
            printf("[%2d] accept start ... %s
    ", id, times_str(v));
            socket_t c = socket_accept(s, cddr);
            if (s == INVALID_SOCKET) {
                CERR("accept s = %lld is error", (long long)s);
                break;
            }
    
            // 连接成功打印链接消息
            char ip[INET_ADDRSTRLEN];
            printf("[%2d] [%s:%d] accept success ... %s
    ", id,
                   socket_pton(cddr, ip), ntohs(cddr->sin_port),
                   times_str(v));
    
            socket_close(c);
        }
    
        socket_close(s);
    }

    是不是很清爽, 舒服还是 structc 框架给予的 ~ &(左旋转 90 度) 力量

      structc https://github.com/wangzhione/structc

    玩别人玩剩下的也挺好玩的 哈哈 ~ : ) 

    毕竟写过 -|

    后记 - 搬运打杂

      错误是难免的, 欢迎指正, 共同提升, 感受纯粹的力量

     浪子回头 - http://music.163.com/#/song?id=516728102 

  • 相关阅读:
    moment.js相关知识总结
    git相关使用解释
    .我的第一篇博客
    QT项目配置
    重载->
    内核对象同步
    模式对话框与非模式对话框
    显示与隐式类型转换
    size_t与size_type
    系统级源代码:系统裁剪
  • 原文地址:https://www.cnblogs.com/life2refuel/p/9295570.html
Copyright © 2011-2022 走看看