本例中端口复用可以让服务器中多个socket共同使用同一个端口号。
问题描述:在tcp连接下,如果服务器主动关闭连接(比如ctrl+c结束服务器进程),那么由于服务器这边会出现time_wait状态,所以不能立即重新启动服务器进程。
解决这个问题就可以用端口复用,让多个socket可以同时绑定在一个ip和端口上,这样,就算是上一个socket处于time_wait状态占用着该端口,也不会影响其它socket使用该端口。 在使用端口复用的时候,服务器这边只需要在每次对socket绑定(bind)的时候对这个socket的选项进行设置就可以了。这样就可以让这个socket可以在该端口被占用的情况下依然能够使用该端口。 设置socket选项的函数为: setsockopt(int sockfd,int level,int optname,const void* optval,socklen_t optlen);
下面检测一下使用端口复用的效果:
NO1、不使用端口复用,代码如下:
服务器:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main()
{
int sockfd = socket(AF_INET,SOCK_STREAM,0);
assert(sockfd != -1);
struct sockaddr_in saddr,caddr;
memset(&saddr,0,sizeof(saddr));
saddr.sin_family = AF_INET;
saddr.sin_port = htons(6000);
saddr.sin_addr.s_addr = inet_addr("127.0.0.1");
//不使用端口复用,屏蔽起来
/*
int opt = 1;
setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,(const void*)&opt,sizeof(opt));
*/
int res = bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
assert(res != -1);
listen(sockfd,5);
while(1)
{
int len = sizeof(caddr);
int c = accept(sockfd,(struct sockaddr*)&caddr,&len);
if( c < 0)
{
continue;
}
printf("accept c = %d\n",c);
while(1)
{
char buff[128] = {0};
recv(c,buff,127,0);//read
printf("buff=%s\n",buff);
send(c,"OK",2,0);
if(strncmp(buff,"end",3)==0)
{
break;
}
}
close(c);
}
}
客户端:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main()
{
int sockfd = socket(AF_INET,SOCK_STREAM,0);
assert(sockfd != -1);
struct sockaddr_in saddr,caddr;
memset(&saddr,0,sizeof(saddr));
saddr.sin_family = AF_INET;
saddr.sin_port = htons(6000);
saddr.sin_addr.s_addr = inet_addr("127.0.0.1");
int res = connect(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
assert(res != -1);
while(1)
{
printf("input:\n");
char buff[128]={0};
fgets(buff,128,stdin);
char s[128];
strcpy(s,buff);
buff[strlen(buff)-1]=0;
send(sockfd,buff,strlen(buff),0);//write
memset(buff,0,128);
recv(sockfd,buff,127,0);//read
if(strncmp(s,"end",3)==0)
{
break;
}
printf("buff=%s\n",buff);
}
close(sockfd);
}
上述是不使用端口复用的代码,看看运行结果
第一次运行起来进行正常的通信。然后先关闭服务器再关闭客户端,再立即启动服务器,出现下图情况:
通过netstat命令查看发现该端口处于time_wait状态,所以服务器不能立即重启:
NO2、上述是没有使用端口复用的服务器,再看看使用端口复用的服务器:
客户端代码和上次的一样不用修改,服务器的代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main()
{
int sockfd = socket(AF_INET,SOCK_STREAM,0);
assert(sockfd != -1);
struct sockaddr_in saddr,caddr;
memset(&saddr,0,sizeof(saddr));
saddr.sin_family = AF_INET;
saddr.sin_port = htons(6000);
saddr.sin_addr.s_addr = inet_addr("127.0.0.1");
//使用端口复用,在bind之前先设置好socket的选线,让其可以绑定在已经正在被别的socket使用的端口上。
int opt = 1;
setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,(const void*)&opt,sizeof(opt));
int res = bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
assert(res != -1);
listen(sockfd,5);
while(1)
{
int len = sizeof(caddr);
int c = accept(sockfd,(struct sockaddr*)&caddr,&len);
if( c < 0)
{
continue;
}
printf("accept c = %d\n",c);
while(1)
{
char buff[128] = {0};
recv(c,buff,127,0);//read
printf("buff=%s\n",buff);
send(c,"OK",2,0);
if(strncmp(buff,"end",3)==0)
{
break;
}
}
close(c);
}
}
使用刚才同样的方法进行测试,结果如下:
可见,在服务器主动关闭连接后,我还是可以立即重新启动服务器的,而不受time_wait的影响。但是通过netstat命令查看这个端口上还是有time_wait的出现:
这时的time_wait是上次连接中与该地址和端口绑定的socket出现了time_wait状态,但是采用了端口复用后,可以多个socket和这个端口进行绑定,所以新的一次的连接使用的是新创建的socket和该端口进行绑定,并不会受到上一个socket处于time_wait的影响。
虽然可以立即启动服务器,但是对于高并发模式下的服务器在短时间内也是不能使用已经处于time_wait状态的socket的,要解决这样的问题就要用其它的方法(比如通过设置内核参数避免出现time_wait状态)。 上述中服务器能够立即重启是因为使用了新的socket和端口进行了绑定,而上次已经断开连接的socket还是在处于time_wait中的,这个socket在短时间内是不能再非配出去继续使用的。