Linux网络编程之绑定端口注意事项及端口复用

时间:2024.5.9

Linux网络编程之绑定端口注意事项及端口复用

所谓绑定(bind)是指别人连接我只能通过我所绑定的端口,相当于,我买了一个手机,别人要想联系我,必须要知道我的手机号码,这时候,我需要怎么办呢?我需要给手机插上电话卡,固定一个电话号码,这样别人就能通过这个电话号码联系我。手机插上电话卡,固定一个电话号码,类似于绑定(bind)的过程,绑定(bind)为了固定一个端口号,别的网络程序就可以找到这个端口号,找到这个端口号就能找到这个端口号所对应的网络应用程序。 在网络编程里,通常都是在服务器里绑定(bind)端口,这并不是说客户端里不能绑定(bind)端口,但这里需要注意的是,一个网络应用程序只能绑定一个端口( 一个套接字只能 绑定一个端口 )。

一个套接字不能同时绑定多个端口,如下:

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <unistd.h>

#include <sys/socket.h>

#include <netinet/in.h>

#include <arpa/inet.h>

int main(int argc, charchar *argv[])

{

char server_ip[30] = "10.221.20.12";

int sockfd;

sockfd = socket(AF_INET, SOCK_DGRAM, 0); //创建UDP套接字

if(sockfd < 0)

{

perror("socket");

exit(-1);

}

// 初始化本地网络信息

struct sockaddr_in my_addr;

bzero(&my_addr, sizeof(my_addr));

my_addr.sin_family = AF_INET;

my_addr.sin_port = htons(8000);

my_addr.sin_addr.s_addr = htonl(INADDR_ANY);

// 第一次绑定端口8000

int err_log;

err_log = bind(sockfd, (struct sockaddr*)&my_addr, sizeof(my_addr));

if(err_log != 0)

{

perror("bind 8000");

close(sockfd);

exit(-1);

}

// 又一次绑定别的端口9000, 会绑定失败

my_addr.sin_port = htons(9000);

err_log = bind(sockfd, (struct sockaddr*)&my_addr, sizeof(my_addr));

if(err_log != 0)

{

perror("bind 9000");

close(sockfd);

exit(-1);

}

close(sockfd);

return 0;

}

程序编译运行后结果如下:

如果客户端想绑定端口号,一定要调用发送信息函数之前绑定( bind )端口,因为在发送信息函数( sendto, 或 write ),系统会自动给当前网络程序分配一个随机端口号,这相当于随机绑定了一个端口号,这里只会分配一次,以后通信就以这个随机端口通信,我们再绑定端口号的话,就会绑定失败。如果我们放在发送信息函数( sendto, 或 write )之前绑定,那样程序将以我们绑定的端口号发送信息,不会再随机分配一个端口号。

绑定失败例子( UDP )如下:

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <unistd.h>

#include <sys/socket.h>

#include <netinet/in.h>

#include <arpa/inet.h>

Linux网络编程之绑定端口注意事项及端口复用

int main(int argc, charchar *argv[])

{

char server_ip[30] = "10.221.20.12";

int sockfd;

sockfd = socket(AF_INET, SOCK_DGRAM, 0); //创建UDP套接字

if(sockfd < 0)

{

perror("socket");

exit(-1);

}

struct sockaddr_in dest_addr;

bzero(&dest_addr, sizeof(dest_addr));

dest_addr.sin_family = AF_INET;

dest_addr.sin_port = htons(8080); // 服务器的端口 inet_pton(AF_INET, server_ip, &dest_addr.sin_addr);

char send_buf[512] = "this is for test";

// 如果前面没有绑定端口,sendto()系统会随机分配一个端口

sendto(sockfd, send_buf, strlen(send_buf), 0, (struct sockaddr*)&dest_addr, sizeof(dest_addr));//发送数据

// 初始化本地网络信息

struct sockaddr_in my_addr;

bzero(&my_addr, sizeof(my_addr));

my_addr.sin_family = AF_INET;

my_addr.sin_port = htons(8000);

my_addr.sin_addr.s_addr = htonl(INADDR_ANY);

// sendto()后面绑定端口,绑定失败

int err_log;

err_log = bind(sockfd, (struct sockaddr*)&my_addr, sizeof(my_addr));

if(err_log != 0)

{

perror("bind 8000");

close(sockfd);

exit(-1);

}

close(sockfd);

return 0;

}

程序编译运行后结果如下:

在上面提到:一个网络应用程序只能绑定一个端口( 一个套接字只能绑定一个端口 )。 实际上,默认的情况下,如果一个网络应用程序的一个套接字 绑定了一个端口( 占用了 8000 ),这时候,别的套接字就无法使用这个端口( 8000 ), 验证例子如下:

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <unistd.h>

#include <sys/socket.h>

#include <netinet/in.h>

#include <arpa/inet.h>

int main(int argc, charchar *argv[])

Linux网络编程之绑定端口注意事项及端口复用

{

int sockfd_one;

int err_log;

sockfd_one = socket(AF_INET, SOCK_DGRAM, 0); //创建UDP套接字one

if(sockfd_one < 0)

{

perror("sockfd_one");

exit(-1);

}

// 设置本地网络信息

struct sockaddr_in my_addr;

bzero(&my_addr, sizeof(my_addr));

my_addr.sin_family = AF_INET;

my_addr.sin_port = htons(8000); // 端口为8000

my_addr.sin_addr.s_addr = htonl(INADDR_ANY);

// 绑定,端口为8000

err_log = bind(sockfd_one, (struct sockaddr*)&my_addr, sizeof(my_addr));

if(err_log != 0)

{

perror("bind sockfd_one");

close(sockfd_one);

exit(-1);

}

int sockfd_two;

sockfd_two = socket(AF_INET, SOCK_DGRAM, 0); //创建UDP套接字two

if(sockfd_two < 0)

{

perror("sockfd_two");

exit(-1);

}

// 新套接字sockfd_two,继续绑定8000端口,绑定失败

// 因为8000端口已被占用,默认情况下,端口没有释放,无法绑定

err_log = bind(sockfd_two, (struct sockaddr*)&my_addr, sizeof(my_addr));

if(err_log != 0)

{

perror("bind sockfd_two");

close(sockfd_two);

exit(-1);

}

close(sockfd_one);

close(sockfd_two);

return 0;

}

程序编译运行后结果如下:

那如何让sockfd_one, sockfd_two两个套接字都能成功绑定8000端口呢?这时候就需要要到端口复用了。端口复用允许在一个应用程序可以把 n 个套接字绑在一个端口上而不出错。 设置socket的SO_REUSEADDR选项,即可实现端口复用:

int opt = 1;

// sockfd为需要端口复用的套接字

setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (const voidvoid *)&opt, sizeof(opt));

SO_REUSEADDR可以用在以下四种情况下。 (摘自《Unix网络编程》卷一,即UNPv1)

1、当有一个有相同本地地址和端口的socket1处于TIME_WAIT状态时,而你启动的程序的socket2要占用该地址和端口,你的程序就要用到该选项。

2、SO_REUSEADDR允许同一port上启动同一服务器的多个实例(多个进程)。但每个实例绑定的IP地址是不能相同的。在有多块网卡或用IP Alias技术的机器可以测试这种情况。

3、SO_REUSEADDR允许单个进程绑定相同的端口到多个socket上,但每个socket绑定的ip地址不同。这和2很相似,区别请看UNPv1。

4、SO_REUSEADDR允许完全相同的地址和端口的重复绑定。但这只用于UDP的多播,不用于TCP。

需要注意的是,设置端口复用函数要在绑定之前调用,而且只要绑定到同一个端口的所有套接字都得设置复用:

// sockfd_one, sockfd_two都要设置端口复用

// 在sockfd_one绑定bind之前,设置其端口复用

int opt = 1;

setsockopt( sockfd_one, SOL_SOCKET,SO_REUSEADDR, (const voidvoid *)&opt, sizeof(opt) );

err_log = bind(sockfd_one, (struct sockaddr*)&my_addr, sizeof(my_addr));

Linux网络编程之绑定端口注意事项及端口复用

// 在sockfd_two绑定bind之前,设置其端口复用

opt = 1;

setsockopt( sockfd_two, SOL_SOCKET,SO_REUSEADDR,(const voidvoid *)&opt, sizeof(opt) );

err_log = bind(sockfd_two, (struct sockaddr*)&my_addr, sizeof(my_addr));

端口复用完整代码如下:

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <unistd.h>

#include <sys/socket.h>

#include <netinet/in.h>

#include <arpa/inet.h>

int main(int argc, charchar *argv[])

{

int sockfd_one;

int err_log;

sockfd_one = socket(AF_INET, SOCK_DGRAM, 0); //创建UDP套接字one

if(sockfd_one < 0)

{

perror("sockfd_one");

exit(-1);

}

// 设置本地网络信息

struct sockaddr_in my_addr;

bzero(&my_addr, sizeof(my_addr));

my_addr.sin_family = AF_INET;

my_addr.sin_port = htons(8000); // 端口为8000

my_addr.sin_addr.s_addr = htonl(INADDR_ANY);

// 在sockfd_one绑定bind之前,设置其端口复用

int opt = 1;

setsockopt( sockfd_one, SOL_SOCKET,SO_REUSEADDR,

(const voidvoid *)&opt, sizeof(opt) );

// 绑定,端口为8000

err_log = bind(sockfd_one, (struct sockaddr*)&my_addr, sizeof(my_addr));

if(err_log != 0)

{

perror("bind sockfd_one");

close(sockfd_one);

exit(-1);

}

int sockfd_two;

sockfd_two = socket(AF_INET, SOCK_DGRAM, 0); //创建UDP套接字two

if(sockfd_two < 0)

{

perror("sockfd_two");

exit(-1);

}

// 在sockfd_two绑定bind之前,设置其端口复用

opt = 1;

setsockopt( sockfd_two, SOL_SOCKET,SO_REUSEADDR,

(const voidvoid *)&opt, sizeof(opt) );

// 新套接字sockfd_two,继续绑定8000端口,成功

err_log = bind(sockfd_two, (struct sockaddr*)&my_addr, sizeof(my_addr));

if(err_log != 0)

{

perror("bind sockfd_two");

close(sockfd_two);

exit(-1);

}

close(sockfd_one);

close(sockfd_two);

return 0;

}

端口复用允许在一个应用程序可以把 n 个套接字绑在一个端口上而不出错。同时,这 n 个套接字发送信息都正常,没有问题。但是,这些套接字并不是所有都能读取信息,只有最后一个套接字会正常接收数据。

下面,我们在之前的代码上,添加两个线程,分别负责接收sockfd_one,sockfd_two的信息: #include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <unistd.h>

#include <sys/socket.h>

#include <netinet/in.h>

#include <arpa/inet.h>

#include <pthread.h>

// 线程1的回调函数

voidvoid *recv_one(voidvoid *arg)

{

printf("===========recv_one==============\n");

int sockfd = (int )arg;

while(1){

int recv_len;

char recv_buf[512] = "";

struct sockaddr_in client_addr;

char cli_ip[INET_ADDRSTRLEN] = "";//INET_ADDRSTRLEN=16 socklen_t cliaddr_len = sizeof(client_addr);

recv_len = recvfrom(sockfd, recv_buf, sizeof(recv_buf), 0, (struct sockaddr*)&client_addr, &cliaddr_len);

inet_ntop(AF_INET, &client_addr.sin_addr, cli_ip, INET_ADDRSTRLEN);

printf("\nip:%s ,port:%d\n",cli_ip, ntohs(client_addr.sin_port));

printf("sockfd_one =========== data(%d):%s\n",recv_len,recv_buf);

}

return NULL;

}

// 线程2的回调函数

voidvoid *recv_two(voidvoid *arg)

{

printf("+++++++++recv_two++++++++++++++\n");

int sockfd = (int )arg;

while(1){

int recv_len;

char recv_buf[512] = "";

struct sockaddr_in client_addr;

char cli_ip[INET_ADDRSTRLEN] = "";//INET_ADDRSTRLEN=16 socklen_t cliaddr_len = sizeof(client_addr);

recv_len = recvfrom(sockfd, recv_buf, sizeof(recv_buf), 0, (struct sockaddr*)&client_addr, &cliaddr_len);

inet_ntop(AF_INET, &client_addr.sin_addr, cli_ip, INET_AD

DRSTRLEN);

printf("\nip:%s ,port:%d\n",cli_ip, ntohs(client_addr.sin_port));

printf("sockfd_two @@@@@@@@@@@@@@@ data(%d):%s\n",recv_len,recv_buf);

}

return NULL;

}

int main(int argc, charchar *argv[])

{

int err_log;

/////////////////////////sockfd_one

int sockfd_one;

sockfd_one = socket(AF_INET, SOCK_DGRAM, 0); //创建UDP套接字one

if(sockfd_one < 0)

{

perror("sockfd_one");

exit(-1);

}

// 设置本地网络信息

struct sockaddr_in my_addr;

bzero(&my_addr, sizeof(my_addr));

my_addr.sin_family = AF_INET;

my_addr.sin_port = htons(8000); // 端口为8000

my_addr.sin_addr.s_addr = htonl(INADDR_ANY);

// 在sockfd_one绑定bind之前,设置其端口复用

int opt = 1;

setsockopt( sockfd_one, SOL_SOCKET,SO_REUSEADDR,

(const voidvoid *)&opt, sizeof(opt) );

// 绑定,端口为8000

err_log = bind(sockfd_one, (struct sockaddr*)&my_addr, sizeof(my_addr));

if(err_log != 0)

{

perror("bind sockfd_one");

close(sockfd_one);

exit(-1);

}

//接收信息线程1

pthread_t tid_one;

pthread_create(&tid_one, NULL, recv_one, (voidvoid *)sockfd_one);

/////////////////////////sockfd_two

int sockfd_two;

sockfd_two = socket(AF_INET, SOCK_DGRAM, 0); //创建UDP套接字two

if(sockfd_two < 0)

{

perror("sockfd_two");

exit(-1);

}

// 在sockfd_two绑定bind之前,设置其端口复用

opt = 1;

setsockopt( sockfd_two, SOL_SOCKET,SO_REUSEADDR,

(const voidvoid *)&opt, sizeof(opt) );

// 新套接字sockfd_two,继续绑定8000端口,成功

err_log = bind(sockfd_two, (struct sockaddr*)&my_addr, sizeof(my_addr));

if(err_log != 0)

{

perror("bind sockfd_two");

close(sockfd_two);

exit(-1);

}

//接收信息线程2

pthread_t tid_two;

pthread_create(&tid_two, NULL, recv_two, (voidvoid *)sockfd_two);

while(1){ // 让程序阻塞在这,不结束

NULL;

}

close(sockfd_one);

close(sockfd_two);

return 0;

}

接着,通过网络调试助手给这个服务器发送数据,结果显示,只有最后一个套接字sockfd_two会正常接收数据:

我们上面的用法,实际上没有太大的意义。端口复用最常用的用途应该是防止服务器重启时之前绑定的端口还未释放或者程序突然退出而系统没有释放端口。这种情况下如果设定了端口复用,则新启动的服务器进程可以直接绑定端口。如果没有设定端口复用,绑定会失败,提示ADDR已经在使用中——那只好等等再重试了,麻烦!

Linux网络编程之绑定端口注意事项及端口复用

更多相关推荐:
linux打开和关闭端口

linux打开和关闭端口20xx0302145359我来说两句收藏我要投稿查看哪些端口被打开netstatanp关闭端口号iptablesAINPUTptcpdrop端口号jDROPiptablesAOUTPU...

linux开启防火墙端口和查看,开启相关端口号

当Linux打开防火墙后你会发现从本机登录23端口是没有问题的但是如果从另一台pc登录该linux系统后你会发现提示这样的错误不能打开到主机的连接在端口23连接失败因为linux防火墙默认是关闭23端口的如果允...

linux修改SSH端口号

第一种1假如要改SSH的默认端口22那么你只要修改vietcsshsshdconfig中Port22这里把22改成自己要设的端口就行了不过千万别设和现已有的端口相同哦以防造成未知后果2假如要限制SSH登陆的IP...

linux查看端口的方法

想查看TCP或者UDP端口使用情况使用netstatanp如果有些进程看不见如只显示可以尝试sudonetstatanp如果想看某个端口的信息使用lsof命令如sudolsofi631bash300netsta...

linux端口修改

SUSELinux立即关闭防火墙的命令和指定开放某些端口SUSE防火墙查看命令linuxchkconfiglistgrepifireSuSEfirewall2init0off1off2off3off4off5o...

如何更改linux下的Apache端口号

如何更改linux下的Apache端口号一修改etchttpdconfhttpdconf文件中的监听端口号Listen80把80修改成需要的号如8000即Listen8000二查看SELinux下http相关端...

如何更改linux下的Apache端口号

如何更改linux下的Apache端口号一修改etchttpdconfhttpdconf文件中的监听端口号Listen80把80修改成需要的号如8000即Listen8000二查看SELinux下http相关端...

Linux配置端口转发

编辑etcsysctlconf文件开启linux的数据转发功能sysctlp这条命令是使数据转发功能生效vietcsysctlconf将netipv4ipforward0更改为netipv4ipforward1...

Linux上使用netstat查看Mysql端口和连接

Linux上使用netstat查看Mysql端口和连接linux上使用netstat察看mysql端口和连接近日发现写的一个java程序的数据库连接在大压力下工作不打正常因此研究了一下dbcp中间为了查看mys...

查看linux中某个端口(port)是否被占用(netstat,lsof)

netstattunlp会显示所有端口和所有对应的程序用grep管道可以过滤出想要的关键字段列一下22端口占用的程序rootleiwantmpnetstattunlpgrep22tcp0000004295700...

linux netstat 统计连接数查看外部 如何查80端口tcp链接数

linuxnetstat统计连接数查看外部如何查80端口tcp链接数时间20xx03261914来源未知作者admin点击2530次服务器上的一些统计数据1统计80端口连接数netstatnatgrepiquo...

Linux服务器必须严防死守的一个端口

Linux服务器必须严防死守的一个端口SSH是每一台Linux服务器的标准配置无论你的服务器上部署了什么服务你都必须通过SSH远程管理你的服务器这是除去其他应用开放的端口以外必须开放的一个端口SSH端口是潜在的...

linux端口号(26篇)