为什么TCP服务端需要调用bind函数而客户端通常不需要呢?

您所在的位置:网站首页 ip地址绑定端口号是什么 为什么TCP服务端需要调用bind函数而客户端通常不需要呢?

为什么TCP服务端需要调用bind函数而客户端通常不需要呢?

2024-07-16 21:28| 来源: 网络整理| 查看: 265

       那一年, 某哥让我写个tcp服务端客户端程序, 我草草写完, 然后他检查,并问我, 为什么客户端不用bind呢? 然后, 我卡壳了, 好尴尬啊。 现在, 我们来一起彻底了解一下这个问题。

       先看看bind函数是干啥的。bind函数就是绑定, 将一个socket绑定到一个地址上, 也可以这么说:bind函数对一个socket进行命名(注意:socket名称包括三要素: 协议, ip,  port)。

 

       我们来看看最常见的方式, tcp服务端用bind, 但是tcp的客户端不用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("0.0.0.0"); // socket对应的IP地址 addrSrv.sin_port = htons(8888); // socket对应的端口 // 将socket绑定到某个IP和端口(IP标识主机,端口标识通信进程) bind(sockSrv, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR)); // 将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 sendBuf[100] = {0}; sprintf(sendBuf,"%s", inet_ntoa(addrClient.sin_addr)); // 将客户端的IP地址保存下来 send(sockConn, sendBuf, strlen(sendBuf) + 1, 0); // 发送数据到客户端,最后一个参数一般设置为0 char recvBuf[100] = {0}; recv(sockConn, recvBuf, 100 - 1, 0); // 接收客户端数据,最后一个参数一般设置为0 printf("%s\n", recvBuf); getchar(); 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); WSAStartup( wVersionRequested, &wsaData ); SOCKET sockClient = socket(AF_INET, SOCK_STREAM, 0); SOCKADDR_IN addrSrv; addrSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); addrSrv.sin_family = AF_INET; addrSrv.sin_port = htons(8888); connect(sockClient, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR)); char recvBuf[100] = {0}; recv(sockClient, recvBuf, 100, 0); printf("%s\n", recvBuf); send(sockClient, "hello world", strlen("hello world") + 1, 0); getchar(); closesocket(sockClient); WSACleanup(); return 0; }

      启动它。

 

 

     然后, 我们在cmd中查看连接情况, 结果如下:

C:\Documents and Settings\Administrator>netstat -nao | findstr 8888   TCP    0.0.0.0:8888           0.0.0.0:0              LISTENING       11256   TCP    127.0.0.1:2964         127.0.0.1:8888         ESTABLISHED     13688   TCP    127.0.0.1:8888         127.0.0.1:2964         ESTABLISHED     11256

       

     可以看到, 客户端的端口号是2964. 实际上, 这个端口号是操作系统随机分配的, 在分配的时候, 操作系统会保证不与现有的端口冲突。  好, 关掉这两个进程。 我们再重启服务端, 然后再重启客户端, 建立新的tcp连接, 我们再在cmd中查一次, 结果为:

C:\Documents and Settings\Administrator>netstat -nao | findstr 8888   TCP    0.0.0.0:8888           0.0.0.0:0              LISTENING       11256   TCP    127.0.0.1:2964         127.0.0.1:8888         ESTABLISHED     13688   TCP    127.0.0.1:8888         127.0.0.1:2964         ESTABLISHED     11256 C:\Documents and Settings\Administrator>netstat -nao | findstr 8888   TCP    0.0.0.0:8888           0.0.0.0:0              LISTENING       13296   TCP    127.0.0.1:3156         127.0.0.1:8888         ESTABLISHED     13628   TCP    127.0.0.1:8888         127.0.0.1:3156         ESTABLISHED     13296

 

      我们发现, 客户端的端口编程了3156, 和上次的 2964不一致, 这就印证了操作系统会随机分配客户端端口这个说法。

 

      以上就是最经典的服务端bind(这个是必须的), 客户端不bind.  那么, 我们自然要问, 客户端可不可以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("0.0.0.0"); // socket对应的IP地址 addrSrv.sin_port = htons(8888); // socket对应的端口 // 将socket绑定到某个IP和端口(IP标识主机,端口标识通信进程) bind(sockSrv,(SOCKADDR*)&addrSrv, sizeof(SOCKADDR)); // 将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 sendBuf[100] = {0}; sprintf(sendBuf,"%s", inet_ntoa(addrClient.sin_addr)); // 将客户端的IP地址保存下来 send(sockConn, sendBuf, strlen(sendBuf) + 1, 0); // 发送数据到客户端,最后一个参数一般设置为0 char recvBuf[100] = {0}; recv(sockConn, recvBuf, 100 - 1, 0); // 接收客户端数据,最后一个参数一般设置为0 printf("%s\n", recvBuf); getchar(); closesocket(sockConn); } closesocket(sockSrv); WSACleanup(); return 0; }

      启动它。

 

 

      客户端采用bind, 如下:

 

#include #include #pragma comment(lib, "ws2_32.lib") int main() { WORD wVersionRequested; WSADATA wsaData; wVersionRequested = MAKEWORD(1, 1); WSAStartup( wVersionRequested, &wsaData ); SOCKET sockClient = socket(AF_INET, SOCK_STREAM, 0); // tcp客户端也要玩玩bind啦, 童鞋们!!! SOCKADDR_IN addrClient; addrClient.sin_family = AF_INET; addrClient.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); // 客户端地址 addrClient.sin_port = htons(7777); // 客户端为7777端口 bind(sockClient,(SOCKADDR*)&addrClient, sizeof(SOCKADDR)); // 客户端也来玩玩绑定 SOCKADDR_IN addrSrv; addrSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); // 服务器地址 addrSrv.sin_family = AF_INET; addrSrv.sin_port = htons(8888); connect(sockClient, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR)); char recvBuf[100] = {0}; recv(sockClient, recvBuf, 100, 0); printf("%s\n", recvBuf); send(sockClient, "hello world", strlen("hello world") + 1, 0); getchar(); closesocket(sockClient); WSACleanup(); return 0; }

      好, 启动它。

 

 

      这样, tcp的还是建立了正常的连接, 我们在cmd中查看一下:

C:\Documents and Settings\Administrator>netstat -nao | findstr 8888   TCP    0.0.0.0:8888           0.0.0.0:0              LISTENING       11256   TCP    127.0.0.1:2964         127.0.0.1:8888         ESTABLISHED     13688   TCP    127.0.0.1:8888         127.0.0.1:2964         ESTABLISHED     11256 C:\Documents and Settings\Administrator>netstat -nao | findstr 8888   TCP    0.0.0.0:8888           0.0.0.0:0              LISTENING       13296   TCP    127.0.0.1:3156         127.0.0.1:8888         ESTABLISHED     13628   TCP    127.0.0.1:8888         127.0.0.1:3156         ESTABLISHED     13296 C:\Documents and Settings\Administrator>netstat -nao | findstr 8888   TCP    0.0.0.0:8888           0.0.0.0:0              LISTENING       12916   TCP    127.0.0.1:7777         127.0.0.1:8888         ESTABLISHED     13176   TCP    127.0.0.1:8888         127.0.0.1:7777         ESTABLISHED     12916         可以看到, 客户端的端口号为7777, 反复多次做这个实验, 发下端口号都是7777. 由此可见, 客户端自己指定端口是成功的, 客户端也是可以bind的。

 

        现在, 我们可算是看清楚了, tcp服务端必须有bind, 客户端通常不用bind,  当然如果你够无聊, 那也可以用一下bind. 在这里, 我要说一下了: 客户端用bind的程序很容易出问题, 你想想啊, 操作系统指定的不会冲突的随机端口难道不比你自己指定的容易冲突的固定端口好?

        在很多场景下, 我们要在一个pc上开启多个客户端进程, 如果指定固定端口, 必然会造成端口冲突, 影响通信!所以, 我们就不要费力不讨好了, 客户端就不要指定端口了, 让操作系统来搞。这样,实际上就是操作系统对客户端的socket进行了隐式的命名(名称中的端口是随机的)。

 

        ok, 该休息了, 明天继续聊其他话题。

 

 



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3