做论坛网站能赚钱吗,flash网站建设教程,一家专门做灯的网站,实名网站空间端口复用最常用的用途应该是防止服务器重启时之前绑定的端口还未释放或者程序突然退出而系统没有释放端口。这种情况下如果设定了端口复用#xff0c;则新启动的服务器进程可以直接绑定端口。如果没有设定端口复用#xff0c;绑定会失败#xff0c;提示ADDR已经在使用中——…端口复用最常用的用途应该是防止服务器重启时之前绑定的端口还未释放或者程序突然退出而系统没有释放端口。这种情况下如果设定了端口复用则新启动的服务器进程可以直接绑定端口。如果没有设定端口复用绑定会失败提示ADDR已经在使用中——那只好等等再重试了麻烦
实际上默认的情况下如果一个网络应用程序的一个套接字 绑定了一个端口( 占用了 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.hint main(int argc, char *argv[])
{int sockfd_one;int err_log;sockfd_one socket(AF_INET, SOCK_DGRAM, 0); //创建UDP套接字oneif(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); // 端口为8000my_addr.sin_addr.s_addr htonl(INADDR_ANY);// 绑定端口为8000err_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套接字twoif(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;
}
/*
serDESKTOP-MF4BDTB:/mnt/e/test_code$ g SO_REUSEADDR.cpp -o SO_REUSEADDR.O
userDESKTOP-MF4BDTB:/mnt/e/test_code$ ./SO_REUSEADDR.O
bind sockfd_two: Address already in use
userDESKTOP-MF4BDTB:/mnt/e/test_code$
*/ 程序编译运行后结果如下 那如何让sockfd_one, sockfd_two两个套接字都能成功绑定8000端口呢这时候就需要要到端口复用了。端口复用允许在一个应用程序可以把 n 个套接字绑在一个端口上而不出错。
设置socket的SO_REUSEADDR选项即可实现端口复用
int opt 1;
// sockfd为需要端口复用的套接字
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (const void *)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 void *)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 void *)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.hint main(int argc, char *argv[])
{int sockfd_one;int err_log;sockfd_one socket(AF_INET, SOCK_DGRAM, 0); //创建UDP套接字oneif(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); // 端口为8000my_addr.sin_addr.s_addr htonl(INADDR_ANY);// 在sockfd_one绑定bind之前设置其端口复用int opt 1;setsockopt( sockfd_one, SOL_SOCKET,SO_REUSEADDR, (const void *)opt, sizeof(opt) );// 绑定端口为8000err_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套接字twoif(sockfd_two 0){perror(sockfd_two);exit(-1);}// 在sockfd_two绑定bind之前设置其端口复用opt 1;setsockopt( sockfd_two, SOL_SOCKET,SO_REUSEADDR, (const void *)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_onesockfd_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的回调函数
void *recv_one(void *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_ADDRSTRLEN16socklen_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的回调函数
void *recv_two(void *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_ADDRSTRLEN16socklen_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_two data(%d):%s\n,recv_len,recv_buf);}return NULL;
}int main(int argc, char *argv[])
{int err_log;/sockfd_oneint sockfd_one;sockfd_one socket(AF_INET, SOCK_DGRAM, 0); //创建UDP套接字oneif(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); // 端口为8000my_addr.sin_addr.s_addr htonl(INADDR_ANY);// 在sockfd_one绑定bind之前设置其端口复用int opt 1;setsockopt( sockfd_one, SOL_SOCKET,SO_REUSEADDR, (const void *)opt, sizeof(opt) );// 绑定端口为8000err_log bind(sockfd_one, (struct sockaddr*)my_addr, sizeof(my_addr));if(err_log ! 0){perror(bind sockfd_one);close(sockfd_one); exit(-1);}//接收信息线程1pthread_t tid_one;pthread_create(tid_one, NULL, recv_one, (void *)sockfd_one);/sockfd_twoint sockfd_two;sockfd_two socket(AF_INET, SOCK_DGRAM, 0); //创建UDP套接字twoif(sockfd_two 0){perror(sockfd_two);exit(-1);}// 在sockfd_two绑定bind之前设置其端口复用opt 1;setsockopt( sockfd_two, SOL_SOCKET,SO_REUSEADDR, (const void *)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);}//接收信息线程2pthread_t tid_two;pthread_create(tid_two, NULL, recv_two, (void *)sockfd_two);while(1){ // 让程序阻塞在这不结束NULL;}close(sockfd_one);close(sockfd_two);return 0;
} 接着通过网络调试助手给这个服务器发送数据结果显示只有最后一个套接字sockfd_two会正常接收数据 我们上面的用法实际上没有太大的意义。端口复用最常用的用途应该是防止服务器重启时之前绑定的端口还未释放或者程序突然退出而系统没有释放端口。这种情况下如果设定了端口复用则新启动的服务器进程可以直接绑定端口。如果没有设定端口复用绑定会失败提示ADDR已经在使用中——那只好等等再重试了麻烦