linux网络编程之绑定端口注意事项及端口复用.docx
- 文档编号:7846840
- 上传时间:2023-01-26
- 格式:DOCX
- 页数:15
- 大小:328.61KB
linux网络编程之绑定端口注意事项及端口复用.docx
《linux网络编程之绑定端口注意事项及端口复用.docx》由会员分享,可在线阅读,更多相关《linux网络编程之绑定端口注意事项及端口复用.docx(15页珍藏版)》请在冰豆网上搜索。
linux网络编程之绑定端口注意事项及端口复用
Linux网络编程之绑定端口注意事项及端口复用
所谓绑定(bind)是指别人连接我只能通过我所绑定的端口,相当于,我买了一个手机,别人要想联系我,必须要知道我的手机号码,这时候,我需要怎么办呢?
我需要给手机插上电话卡,固定一个电话号码,这样别人就能通过这个电话号码联系我。
手机插上电话卡,固定一个电话号码,类似于绑定(bind)的过程,绑定(bind)为了固定一个端口号,别的网络程序就可以找到这个端口号,找到这个端口号就能找到这个端口号所对应的网络应用程序。
在网络编程里,通常都是在服务器里绑定(bind)端口,这并不是说客户端里不能绑定(bind)端口,但这里需要注意的是,一个网络应用程序只能绑定一个端口(一个套接字只能绑定一个端口)。
一个套接字不能同时绑定多个端口,如下:
#include
#include
#include
#include
#include
#include
#include
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
#include
#include
#include
#include
#include
#include
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
#include
#include
#include
#include
#include
#include
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);
// 绑定,端口为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地址是不能相同的。
在有多块网卡或用IPAlias技术的机器可以测试这种情况。
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));
// 在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
#include
#include
#include
#include
#include
#include
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
#include
#include
#include
#include
#include
#include
#include
// 线程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){
XX文库-让每个人平等地提升自我 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)
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- linux 网络 编程 绑定 端口 注意事项
![提示](https://static.bdocx.com/images/bang_tan.gif)