使用setsockopt实现端口复用
Posted milaiko
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用setsockopt实现端口复用相关的知识,希望对你有一定的参考价值。
端口复用
使用场景
我们知道主动关闭连接方,最终会进入一个状态——timewait, 而当服务器主动关闭的时候,它会进入这个状态并等待2MSL的时长。
假设一个场景,当服务器需要更新时, 需要服务器主动关闭连接,这时候服务器就需要等待2msl才能使用这个端口,对于用户而言,2msl的时长太长,(linux的2msl时长大约有40s到60s),所以需要复用这个端口,使得服务端能够正常运行。
函数简介
#inlude<sys/socket.h>
int getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen);
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t *optlen);
-
level(级别)指定系统中解释选项的代码为通用套接字代码,或为某个特定协议的代码。
-
optval是一个指向某个变量(*optval)的指针,setsockopt从*optval中取得选项待设置的新值,getsockopt则把已获得的选项当前值存放到*optval中。
对于端口复用的实现, 需要设置level,optname
-
level(级别) 选为SOL_SOCKET
-
optname设为SO_REUSEADDR(允许重用本地地址)或者SO_REUSEPORT(允许重用本地端口)
其中opt_val = 1表示启用,为0表示禁用。
实现例子
注意:这个是基于多线程实现的服务器端
server.cpp
#include<unistd.h>
#include<arpa/inet.h>
#include<sys/socket.h>
#include<fcntl.h>
#include<ctype.h>
#include<pthread.h>
#include<string.h>
#include<stdio.h>
#include"wrap.h"
const int MAXVALUE = 8192;
const int SERV_PROT = 8888;
//定义一个结构体将客户端地址和套接字绑定起来
struct s_info{
struct sockaddr_in cliaddr;
int connfd;
};
void* do_work(void *arg){ //子线程
int n,i;
struct s_info *ts = (struct s_info*)arg;
char buf[MAXVALUE];
char str[INET_ADDRSTRLEN]; //define Inet_addrstrlen 16
while(1){
n = Read(ts->connfd, buf, MAXVALUE);
if(n == 0){
printf("the client %d closed...\\n", ts->connfd);
break;
}
printf("recieved from %s at port %d\\n",
inet_ntop(AF_INET, &(*ts).cliaddr.sin_addr, str, sizeof(str)),
ntohs((*ts).cliaddr.sin_port)); //note inet_ntop 网络字节序转换为ip地址,返回的是str的地址,
for(i = 0;i<n;i++){
buf[i] = toupper(buf[i]);
}
Write(STDOUT_FILENO, buf, n);
Write(ts->connfd, buf, n);
}
Close(ts->connfd);
return (void*)0;
//pthread_exit(0);
}
int main(){
struct sockaddr_in seraddr, cliaddr;
socklen_t cliaddr_len;
int listenfd, connfd;
pthread_t tid;
struct s_info ts[256];
int i=0;
//创建套接字
listenfd = Socket(AF_INET, SOCK_STREAM, 0);
//设置端口复用
int opt = 1;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, (void*)&opt, sizeof(opt));
//设置服务器地址结构体
bzero(&seraddr, sizeof(seraddr));
seraddr.sin_family = AF_INET;
seraddr.sin_addr.s_addr = htonl(INADDR_ANY);
seraddr.sin_port = htons(SERV_PROT);
//绑定套接字
Bind(listenfd, (struct sockaddr*)&seraddr, sizeof(seraddr));
//监听套接字
Listen(listenfd, 128);
printf("Accepting client connect ......");
while(1){
cliaddr_len = sizeof(cliaddr);
connfd = Accept(listenfd, (struct sockaddr*)&cliaddr, &cliaddr_len); //阻塞监听客户端请求
ts[i].cliaddr = cliaddr;
ts[i].connfd = connfd;
//创建子线程
pthread_create(&tid, NULL, do_work, (void*)&ts[i]);
pthread_detach(tid); //子线程分离,防止僵尸线程
i++;
}
return 0;
}
实践
第一步
将当前目录下的server.cpp编译并启动
g++ server.cpp -o server -pthread
./server
第二步
开启另一个终端,并使用nc作为客户端与服务端连接 格式 nc 127.0.0.1 端口号
nc 127.0.0.1 8888
第三步
先将服务端关了,再将客户端关了,模拟服务端变成timewait状态,因为进入了timewait状态需要等待2msl时长才会真正close,而端口复用允许它复用8888这个端口和客户端通信。
为了验证一下,我们用netstat来查看8888这个端口的状态
sudo netstat -apn | grep 8888
output:
tcp 0 0 127.0.0.1:8888 127.0.0.1:47780 TIME_WAIT
第四步
重新开服务器
./server
并在另一个shell查看
sudo netstat -apn| grep 8888
tcp 0 0 0.0.0.0:8888 0.0.0.0:* LISTEN 5346/./mult_pthread
tcp 0 0 127.0.0.1:8888 127.0.0.1:47780 TIME_WAIT -
发现
这里的端口复用并不是将time_wait提前结束了,为了保证tcp的可靠性,time_wait依然存在,只不过是重新利用这个端口进行另一个通信罢了。
发现8888有两个状态 listen 和time_wait, 如果把setsockopt给关掉了,又会怎样?
以上是关于使用setsockopt实现端口复用的主要内容,如果未能解决你的问题,请参考以下文章