【tcp】关于用本地同一个ip:port跟不同服务端建立tcp连接

我们看一个案例

# echo  > /dev/tcp/www.example.com/8090

# ss  -tan | grep TIME-WAIT
TIME-WAIT  0      0      172.22.54.177:58044              60.29.63.124:8090


# curl  -sSI  --local-port   58044   https://www.baidu.com
curl: (45) bind failed with errno 98: Address already in use

这段代码展示了一系列命令和输出。解读如下:

  1. 第一个命令 echo > /dev/tcp/www.example.com/8090 是一个简单的方法来测试网络连接。它尝试在指定的主机和端口上打开一个TCP连接。如果连接成功,命令会立即退出,否则会报错。

  2. 第二个命令 ss -tan | grep TIME-WAIT 用于列出当前系统中处于TIME-WAIT状态的TCP连接。TIME-WAIT状态是指在TCP连接关闭后,等待一段时间以确保所有相关数据都被正确处理的状态。

  3. 输出结果显示了一个处于TIME-WAIT状态的TCP连接,其中本地IP地址是172.22.54.177,本地端口是58044,远程IP地址是60.29.63.124,远程端口是8090。

  4. 第三个命令 curl -sSI --local-port 58044 https://www.baidu.com 是一个HTTP请求命令,尝试通过指定本地端口(58044)来访问百度网站。然而,由于该端口已经被之前的TCP连接占用,curl命令无法绑定到该端口,因此返回了一个错误信息。

综上所述,这段代码的目的是测试网络连接和查看处于TIME-WAIT状态的TCP连接,同时展示了一个端口被占用的错误情况。

网络编程中的SO_REUSEADDR和SO_REUSEPORT

SO_REUSEADDR

目前为止我见到的设置SO_REUSEADDR的使用场景:

server端在调用bind函数时

setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR,(const void *)&reuse , sizeof(int));

目的:当服务端(注意不是客户端)出现time-wait状态的链接时,确保server能够重启成功。

注意:SO_REUSEADDR只有针对time-wait链接(linux系统time-wait连接持续时间为1min),确保server重启成功的这一个作用,至于网上有文章说:如果有socket绑定了0.0.0.0:port;设置该参数后,其他socket可以绑定本机ip:port。
本人经过试验后均提示“Address already in use”错误,绑定失败。

举个例子: server监听9980端口,由于主动关闭链接,产生了一个time-wait状态的链接

image.png

如果此时server重启,并且server没有设置SO_REUSEADDR参数,server重启失败,报错:“Address already in use”
如果设置SO_REUSEADDR,重启ok。

SO_REUSEPORT

SO_REUSEPORT使用场景:Linux kernel 3.9 引入了最新的SO_REUSEPORT选项,使得多进程或者多线程创建多个绑定同一个ip:port的监听socket,提高服务器的接收链接的并发能力,程序的扩展性更好,此时需要设置SO_REUSEPORT(注意所有进程都要设置才生效)。

setsockopt(listenfd, SOL_SOCKET, SO_REUSEPORT,(const void *)&reuse , sizeof(int));

目的:每一个进程有一个独立的监听socket,并且bind相同的ip:port,独立的listen()和accept();提高接收连接的能力。(例如nginx多进程同时监听同一个ip:port)

解决的问题:

(1)避免了应用层多线程或者进程监听同一ip:port的“惊群效应”。

(2)内核层面实现负载均衡,保证每个进程或者线程接收均衡的连接数。

(3)只有effective-user-id相同的服务器进程才能监听同一ip:port (安全性考虑)

代码示例:server_128.c

1 #include <stdio.h>                                                                                                               
 2 #include <stdlib.h>
 3 #include <string.h>
 4 #include <netinet/in.h>
 5 #include <sys/socket.h>
 6 #include <arpa/inet.h>
 7 #include <sys/types.h>
 8 #include <errno.h>
 9 #include <time.h>
10 #include <unistd.h>
11 #include <sys/wait.h>
12 void work () {
13         int listenfd = socket(AF_INET, SOCK_STREAM, 0);
14         if (listenfd < 0) {
15                 perror("listen socket");
16                 _exit(-1);
17         }
18         int ret = 0;
19         int reuse = 1;
20         ret = setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR,(const void *)&reuse , sizeof(int));
21         if (ret < 0) {
22                 perror("setsockopt");
23                 _exit(-1);
24         }
25         ret = setsockopt(listenfd, SOL_SOCKET, SO_REUSEPORT,(const void *)&reuse , sizeof(int));
26         if (ret < 0) {
27             perror("setsockopt");
28             _exit(-1);
29         }
30         struct sockaddr_in addr;
31         memset(&addr, 0, sizeof(addr));
32         addr.sin_family = AF_INET;
33         //addr.sin_addr.s_addr = inet_addr("10.95.118.221");
34         addr.sin_addr.s_addr = inet_addr("0.0.0.0");                                                                             
35         addr.sin_port = htons(9980);
36         ret = bind(listenfd, (struct sockaddr *)&addr, sizeof(addr));
37         if (ret < 0) {
38                 perror("bind addr");
39                 _exit(-1);
40        }
41         printf("bind success\n");
42         ret = listen(listenfd,10);
43         if (ret < 0) {
44                 perror("listen");
45                 _exit(-1);
46         }
47         printf("listen success\n");
48         struct sockaddr clientaddr;
49         int len = 0;
50         while(1) {
51                 printf("process:%d accept...\n", getpid());
52                 int clientfd = accept(listenfd, (struct sockaddr*)&clientaddr, &len);
53                 if (clientfd < 0) {
54                         printf("accept:%d %s", getpid(),strerror(errno));
55                         _exit(-1);
56                 }
57                 close(clientfd);
58                 printf("process:%d close socket\n", getpid());
59         }
60 }
61 int main(){
62         printf("uid:%d euid:%d\n", getuid(),geteuid());
63         int i = 0;
64         for (i = 0; i< 6; i++) {
65                 pid_t pid = fork();
66                 if (pid == 0) {
67                         work();
68                 }
69                 if(pid < 0) {
70                         perror("fork");
71                         continue;
72                 }
73         }
74         int status,id;
75         while((id=waitpid(-1, &status, 0)) > 0) {
76                 printf("%d exit\n", id);
77         }
78         if(errno == ECHILD) {
79                 printf("all child exit\n");
80         }
81         return 0;
82 }

上述示例程序,启动了6个子进程
每个子进程创建自己的监听socket,bind相同的ip:port;
独立的listen(),accept();
如果每个子进程不设置SO_REUSEADDR选项,会提示“Address already in use”错误。

安全性:是不是只要设置了SO_REUSEPORT选项的服务器程序,就能够监听相同的ip:port 并获取报文呢?
当然不是,当前Linux内核要求后来启动的服务器程序与前面启动的服务器程序的effective-user-id要相同。

image.png

上图中的server_127和server128两个服务器程序都设置了SO_REUSEPORT,监听同一ip:port,用root用户启动两个服务器程序,因为effective-user-id相等,都为0,所以启动没问题。

修改server127的权限:chmod u+s server_127。

image.png

启动完server_128后,在启动server_127,提示错误: Address already in use。

image.png

参考

网络编程中的SO_REUSEADDR和SO_REUSEPORT参数详解
https://blog.51cto.com/u_15076236/4191893

网络编程:SO_REUSEADDR的使用
https://zhuanlan.zhihu.com/p/79999012

深入理解Linux端口重用这一特性
https://mp.weixin.qq.com/s/SYCUMvzktgeGbyAfRdqhmg

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,258评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,335评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,225评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,126评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,140评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,098评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,018评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,857评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,298评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,518评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,678评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,400评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,993评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,638评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,801评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,661评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,558评论 2 352

推荐阅读更多精彩内容