我赞同Bob Quinn和Dave Shute的说法: WinSock中的SO_REUSEADDR就是个鸡肋, 最好不用它
来源:互联网 发布:jsp博客系统源码 编辑:程序博客网 时间:2024/04/29 11:55
本文, 我们讨论的范围是WinSock, 不是unix/linux中的socket. 在Windows Sockets这本书中, 作者Bob Quinn和Dave Shute说:SO_REUSEADDR很少有正当的需要, 我们应该尽量不用它。
不高谈阔论了, 我们来程序: (说明, 我pc的ip是192.168.1.100)
#include <stdio.h>#include <winsock2.h> // 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); // 接收客户端数据,最后一个参数一般设置为0printf("%s\n", recvBuf);closesocket(sockConn);}closesocket(sockSrv);WSACleanup();return 0;}程序结果为:
error, iRet is -1
好, 我们设置一下SO_REUSEADDR, 代码如下:
#include <stdio.h>#include <winsock2.h> // 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); // 接收客户端数据,最后一个参数一般设置为0printf("%s\n", recvBuf);closesocket(sockConn);}closesocket(sockSrv);WSACleanup();return 0;}结果也为:
error, iRet is -1
可见, 在同一进程进行两次bind, 无论是否启用SO_REUSEADDR, 第二次bind都会失效。
继续看:
#include <stdio.h>#include <winsock2.h> // 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); // 接收客户端数据,最后一个参数一般设置为0printf("%s\n", recvBuf);closesocket(sockConn);}closesocket(sockSrv);WSACleanup();return 0;}我们开启这份代码对应的两个进程, 发现第一个进程正常, 第二个进程同样会打印:error, iRet is -1. 此时, 刚好就是地址冲突了。 好, 先关掉这两个进程。
我们再次来用一下SO_REUSEADDR, 程序为:
#include <stdio.h>#include <winsock2.h> // 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); // 接收客户端数据,最后一个参数一般设置为0printf("%s\n", recvBuf);closesocket(sockConn);}closesocket(sockSrv);WSACleanup();return 0;}我们开启这份代码的两个进程, 发现bind都不会失败, 可见SO_REUSEADDR是起到作用了---防止地址冲突导致bind失败。 好, 我们关掉这两个进程, 免得影响后面的实验。
防止地址冲突导致bind失败又有什么用呢? 其实用处确实不大, 没有采用SO_REUSEADDR时候, 如果地址冲突了, 程序猿自己有责任由义务去检查是否已经有端口在监听, 而不是用SO_REUSEADDR来规避, 因为, 从理论上来讲, 重复捆绑会让WinSock底层的协议栈非常难堪, 这不是成心搞破坏搞骚乱么?
我们看一下实验的验证结果。 服务端程序为(采用SO_REUSEADDR):
#include <stdio.h>#include <winsock2.h> // 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); // 接收客户端数据,最后一个参数一般设置为0printf("%s\n", recvBuf);closesocket(sockConn);}closesocket(sockSrv);WSACleanup();return 0;}
客户端程序为:
#include <winsock2.h>#include <stdio.h>#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上继续讨论这个话题。
0 0
- 我赞同Bob Quinn和Dave Shute的说法: WinSock中的SO_REUSEADDR就是个鸡肋, 最好不用它
- 《Windows Sockets 网络编程》. Bob Quinn & Dave Shuttle (非常实用的Windows编程书籍)
- 你可以赞同它,也可以反对它,但唯一做不到的就是忽视它。
- 扫地机器人就是个鸡肋
- 有了vb.net,C#就是个鸡肋
- 最好的时光,就是你喜欢我
- 这是我赞同的观点!
- 践行这个信念,就是对我的最好报答
- 遇见,就是最好的
- 哼!我就是不用大学刚毕业的人
- 学习曲线通俗的说法就是“熟能生巧”
- SO_REUSEADDR和SO_REUSEPORT的区别
- 独特的表白:我就是个程序员
- 知乎上赞同数最高的999个回答
- 我就是个
- TCP/IP中的SO_REUSEADDR和SO_KEEPALIVE
- 唯快不破:TCP/IP中的SO_REUSEADDR和SO_KEEPALIVE
- 计算机中的“类”的说法
- C++刷题三
- 常见的C语言内存出错问题分析
- 设计模式 创建型模式实践
- 【Linux】Linux文件系统的实现
- 再不努力就老了
- 我赞同Bob Quinn和Dave Shute的说法: WinSock中的SO_REUSEADDR就是个鸡肋, 最好不用它
- 判断线段相交模板
- 第五周总结
- getchar()和EOF总结
- clang++ 优化返回代码NRVO
- The resource identified by this request is only capable of generating responses with characteristics
- UVA - 156 Ananagrams
- 蛋哥的学习笔记之-基于Unity的Shader编程:0-2 基本3D图形渲染管线概述
- 【链表】链表上的绝技