黑客业务

24小时接单的黑客,黑客业务,黑客怎么找,网络黑客,黑客技术

多进程可以监听同一端口吗

当然,只要你用 ,SO_REUSEPORT 这个参数。

先来看看man文档中是怎么说的:

  • SO_REUSEPORT(sinceLinux3.9)
  • PermitsmultipleAF_INETorAF_INET6socketstobeboundtoan
  • identicalsocketaddress.Thisoptionmustbesetoneach
  • socket(includingthefirstsocket)priortocallingbind(2)
  • onthesocket.Topreventporthijacking,allofthepro‐
  • cessesbindingtothesameaddressmusthavethesameeffec‐
  • tiveUID.ThisoptioncanbeemployedwithbothTCPandUDP
  • sockets.
  • ForTCPsockets,thisoptionallowsaccept(2)loaddistribu‐
  • tioninamulti-threadedservertobeimprovedbyusingadis‐
  • tinctlistenersocketforeachthread.Thisprovidesimproved
  • loaddistributionascomparedtotraditionaltechniquessuch
  • usingasingleaccept(2)ingthreadthatdistributesconnec‐
  • tions,orhavingmultiplethreadsthatcompetetoaccept(2)
  • fromthesamesocket.
  • ForUDPsockets,theuseofthisoptioncanprovidebetter
  • distributionofincomingdatagramstomultipleprocesses(or
  • threads)ascomparedtothetraditionaltechniqueofhaving
  • multipleprocessescompetetoreceivedatagramsonthesame
  • socket.
  • 从文档中可以看出,该参数允许多个socket即使绑定到同一地址,socket是处于listen状态的。

    当多个listen状态的socket绑定到同一地址时,各个socket的accept操作可以接受新的tcp连接。

    写段代码测试很神奇,对吧?

  • #include<arpa/inet.h>
  • #include<assert.h>
  • #include<stdio.h>
  • #include<stdlib.h>
  • #include<strings.h>
  • #include<sys/socket.h>
  • #include<sys/types.h>
  • #include<unistd.h>
  • staticinttcp_listen(char*ip,intport){
  • intlfd,opt,err;
  • structsockaddr_inaddr;
  • lfd=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
  • assert(lfd!=-1);
  • opt=1;
  • err=setsockopt(lfd,SOL_SOCKET,SO_REUSEPORT,&opt,sizeof(opt));
  • assert(!err);
  • bzero(&addr,sizeof(addr));
  • addr.sin_family=AF_INET;
  • addr.sin_addr.s_addr=inet_addr(ip);
  • addr.sin_port=htons(port);
  • err=bind(lfd,(structsockaddr*)&addr,sizeof(addr));
  • assert(!err);
  • err=listen(lfd,8);
  • assert(!err);
  • returnlfd;
  • }
  • intmain(intargc,char*argv[]){
  • intlfd,sfd;
  • lfd=tcp_listen("127.0.0.1",8888);
  • while(1){
  • sfd=accept(lfd,NULL,NULL);
  • close(sfd);
  • printf("接收到tcp连接:%d\n",sfd);
  • }
  • return0;
  • }
  • 编译和执行程序:

  • $gccserver.c&&./a.out
  • 看看目前8888端口的所有内容socket的状态:

  • $ss-antp|grep8888
  • LISTEN08127.0.0.1:88880.0.0.0:*users:(("a.out",pid=32505,fd=3))
  • 和我们预期的一样,只有一个socket处于listen状态。

    我们再次执行这个程序:

  • $gccserver.c&&./a.out
  • 再次检查888端口socket的状态:

  • $ss-antp|grep8888
  • LISTEN08127.0.0.1:88880.0.0.0:*users:(("a.out",pid=32607,fd=3))
  • LISTEN08127.0.0.1:88880.0.0.0:*users:(("a.out",pid=32505,fd=3))
  • 这时已经有两个了socket监控8888端口(注意他们ip地址也一样),这两个socket属于两个过程。

    我们现在再用ncat连接8888端口的模拟客户端:

  • $ncatlocalhost8888
  • 重复操作并建立n个到8888端口的tcp此时,两个服务终端的输出如下。

    服务端1:

  • $gccserver.c&&./a.out
  • 接收到tcp连接:4
  • 接收到tcp连接:4
  • 接收到tcp连接:4
  • 服务端2:

  • $gccserver.c&&./a.out
  • 接收到tcp连接:4
  • 接收到tcp连接:4
  • 可以看到,tcp连接基本上均匀分布在两个服务器上,神奇。

    下面我们来看看相应的。linux内核代码,看看它是如何实现的。

  • //net/ipv4/inet_connection_sock.c
  • intinet_csk_get_port(structsock*sk,unsignedshortsnum)
  • {
  • ...
  • structinet_hashinfo*hinfo=sk->sk_prot->h.hashinfo;
  • intret=1,port=snum;
  • structinet_bind_hashbucket*head;
  • ...
  • structinet_bind_bucket*tb=NULL;
  • ...
  • head=&hinfo->bhash[inet_bhashfn(net,port,
  • hinfo->bhash_size)];
  • ...
  • inet_bind_bucket_for_each(tb,&head->chain)
  • if(net_eq(ib_net(tb),net)&&tb->l3mdev==l3mdev&&
  • tb->port==port)
  • gototb_found;
  • tb_not_found:
  • tb=inet_bind_bucket_create(hinfo->bind_bucket_cachep,
  • net,head,port,l3mdev);
  • ...
  • tb_found:
  • if(!hlist_empty(&tb->owners)){
  • ...
  • if(...||sk_reuseport_match(tb,sk))
  • gotosuccess;
  • ...
  • }
  • success:
  • if(hlist_empty(&tb->owners)){
  • ...
  • if(sk->sk_reuseport){
  • tb->fastreuseport=FASTREUSEPORT_ANY;
  • ...
  • }else{
  • tb->fastreuseport=0;
  • }
  • }else{
  • ...
  • }
  • ...
  • }
  • EXPORT_SYMBOL_GPL(inet_csk_get_port);
  • 当我们做bind此方法将在操作时调用,参数snum就是我们要bind的端口。

    该方法,类型struct inet_bind_bucket代表端口bind具体信息,比如:哪个?socket在bind这个端口。

    hinfo->bhash是用于存放struct inet_bind_bucket实例的hashmap。

    该方法先从hinfo->bhash这个hashmap中搜,这个端口已经被发现了吗?bind如果没有,创造一个新的tb,比如我们第一次listen该端口在操作过程中没有使用,因此将创建新的端口tb。

    新创建的tb,它的tb->owners是empty,此时,如果我们设置它,SO_REUSEPORT参数,那sk->sk_reuseport字段值大于0,也就是说,第一次listen操作之后,tb->fastreuseport设值设置为FASTREUSEPORT_ANY(大于0)。

    第二次做的时候listen操作时,此时将进入此方法hinfo->bhash的map有相同的端口tb,所以会goto到tb_found部分。

    因为之前的listen操作对应socket放入到tb->owners所以第二次listen操作,tb->owners不为empty。

    然后,逻辑处理将进入sk_reuseport_match如果该方法返回,则该方法true,内核允许第二次listen使用本地址进行操作。

    我们看下sk_reuseport_match方法:

  • //net/ipv4/inet_connection_sock.c
  • staticinlineintsk_reuseport_match(structinet_bind_bucket*tb,
  • structsock*sk)
  • {
  • ...
  • if(tb->fastreuseport<=0)
  • return0;
  • if(!sk->sk_reuseport)
  • return0;
  • ...
  • if(tb->fastreuseport==FASTREUSEPORT_ANY)
  • return1;
  • ...
  • }
  • 由于上一次listen操作,tb->fastreuseport被设置为FASTREUSEPORT_ANY,而此次listen操作的socket,又设置了SO_REUSEPORT参数,即sk->sk_reuseport值大于0,因此该方法最终返回true。

    由上可见,设置SO_REUSEPORT参数结束后,第二次listen中的bind操作没用。我们来看看相应的。listen操作:

  • //net/core/sock_reuseport.c
  • intreuseport_add_sock(structsock*sk,structsock*sk2,boolbind_inany)
  • {
  • structsock_reuseport*old_reuse,*reuse;
  • ...
  • reuse=rcu_dereference_protected(sk2->sk_reuseport_cb,
  • lockdep_is_held(&reuseport_lock));
  • ...
  • reuse->socks[reuse->num_socks]=sk;
  • ...
  • reuse->num_socks ;
  • rcu_assign_pointer(sk->sk_reuseport_cb,reuse);
  • ...
  • }
  • EXPORT_SYMBOL(reuseport_add_sock);
  • listen方法最终会调用上面的方法,在该方法中,sk代表第二次listen操作的socket,sk2代表第一次listen操作的socket。

    该方法的一般逻辑如下:

    1. 将sk2->sk_reuseport_cb给予字段值赋值reuse。

    2. 将sk放入到reuse->socks在代表字段的数组中。

    3. 将sk的sk_reuseport_cb字段也指向这个数组。

    也就是说,这种方法将是所有的第二次及以后listen操作的socket放入到reuse->socks代表字段的数组(第一次listen操作的socket在创建struct sock_reuseport同时,所有的例子都被放入了数组中),同时,listen的socket的sk->sk_reuseport_cb所有的字段reuse,这样,我们就可以通过listen的socket的sk_reuseport_cb字段,拿到struct sock_reuseport例子,然后你可以得到所有其他的东西listen同一端口的socket。

    到目前为止,reuseport如何实现基本清晰,当有新的时候tcp当连接到来时,只要我们找到一个来监控端口listen的socket,就等于拿到了所有设置了SO_REUSEPORT参数,监控同一端口的其他参数socket,我们只需要随机选择一个socket,然后让它完成tcp这样我们就可以实现连接建立过程tcp连接均匀负载到这些listen socket上了。

    看看相应的代码:

  • //net/core/sock_reuseport.c
  • structsock*reuseport_select_sock(structsock*sk,
  • u32hash,
  • structsk_buff*skb,
  • inthdr_len)
  • {
  • structsock_reuseport*reuse;
  • ...
  • structsock*sk2=NULL;
  • u16socks;
  • ...
  • reuse=rcu_dereference(sk->sk_reuseport_cb);
  • ...
  • socks=READ_ONCE(reuse->num_socks);
  • if(likely(socks)){
  • ...
  • if(!sk2)
  • sk2=reuse->socks[reciprocal_scale(hash,socks)];
  • }
  • ...
  • returnsk2;
  • }
  • EXPORT_SYMBOL(reuseport_select_sock);
  • 看,这个方法终于用了reciprocal_scale方法,计算被选中的listen socket索引,最后回到这个listen socket继续处理tcp连接请求。

    看下reciprocal_scale方法是如何实现的:

  • //include/linux/kernel.h
  • /**
  • *reciprocal_scale-"scale"avalueintorange[0,ep_ro)
  • *...
  • */
  • staticinlineu32reciprocal_scale(u32val,u32ep_ro)
  • {
  • return(u32)(((u64)val*ep_ro)>>32);
  • }
  • 虽然我们看不懂算法,但通过它的注释,我们可以知道它返回值的范围是[0, ep_ro),结合以上reuseport_select_sock我们可以确定方法,所有的返回都是listen socket数组下标索引。

    至此,有关SO_REUSEPORT我们完成了参数的内容。

    上篇文章 socket的SO_REUSEADDR 中参数综合分析,我们分析SO_REUSEADDR参数,这个参数和SO_REUSEADDR有什么区别?

    SO_REUSEPORT参数是SO_REUSEADDR这两个参数的目的是重复使用本地地址,但是SO_REUSEADDR不允许处于listen重复使用状态地址,SO_REUSEPORT允许,同时,SO_REUSEPORT参数还会把新来的tcp连接负载载均衡listen socket上,为我们tcp服务器编程提供了一种新的模式。

    其实这个参数是我上次写的。socks5代理那个项目是有用的(是的,我又用了rust实现了一版socks5代理),通过使用该参数,我可以开多个进程同时处理socks5代理请求,现在使用的感觉是,真的很快,使用Google一点问题都没有。

    好吧,就到这里吧。

    本文转载自微信公众号「卯时卯刻」,可通过以下二维码关注。转载本文请联系毛时毛时刻微信官方账号。

       
    • 评论列表:
    •  只酷城鱼
       发布于 2022-06-04 07:33:13  回复该评论
    • 来看看相应的。linux内核代码,看看它是如何实现的。//net/ipv4/inet_connection_sock.cintinet_csk_get_port(st
    •  鸽吻又怨
       发布于 2022-06-04 06:25:45  回复该评论
    • erver.c&&./a.out接收到tcp连接:4接收到tcp连接:4可以看到,tcp连接基本上均匀分布在两个服务器上,神奇。下面我们来看看相应的。linux内核代码,看看它是如何实现的。//net/ipv4/inet_connection_sock.cintine

    发表评论:

    Powered By

    Copyright Your WebSite.Some Rights Reserved.