博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
我赞同Bob Quinn和Dave Shute的说法: WinSock中的SO_REUSEADDR就是个鸡肋, 最好不用它
阅读量:4140 次
发布时间:2019-05-25

本文共 8237 字,大约阅读时间需要 27 分钟。

       本文, 我们讨论的范围是WinSock, 不是unix/linux中的socket. 在Windows Sockets这本书中, 作者Bob Quinn和Dave Shute说:SO_REUSEADDR很少有正当的需要, 我们应该尽量不用它。

      

       不高谈阔论了, 我们来程序: (说明, 我pc的ip是192.168.1.100)

 

#include 
#include
// winsock接口#pragma comment(lib, "ws2_32.lib") // winsock实现int main(){ WORD wVersionRequested; // 双字节,winsock库的版本 WSADATA wsaData; // winsock库版本的相关信息 wVersionRequested = MAKEWORD(1, 1); // 0x0101 即:257 // 加载winsock库并确定winsock版本,系统会把数据填入wsaData中 WSAStartup( wVersionRequested, &wsaData ); // AF_INET 表示采用TCP/IP协议族 // SOCK_STREAM 表示采用TCP协议 // 0是通常的默认情况 unsigned int sockSrv = socket(AF_INET, SOCK_STREAM, 0); SOCKADDR_IN addrSrv; addrSrv.sin_family = AF_INET; // TCP/IP协议族 addrSrv.sin_addr.S_un.S_addr = inet_addr("192.168.1.100"); // socket对应的IP地址 addrSrv.sin_port = htons(8888); // socket对应的端口 // 将socket绑定到某个IP和端口(IP标识主机,端口标识通信进程) int iRet = bind(sockSrv,(SOCKADDR*)&addrSrv, sizeof(SOCKADDR)); if(iRet < 0) { printf("error, iRet is %d\n", iRet); return 1; } iRet = bind(sockSrv,(SOCKADDR*)&addrSrv, sizeof(SOCKADDR)); if(iRet < 0) { printf("error, iRet is %d\n", iRet); return 1; } // 将socket设置为监听模式,5表示等待连接队列的最大长度 listen(sockSrv, 5); SOCKADDR_IN addrClient; int len = sizeof(SOCKADDR); while(1) { // sockSrv为监听状态下的socket // &addrClient是缓冲区地址,保存了客户端的IP和端口等信息 // len是包含地址信息的长度 // 如果客户端没有启动,那么程序一直停留在该函数处 unsigned int sockConn = accept(sockSrv,(SOCKADDR*)&addrClient, &len); char recvBuf[100] = {0}; recv(sockConn, recvBuf, 100 - 1, 0); // 接收客户端数据,最后一个参数一般设置为0 printf("%s\n", recvBuf); closesocket(sockConn); } closesocket(sockSrv); WSACleanup(); return 0;}

      程序结果为:

 

error, iRet is -1

      

      好, 我们设置一下SO_REUSEADDR, 代码如下:

 

#include 
#include
// winsock接口#pragma comment(lib, "ws2_32.lib") // winsock实现int main(){ WORD wVersionRequested; // 双字节,winsock库的版本 WSADATA wsaData; // winsock库版本的相关信息 wVersionRequested = MAKEWORD(1, 1); // 0x0101 即:257 // 加载winsock库并确定winsock版本,系统会把数据填入wsaData中 WSAStartup( wVersionRequested, &wsaData ); // AF_INET 表示采用TCP/IP协议族 // SOCK_STREAM 表示采用TCP协议 // 0是通常的默认情况 unsigned int sockSrv = socket(AF_INET, SOCK_STREAM, 0); SOCKADDR_IN addrSrv; addrSrv.sin_family = AF_INET; // TCP/IP协议族 addrSrv.sin_addr.S_un.S_addr = inet_addr("192.168.1.100"); // socket对应的IP地址 addrSrv.sin_port = htons(8888); // socket对应的端口 int reuse = 1; setsockopt(sockSrv, SOL_SOCKET, SO_REUSEADDR, (char *)&reuse, sizeof(reuse)); // 将socket绑定到某个IP和端口(IP标识主机,端口标识通信进程) int iRet = bind(sockSrv,(SOCKADDR*)&addrSrv, sizeof(SOCKADDR)); if(iRet < 0) { printf("error, iRet is %d\n", iRet); return 1; } iRet = bind(sockSrv,(SOCKADDR*)&addrSrv, sizeof(SOCKADDR)); if(iRet < 0) { printf("error, iRet is %d\n", iRet); return 1; } // 将socket设置为监听模式,5表示等待连接队列的最大长度 listen(sockSrv, 5); SOCKADDR_IN addrClient; int len = sizeof(SOCKADDR); while(1) { // sockSrv为监听状态下的socket // &addrClient是缓冲区地址,保存了客户端的IP和端口等信息 // len是包含地址信息的长度 // 如果客户端没有启动,那么程序一直停留在该函数处 unsigned int sockConn = accept(sockSrv,(SOCKADDR*)&addrClient, &len); char recvBuf[100] = {0}; recv(sockConn, recvBuf, 100 - 1, 0); // 接收客户端数据,最后一个参数一般设置为0 printf("%s\n", recvBuf); closesocket(sockConn); } closesocket(sockSrv); WSACleanup(); return 0;}

     结果也为:

 

error, iRet is -1

 

     可见, 在同一进程进行两次bind, 无论是否启用SO_REUSEADDR, 第二次bind都会失效。

 

      继续看:

 

#include 
#include
// winsock接口#pragma comment(lib, "ws2_32.lib") // winsock实现int main(){ WORD wVersionRequested; // 双字节,winsock库的版本 WSADATA wsaData; // winsock库版本的相关信息 wVersionRequested = MAKEWORD(1, 1); // 0x0101 即:257 // 加载winsock库并确定winsock版本,系统会把数据填入wsaData中 WSAStartup( wVersionRequested, &wsaData ); // AF_INET 表示采用TCP/IP协议族 // SOCK_STREAM 表示采用TCP协议 // 0是通常的默认情况 unsigned int sockSrv = socket(AF_INET, SOCK_STREAM, 0); SOCKADDR_IN addrSrv; addrSrv.sin_family = AF_INET; // TCP/IP协议族 addrSrv.sin_addr.S_un.S_addr = inet_addr("192.168.1.100"); // socket对应的IP地址 addrSrv.sin_port = htons(8888); // socket对应的端口 // 将socket绑定到某个IP和端口(IP标识主机,端口标识通信进程) int iRet = bind(sockSrv,(SOCKADDR*)&addrSrv, sizeof(SOCKADDR)); if(iRet < 0) { printf("error, iRet is %d\n", iRet); return 1; } // 将socket设置为监听模式,5表示等待连接队列的最大长度 listen(sockSrv, 5); SOCKADDR_IN addrClient; int len = sizeof(SOCKADDR); while(1) { // sockSrv为监听状态下的socket // &addrClient是缓冲区地址,保存了客户端的IP和端口等信息 // len是包含地址信息的长度 // 如果客户端没有启动,那么程序一直停留在该函数处 unsigned int sockConn = accept(sockSrv,(SOCKADDR*)&addrClient, &len); char recvBuf[100] = {0}; recv(sockConn, recvBuf, 100 - 1, 0); // 接收客户端数据,最后一个参数一般设置为0 printf("%s\n", recvBuf); closesocket(sockConn); } closesocket(sockSrv); WSACleanup(); return 0;}

      我们开启这份代码对应的两个进程, 发现第一个进程正常, 第二个进程同样会打印:error, iRet is -1.  此时, 刚好就是地址冲突了。 好, 先关掉这两个进程。

 

 

      我们再次来用一下SO_REUSEADDR, 程序为:

 

#include 
#include
// winsock接口#pragma comment(lib, "ws2_32.lib") // winsock实现int main(){ WORD wVersionRequested; // 双字节,winsock库的版本 WSADATA wsaData; // winsock库版本的相关信息 wVersionRequested = MAKEWORD(1, 1); // 0x0101 即:257 // 加载winsock库并确定winsock版本,系统会把数据填入wsaData中 WSAStartup( wVersionRequested, &wsaData ); // AF_INET 表示采用TCP/IP协议族 // SOCK_STREAM 表示采用TCP协议 // 0是通常的默认情况 unsigned int sockSrv = socket(AF_INET, SOCK_STREAM, 0); SOCKADDR_IN addrSrv; addrSrv.sin_family = AF_INET; // TCP/IP协议族 addrSrv.sin_addr.S_un.S_addr = inet_addr("192.168.1.100"); // socket对应的IP地址 addrSrv.sin_port = htons(8888); // socket对应的端口 int reuse = 1; setsockopt(sockSrv, SOL_SOCKET, SO_REUSEADDR, (char *)&reuse, sizeof(reuse)); // 将socket绑定到某个IP和端口(IP标识主机,端口标识通信进程) int iRet = bind(sockSrv,(SOCKADDR*)&addrSrv, sizeof(SOCKADDR)); if(iRet < 0) { printf("error, iRet is %d\n", iRet); return 1; } // 将socket设置为监听模式,5表示等待连接队列的最大长度 listen(sockSrv, 5); SOCKADDR_IN addrClient; int len = sizeof(SOCKADDR); while(1) { // sockSrv为监听状态下的socket // &addrClient是缓冲区地址,保存了客户端的IP和端口等信息 // len是包含地址信息的长度 // 如果客户端没有启动,那么程序一直停留在该函数处 unsigned int sockConn = accept(sockSrv,(SOCKADDR*)&addrClient, &len); char recvBuf[100] = {0}; recv(sockConn, recvBuf, 100 - 1, 0); // 接收客户端数据,最后一个参数一般设置为0 printf("%s\n", recvBuf); closesocket(sockConn); } closesocket(sockSrv); WSACleanup(); return 0;}

      我们开启这份代码的两个进程, 发现bind都不会失败, 可见SO_REUSEADDR是起到作用了---防止地址冲突导致bind失败。 好, 我们关掉这两个进程, 免得影响后面的实验。

 

 

      防止地址冲突导致bind失败又有什么用呢? 其实用处确实不大, 没有采用SO_REUSEADDR时候, 如果地址冲突了, 程序猿自己有责任由义务去检查是否已经有端口在监听, 而不是用SO_REUSEADDR来规避, 因为, 从理论上来讲, 重复捆绑会让WinSock底层的协议栈非常难堪, 这不是成心搞破坏搞骚乱么?

 

      我们看一下实验的验证结果。 服务端程序为(采用SO_REUSEADDR):

 

#include 
#include
// winsock接口#pragma comment(lib, "ws2_32.lib") // winsock实现int main(){ WORD wVersionRequested; // 双字节,winsock库的版本 WSADATA wsaData; // winsock库版本的相关信息 wVersionRequested = MAKEWORD(1, 1); // 0x0101 即:257 // 加载winsock库并确定winsock版本,系统会把数据填入wsaData中 WSAStartup( wVersionRequested, &wsaData ); // AF_INET 表示采用TCP/IP协议族 // SOCK_STREAM 表示采用TCP协议 // 0是通常的默认情况 unsigned int sockSrv = socket(AF_INET, SOCK_STREAM, 0); SOCKADDR_IN addrSrv; addrSrv.sin_family = AF_INET; // TCP/IP协议族 addrSrv.sin_addr.S_un.S_addr = inet_addr("192.168.1.100"); // socket对应的IP地址 addrSrv.sin_port = htons(8888); // socket对应的端口 int reuse = 1; setsockopt(sockSrv, SOL_SOCKET, SO_REUSEADDR, (char *)&reuse, sizeof(reuse)); // 将socket绑定到某个IP和端口(IP标识主机,端口标识通信进程) int iRet = bind(sockSrv,(SOCKADDR*)&addrSrv, sizeof(SOCKADDR)); if(iRet < 0) { printf("error, iRet is %d\n", iRet); return 1; } // 将socket设置为监听模式,5表示等待连接队列的最大长度 listen(sockSrv, 5); SOCKADDR_IN addrClient; int len = sizeof(SOCKADDR); while(1) { // sockSrv为监听状态下的socket // &addrClient是缓冲区地址,保存了客户端的IP和端口等信息 // len是包含地址信息的长度 // 如果客户端没有启动,那么程序一直停留在该函数处 unsigned int sockConn = accept(sockSrv,(SOCKADDR*)&addrClient, &len); char recvBuf[100] = {0}; recv(sockConn, recvBuf, 100 - 1, 0); // 接收客户端数据,最后一个参数一般设置为0 printf("%s\n", recvBuf); closesocket(sockConn); } closesocket(sockSrv); WSACleanup(); return 0;}

 

 

 

     客户端程序为:

 

#include 
#include
#pragma comment(lib, "ws2_32.lib")int main(){ WORD wVersionRequested; WSADATA wsaData; wVersionRequested = MAKEWORD(1, 1);

 

       实验过程:我们同时开启4个服务端(设分别为1, 2, 3, 4), 然后开启客户端, 发现在服务器4上有good.  也就是说, 客户端与服务器4建立了通信。 好, 我们关掉这5个进程, 再做重复的实验, 这次又发现不是第4个进程上出现good. 这就说明: 具体是哪个服务器与客户端进行通信, 行为是未定义的。 所以, 你采用SO_REUSEADDR又有什么大的用途呢? 还不如不要SO_REUSEADDR, 让程序产生错误, 让程序猿来定位错误, 检查端口冲突。

 

 

   

       当然, 据我了解, unix/linux上的SO_REUSEADDR貌似相对复杂一点点, 以后我还会在unix/linux上继续讨论这个话题。

 

 

 

转载地址:http://bugvi.baihongyu.com/

你可能感兴趣的文章
laravel事务
查看>>
【JavaScript 教程】浏览器—History 对象
查看>>
这才是学习Vite2的正确姿势!
查看>>
7 个适用于所有前端开发人员的很棒API,你需要了解一下
查看>>
25个构建Web项目的HTML建议,你需要了解一下!
查看>>
【web素材】02-10款大气的购物商城网站模板
查看>>
6种方式实现JavaScript数组扁平化(flat)方法的总结
查看>>
49个在工作中常用且容易遗忘的CSS样式清单整理
查看>>
20种在学习编程的同时也可以在线赚钱的方法
查看>>
隐藏搜索框:CSS 动画正反向序列
查看>>
127个超级实用的JavaScript 代码片段,你千万要收藏好(上)
查看>>
【视频教程】Javascript ES6 教程27—ES6 构建一个Promise
查看>>
【5分钟代码练习】01—导航栏鼠标悬停效果的实现
查看>>
127个超级实用的JavaScript 代码片段,你千万要收藏好(中)
查看>>
127个超级实用的JavaScript 代码片段,你千万要收藏好(下)
查看>>
【web素材】03-24款后台管理系统网站模板
查看>>
Flex 布局教程:语法篇
查看>>
年薪50万+的90后程序员都经历了什么?
查看>>
2019年哪些外快收入可达到2万以上?
查看>>
【JavaScript 教程】标准库—Date 对象
查看>>