1、端口复用用途
(1)防止服务器重启时之前绑定的端口还未释放;
(2)程序突然退出而系统没有释放端口;
说明:TCP 通信后,在四次挥手时,主动发送断开连接方必须处于TIME_WAIT一段时间,目的是确保另一方能够接收到主动发送放最后的ACK,如果没接收到,则会重新发送;主动关闭方重新发送的最终 ACK 并不是因为被动关闭方重传了 ACK(它们并不消耗序列号, 被动关闭方也不会重传),而是因为被动关闭方重传了它的 FIN。事实上,被动关闭方总是 重传 FIN 直到它收到一个最终的 ACK。
端口复用解释:主动断开方,在最后会有一个TIME_WAIT时间,在这个时间段内,主动断开方在等待,并没有及时释放,所以其他设备无法及时的与主动段开放进行再次连接;
2、端口复用 API
#include <sys/types.h> #include <sys/socket.h> int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
说明:如果想使用端口复用,设置的时机是在服务器绑定端口之前;即在绑定端口之前调用 setsockopt 函数;
3、端口复用案例
(1)客户端
#include <stdio.h> #include <arpa/inet.h> #include <stdlib.h> #include <unistd.h> #include <string.h> int main() { // 创建socket int fd = socket(PF_INET, SOCK_STREAM, 0); if(fd == -1) { perror("socket"); return -1; } struct sockaddr_in seraddr; inet_pton(AF_INET, "127.0.0.1", &seraddr.sin_addr.s_addr); seraddr.sin_family = AF_INET; seraddr.sin_port = htons(9999); // 连接服务器 int ret = connect(fd, (struct sockaddr *)&seraddr, sizeof(seraddr)); if(ret == -1){ perror("connect"); return -1; } while(1) { char sendBuf[1024] = {0}; fgets(sendBuf, sizeof(sendBuf), stdin); write(fd, sendBuf, strlen(sendBuf) + 1); // 接收 int len = read(fd, sendBuf, sizeof(sendBuf)); if(len == -1) { perror("read"); return -1; }else if(len > 0) { printf("read buf = %s\n", sendBuf); } else { printf("服务器已经断开连接...\n"); break; } } close(fd); return 0; }
(2)服务端
#include <stdio.h> #include <ctype.h> #include <arpa/inet.h> #include <unistd.h> #include <stdlib.h> #include <string.h> int main(int argc, char *argv[]) { // 创建socket int lfd = socket(PF_INET, SOCK_STREAM, 0); if(lfd == -1) { perror("socket"); return -1; } struct sockaddr_in saddr; saddr.sin_family = AF_INET; saddr.sin_addr.s_addr = INADDR_ANY; saddr.sin_port = htons(9999); //端口复用api,该函数必须在端口连接之前调用才有效 int optval = 1; setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)); int optval = 1; setsockopt(lfd, SOL_SOCKET, SO_REUSEPORT, &optval, sizeof(optval)); // 绑定 int ret = bind(lfd, (struct sockaddr *)&saddr, sizeof(saddr)); if(ret == -1) { perror("bind"); return -1; } // 监听 ret = listen(lfd, 8); if(ret == -1) { perror("listen"); return -1; } // 接收客户端连接 struct sockaddr_in cliaddr; socklen_t len = sizeof(cliaddr); int cfd = accept(lfd, (struct sockaddr *)&cliaddr, &len); if(cfd == -1) { perror("accpet"); return -1; } // 获取客户端信息 char cliIp[16]; inet_ntop(AF_INET, &cliaddr.sin_addr.s_addr, cliIp, sizeof(cliIp)); unsigned short cliPort = ntohs(cliaddr.sin_port); // 输出客户端的信息 printf("client's ip is %s, and port is %d\n", cliIp, cliPort ); // 接收客户端发来的数据 char recvBuf[1024] = {0}; while(1) { int len = recv(cfd, recvBuf, sizeof(recvBuf), 0); if(len == -1) { perror("recv"); return -1; } else if(len == 0) { printf("客户端已经断开连接...\n"); break; } else if(len > 0) { printf("read buf = %s\n", recvBuf); } // 小写转大写 for(int i = 0; i < len; ++i) { recvBuf[i] = toupper(recvBuf[i]); } printf("after buf = %s\n", recvBuf); // 大写字符串发给客户端 ret = send(cfd, recvBuf, strlen(recvBuf) + 1, 0); if(ret == -1) { perror("send"); return -1; } } close(cfd); close(lfd); return 0; }
4、补充
查看网络相关信息命令:netstat
参数:
-a 所有的 socket
-p 显示正在使用 socket 的程序的名称
-n 直接使用 IP 地址,而不通过域名服务器
-apn 和 -anp 是一个意思
eg:netstat -anp | grep 9999 //9999是端口号