本文是根据《UNIX网络编程》一书中对于选项SO_REUSEADDR的描述而进行的一个“局部”验证。书中给出了该选项适用于的四种场景:
1.该选项允许启动一个监听服务器并捆绑其众所周知端口,即使其以前建立的将该端口用作他们的本地端口的连接仍存在。该场景通常是因为监听服务器派生了一个子线程对某客户建立了连接,而当服务器终止,子线程仍然为现有未关闭连接的客户提供服务,再当服务器重启,出现bind失败的错误,而如果该服务器在socket()和bind()之间设置了SO_REUSEADDR选项,则服务器重启将成功bind该端口。项目中已验证。
2.该选项允许在同一端口上启动同一服务器的多个实例,只要每个实例捆绑一个不同的本地IP地址即可。
3.该选项允许单个进程捆绑同一个端口到多个套接字上,只要每次捆绑指定不同的本地地址即可。
4.该选项允许完全重复的捆绑;当一个IP地址和端口已绑定到某个套接字上时,如果传输协议支持,同样的IP地址和端口还可以捆绑到另一个套接字上。一般来说本特性只支持UDP套接字。
因为目前遇到的问题只是UDP的,因此本文将只针对第四点进行验证。对应可执行程序test_for_server的UDP服务器代码:
#include
#include
#include
#include
#include
#include
#define SERVER_IP "127.0.0.1"
#define SERVER_PORT 1024
typedef int thy_bool_t;
#define thy_true_t 1
#define thy_false_t 0
int main(int argc, char** argv){
struct sockaddr_in skaddr;
struct sockaddr_in addr_inp;
int inplg = 0;
int reslg = 0;
int reslg_o = 0;
char buff[256];
int sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if(sockfd < 0){
printf("ERROR:create socket Failed\n");
return -1;
}
bzero(&skaddr, sizeof(struct sockaddr_in));
skaddr.sin_family = AF_INET;
skaddr.sin_addr.s_addr = inet_addr(SERVER_IP);
//skaddr.sin_addr.s_addr = htonl(INADDR_ANY);
skaddr.sin_port = htons(SERVER_PORT);
thy_bool_t bReuseaddr = thy_true_t;
if(setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (const char*)&bReuseaddr, sizeof(thy_bool_t))){
printf("Setsockopt -reuseaddr- Failed\n");
return -2;
}
if(bind(sockfd, (struct sockaddr*)&skaddr, sizeof(struct sockaddr)) < 0){
printf("Bind ERROR\n");
return -3;
}
while(1){
bzero(buff, sizeof(buff));
bzero(&addr_inp, sizeof(struct sockaddr_in));
if(reslg = recvfrom(sockfd, buff, sizeof(buff), 0, (struct sockaddr*)&addr_inp, &inplg)){
printf("Recvfrom.Cont:\n%s\nlg:%d\n", buff, reslg);
}
if(reslg_o = sendto(sockfd, "I get it", 9, 0, (struct sockaddr*)&addr_inp, inplg)){
printf("Have reply %d bytes\n", reslg_o);
}
}
getchar();
if(close(sockfd)){
printf("Close ERROR\n");
return -5;
}
printf("\n\nBYE~\n\n");
return 0;
}
运行test_for_server,之后运行该可执行程序的副本test_for_server_re,两者都bind成功。下一步,将写客户端向服务器发送数据报。以下是对应可执行程序test_for_conn的UDP客户端代码:
#include
#include
#include
#include
#include
#include
#define LOCAL_IP "127.0.0.1"
#define LOCAL_PORT 64401
#define SERVER_IP "127.0.0.1"
#define SERVER_PORT 1024
int main(int argc, char** argv){
char buff[256];
int reslg = 0;
struct sockaddr_in skaddr;
struct sockaddr_in localaddr;
int sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if(sockfd < 0){
printf("ERROR:create socket Failed\n");
}
bzero(&localaddr, sizeof(struct sockaddr_in));
localaddr.sin_family = AF_INET;
localaddr.sin_addr.s_addr = inet_addr(LOCAL_IP);
localaddr.sin_port = htons(LOCAL_PORT);
bzero(&skaddr, sizeof(struct sockaddr_in));
skaddr.sin_family = AF_INET;
skaddr.sin_addr.s_addr = inet_addr(SERVER_IP);
skaddr.sin_port = htons(SERVER_PORT);
if(bind(sockfd, (struct sockaddr*)&localaddr, sizeof(struct sockaddr)) < 0){
printf("The UDP Client bind local Failed\n");
}
while(1){
if(connect(sockfd, (struct sockaddr*)&skaddr, sizeof(struct sockaddr)) < 0){
printf("Conn ERROR\n");
sleep(1);
continue;
}
printf("Connect Success\n");
break;
}
while(1){
bzero(buff, sizeof(buff));
scanf("%s", buff);
if( (reslg = sendto(sockfd, buff, sizeof(buff), 0, (struct sockaddr*)&skaddr, sizeof(struct sockaddr))) != -1 ){
printf("Have sended %d bytes\n", reslg);
}
if(strcmp(buff, "bye") == 0){
break;
}
}
printf("\n\nBYE~\n\n");
return 0;
}
由客户端向服务器发送消息
第二个启动(最新)的服务器收到消息,第一条服务器没有能获得正确的远端地址,猜测与UDP的无连接属性有关。
此外,是否前后两次的服务器都需要设置SO_REUSEADDR选项呢?修改可执行程序test_for_server的代码,注释掉setsockopt,再次先后执行test_for_server和test_for_server_re。
同样地,注释test_for_server_re的setsockopt,保留另一个程序的setsockopt,得到的是同样的结果。
本文总结:
1.SO_REUSEADDR选项允许同一个主机上同时运行同一个应用程序的多个副本(或描述为需要绑定同样地址和端口的程序);
2.这多个程序均需要设置SO_REUSEADDR选项,否则后续程序将不能bind成功;
3.多个socket同时监听相同地址,客户端将向最新发起的socket发送数据。