栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 系统运维 > 运维 > 服务器

详解从Linux源码看Socket(TCP)的bind

服务器 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力
目录
  • 一、一个最简单的Server端例子
  • 二、bind系统调用
    • 2.1、inet_bind
    • 2.2、inet_csk_get_port
  • 三、判断端口号是否冲突
    • 四、SO_REUSEADDR和SO_REUSEPORT
      • 五、SO_REUSEADDR
        • 六、SO_REUSEPORT
          • 七、总结

            一、一个最简单的Server端例子

            众所周知,一个Server端Socket的建立,需要socket、bind、listen、accept四个步骤。



            代码如下:

            void start_server(){
                // server fd
                int sockfd_server;
                // accept fd 
                int sockfd;
                int call_err;
                struct sockaddr_in sock_addr;
            
                sockfd_server = socket(AF_INET,SOCK_STREAM,0);
                memset(&sock_addr,0,sizeof(sock_addr));
                sock_addr.sin_family = AF_INET;
                sock_addr.sin_addr.s_addr = htonl(INADDR_ANY);
                sock_addr.sin_port = htons(SERVER_PORT);
                // 这边就是我们今天的聚焦点bind
                call_err=bind(sockfd_server,(struct sockaddr*)(&sock_addr),sizeof(sock_addr));
                if(call_err == -1){
             fprintf(stdout,"bind error!n");
             exit(1);
                }
                // listen
                call_err=listen(sockfd_server,MAX_BACK_LOG);
                if(call_err == -1){
             fprintf(stdout,"listen error!n");
             exit(1);
                }
            }

            首先我们通过socket系统调用创建了一个socket,其中指定了SOCK_STREAM,而且最后一个参数为0,也就是建立了一个通常所有的TCP Socket。在这里,我们直接给出TCP Socket所对应的ops也就是操作函数。



            二、bind系统调用

            bind将一个本地协议地址(protocol:ip:port)赋予一个套接字。例如32位的ipv4地址或128位的ipv6地址+16位的TCP活UDP端口号。

            #include 
            // 返回,若成功则为0,若出错则为-1
            int bind(int sockfd, const struct sockaddr *myaddr, socklen_t addrlen); 

            好了,我们直接进入Linux源码调用栈吧。

            bind

            // 这边由系统调用的返回值会被glibc的INLINE_SYSCALL包一层

            // 若有错误,则设置返回值为-1,同时将系统调用的返回值的绝对值设置给errno

            |->INLINE_SYSCALL (bind......);

            |->SYSCALL_DEFINE3(bind......);

            ){ // 即有一方没有设置 }

            如果sk2(即已bind的socket)是TCP_LISTEN状态或者,sk2和新sk两者都没有设置_REUSEADDR的时候,可以判断为冲突。

            我们可以得出,如果原sock和新sock都设置了SO_REUSEADDR的时候,只要原sock不是Listen状态,都可以绑定成功,甚至ESTABLISHED状态也可以!



            这个在我们平常工作中,最常见的就是原sock处于TIME_WAIT状态,这通常在我们关闭Server的时候出现,如果不设置SO_REUSEADDR,则会绑定失败,进而启动不来服务。而设置了SO_REUSEADDR,由于不是TCP_LISTEN,所以可以成功。



            这个特性在紧急重启以及线下调试的非常有用,建议开启。

            六、SO_REUSEPORT

            SO_REUSEPORT是Linux在3.9版本引入的新功能。

            1.在海量高并发连接的创建时候,由于正常的模型是单线程listener分发,无法利用多核优势,这就会成为瓶颈。

            2.CPU缓存行丢失

            我们看下一般的Reactor线程模型,



            明显的其单线程listen/accept会存在瓶颈(如果采用多线程epoll accept,则会惊群,加WQ_FLAG_EXCLUSIVE可以解决一部分),尤其是在采用短链接的情况下。
            鉴于此,Linux增加了SO_REUSEPORT,而之前bind中判断是否冲突的下面代码也是为这个参数而添加的逻辑:

            if(!reuseport || !sk2->sk_reuseport ||
            			    (sk2->sk_state != TCP_TIME_WAIT &&
            			     !uid_eq(uid, sock_i_uid(sk2))

            这段代码让我们在多次bind的时候,如果设置了SO_REUSEPORT的时候不会报错,也就是让我们有个多线程(进程)bind/listen的能力。如下图所示:



            而开启了SO_REUSEPORT后,代码栈如下:

            tcp_v4_rcv
            	|->__inet_lookup_skb 
            		|->__inet_lookup
            			|->__inet_lookup_listener
             
            struct sock *__inet_lookup_listener(......)
            {
            	......
            	if (score > hiscore) {
            			result = sk;
            			hiscore = score;
            			reuseport = sk->sk_reuseport;
            			if (reuseport) {
            				phash = inet_ehashfn(net, daddr, hnum,
            						     saddr, sport);
            				matches = 1;
            			}
            		} else if (score == hiscore && reuseport) {
            			matches++;
            			if (((u64)phash * matches) >> 32 == 0)
            				result = sk;
            			phash = next_pseudo_random32(phash);
            		}
            	......
            }

            直接在内核层面做负载均衡,将accept的任务分散到不同的线程的不同socket上(Sharding),毫无疑问可以多核能力,大幅提升连接成功后的socket分发能力。

            Nginx已经采用SO_REUSEPORT

            Nginx在1.9.1版本的时候引入了SO_REUSEPORT,配置如下:

            http {
                 server {
               listen 80 reuseport;
               server_name  localhost;
               # ...
                 }
            }
            
            stream {
                 server {
               listen 12345 reuseport;
               # ...
                 }
            }





            七、总结

            Linux内核源码博大精深,一个看起来简单的bind系统调用竟然牵涉这么多,在里面可以挖掘出各种细节。在此分享出来,希望对读者有所帮助。

            以上就是详解从Linux源码看Socket(TCP)的bind的详细内容,更多关于从Linux Socket(TCP) bind的资料请关注考高分网其它相关文章!

            转载请注明:文章转载自 www.mshxw.com
            本文地址:https://www.mshxw.com/it/304914.html
            我们一直用心在做
            关于我们 文章归档 网站地图 联系我们

            版权所有 (c)2021-2022 MSHXW.COM

            ICP备案号:晋ICP备2021003244-6号