一.网络编程的基本概念1.IP地址IPInternet Protocol是网络之间互联的协议也就是为计算机网络相互连接进行通信而设计的协议。参与网络通信的计算机必须有一个唯一的编号以便能被其它计算机找到IP地址就是用来给Internet上的计算机一个唯一的编号目前IP地址有IPv4和IPv6两个版本。以IPv4为例介绍IP地址的格式IPv4版本的IP地址是一个32位的二进制数通常被分割为4个八位的二进制数每一段的取值为0-255如“61.135.32.26”“26.32.54.8”都是合法的IP地址。“127.0.0.0/8”是IPv4定义的本机环回地址也就是说操作系统内核检测到目标 IP 为127.x.x.x后数据包会从网络层IP层直接折返到接收队列完全不经过物理网卡、路由表转发以及防火墙的外部链路层规则——这意味着即使你拔掉网线、断开 WiFi127.0.0.1的通信依然畅通无阻。2.端口号一个IP地址对应网络上的一台计算机但是一台计算机上通常会运行多个网络程序因此只有IP地址还不够我们还需要能区分服务器上的每一个网络程序端口号就是用来区分这些不同的网络程序的。端口号用2个字节的整数表示所以每台计算机有个端口号其中系统占用0-1023端口号要避免使用。3.TCP传输控制协议Transmission Control ProtocolTCP是一种面向连接的、可靠的、基于字节流的传输层通信协议它用于在互联网上为两台设备之间建立可靠的、按顺序的数据传输通道。4.套接字套接字Socket是TCP/IP网络编程中的基本操作单元它通过“IP地址端口号”的组合唯一标识网络中的通信端点是不同主机进行双向通信的端点。分为流式套接字SOCK_STREAM和数据报套接字SOCK_DGRAM分别对应TCP和UDP协议。用打电话来来类比就是IP地址 对方的电话号码找到哪台设备。端口号Port 分机号或具体联系人找到设备上的哪个程序比如微信或浏览器。TCP 电话接通后双方反复确认“你听得清吗”的可靠通话机制。而套接字Socket就是你手里拿着的那个“电话听筒”——它是你用来发起通话、接听、挂断以及实际传输语音数据的那个操作工具。二.套接字编程的基本步骤网络上的两个程序通过一条双向通路实现数据交换这条双向通路的一端称为一个套接字。TCP套接字编程包括服务器端编程和客户端编程。1.服务器端编程步骤1加载套接字库创建服务器端套接字。2绑定套接字到一个IP地址和端口上。3将套接字设置为监听模式等待连接请求设置为监听套接字。4请求到来时接受连接请求返回一个新的专门对应此连接的套接字。5用这个新的套接字与客户进行通信通信结束就关闭这个套接字。6监听套接字继续处于监听状态等待其它连接请求。7退出程序先关闭监听套接字再释放套接字库。2.客户端编程步骤1加载套接字库创建客户端套接字。2向服务器发出连接请求。3连接成功后与服务器进行通信。4退出程序先关闭套接字再释放套接字库。三.网络编程的主要函数1.WSAStartup()WSAStartup()函数的功能是初始化Winsock DLL库动态链接库该库提供了所有Winsock函数原型如下。int WSAStartup(WORD wVersionRequested, LPWSADATA lpWSAData);参数wVersionRequested指明请求使用的Socket版本高字节指明副版本低字节指明主版本。当前主要使用2.2版本的Winsock对应ws2_32.dll库。参数lpWSAData用于保存返回的Socket版本信息。函数调用成功返回0否则返回错误码。例如WORD wVersionRequested MAKEWORD(2,0); WSADATA wsaData; int err WSAStartup(wVisionRequested, wsaData);MAKEWORD()函数用于将两个byte型数合并为word型数第一个参数为低8位第二个参数为高8位。程序请求2.0版本的Winsock。2.WSACleanup()WSACleanup()函数的功能是终止Winsock 2 DLLws2_32.dll的使用原型如下。int WSACleanup(void);函数没有参数。调用成功返回0失败返回SOCK_ERROR-1。其核心机制是引用计数Winsock 在进程中维护着一个全局引用计数器。每成功调用一次WSAStartup() 计数 1。每成功调用一次WSACleanup()计数 -1。只有当计数归零从 1 变为 0时系统才会真正执行以下操作撤销该进程注册的所有网络资源强制关闭该进程中所有仍处于打开状态的套接字类似于自动调用closesocket()卸载协议链释放底层内核对象。3.socket()socket()函数根据参数指定的地址簇Address Family决定套接字如何解读地址以及使用哪一类网络协议进行通信数据类型和协议来分配一个套接字及其所用的资源原型如下。SOCKET WSAAPI socket(int af, int type, int protocol);参数af为地址簇也就是IP地址类型常用的有AF_INETIPv4地址和AF_INET6IPv6地址。参数type指定套接字类型SOCKET_STREAM为流格式套接字SOCKET_DGRAM为数据报套接字SOCKET_RAW为原始套接字。参数protocol表示传输协议常用的IPPROTO_TCP指定为TCP传输协议IPPTOTO_UDP指定为UDP传输协议0表示由系统自行推导协议类型系统会根据af type自动匹配协议类型AF_INET SOCK_STREAM 0 TCPAF_INET SOCK_DGRAM 0 UDP。4.closesocket()closesocket()函数的功能是将一个套接字关闭原型如下。int closesocket(SOCKET s);参数s是要关闭的套接字。函数成功返回0失败返回SOCKET_ERROR。5.bind()bind()函数的功能是将IP地址和端口绑定到套接字上原型如下。int bind(SOCKET s, const struct sockaddr* name, int namelen);参数s指定要绑定的套接字。name是指向sockaddr结构体的指针可由sockaddr_in强制转换。参数namelen是name的长度。函数成功返回0失败返回SOCKET_ERROR。下面来看看sockaddr结构体其定义如下。struct sockaddr{ unsigned short sa_family; char sa_data[14]; }其中sa_family是两个字节的协议簇可以是AF_INET或AF_INET6表示IPv4和IPv6。sa_data用于存放IP地址和端口号一共14个字节。但是sa_data作为一个整体接受IP地址和端口号并不方便于是又定义了另一个结构体sockaddr_in定义如下。struct sockaddr_in{ short int sin_family; unsigned short int sin_port; struct in_addr sin_addr; unsigned char sin_zero[8]; };其中sin_family表示地址簇sin_port表示端口号sin_addr表示IP地址。其中sin_addr也是结构体占4个字节这三个成员总共占8个字节为了和sockaddr具有相同字节数在最后放8个字节的sin_zero。结构体in_addr定义如下。typedef struct in_addr{ union{ struct{ unsigned char s_b1, s_b2, s_b3, s_b4; }S_un_b; struct{ unsigned short s_w1,s_w2; }S_un_w; unsigned long S_addr; }S_un; }IN_ADDR;只有一个共用体成员分别表示三种存放IP地址的方法用四个字节用两个字用一个长整形。可以用inet_pton()函数将一个点分十进制文本的IP地址转化为in_addr类型函数原型如下。int inet_pton(int af, const char* src, void* dst);其中af是地址簇参数src是点分十进制文本的IP地址参数dst用于接收转换结果。函数成功返回1返回0表示格式无效即传入错误的IP地址文本返回-1发生错误。例如SOCKADDR_IN serverAddr; serverAddr.sin_family AF_INET; inet_pton(AF_INET, 127.0.0.1, serverAddr.sin_addr); serverAddr.sin_port htons(8000); if(bind(serverSocket, (sockaddr*)serverAddr, sizeof(serverAddr) -1){ cout绑定失败!!!endl; WSACleanup(); return 0; }其中htons()函数是将16位整数主机字节序大端序或小端序转换为网路字节序大端序。6.listen()服务器绑定套接字后再调用listen()函数使套接字进入监听状态用来监听用户发来的请求。原型如下。int listen(SOCKET s, int backlog);参数s为监听套接字backlog为客户端请求队列的最大长度。函数成功返回0失败返回SOCKET_ERROR。7.accept()在监听状态下调用accept()函数进行接受客户端的连接即取出队列最前面的客户端请求并创造一个套接字专门与该客户端通信。其函数原型如下。SOCKET accept(SOCKET s, struct sockaddr* addr, int* addrlen);参数s为监听套接字。参数addr用于返回新创建的套接字的地址结构。参数addlen返回新创建的套接字的地址长度。函数成功返回新的套接字句柄失败返回INVALIDE_SOCKET。8.connect()connect()函数用于客户端请求与服务器的监听套接字连接原型如下。int connect(SOCKET s, const struct sockaddr* name, int namelen);参数s是要建立连接的客户端套接字。参数name是服务器套接字的地址信息。参数namelen是地址结构体的长度。函数成功返回0失败返回SOCKET_ERROR。9.send()send()函数用于向TCP连接的另一端发送信息。实际上函数将信息送到socket的缓冲区里真正的发送由协议完成。原型如下。int send(SOCKET s, const char *buf, int len, int flags);参数s是发送端的套接字。参数buf是待发送信息的起始地址。参数len是请求发送的字节数。参数flags一般直接填0表示默认发送行为。发送成功返回发送的字节数失败返回SOCKET_ERROR。10.recv()recv()函数从TCP连接的另一端接送数据。实际上是从socket的缓冲区将数据复制出来。原型如下。int recv(SOCKET s, char *buf, int len, int flags);参数s是接收端的套接字。参数buf指向接收信息的缓冲区。参数len是缓冲区大小。参数flags一般是0。函数成功返回接收的字节数失败返回SOCKET_ERROR连接关闭返回0。四.服务器端与客户端实例1.服务器端编程#includeiostream #includeWinSock2.h #includeWS2tcpip.h #pragma comment(lib, ws2_32) //加载动态链接库 using namespace std; #define SERVER_ADDR 127.0.0.1 //IP地址 #define SERVER_PORT 8000 //端口号 int main() { //初始化Winsock DLL库 WSADATA wsaData; if (WSAStartup(MAKEWORD(2, 2), wsaData)) { cout 网络初始化失败!!! endl; return 0; } //校验版本 if (LOBYTE(wsaData.wVersion) ! 2 || HIBYTE(wsaData.wVersion) ! 2) { printf(Could not find a usable version of Winsock 2.2!!!\n); WSACleanup(); return 1; } //创建套接字 SOCKET serverSocket socket(AF_INET, SOCK_STREAM, 0); if (serverSocket INVALID_SOCKET) { cout 创建socket失败!!! endl; WSACleanup(); return 0; } //绑定IP和端口 sockaddr_in serverAddr; serverAddr.sin_family AF_INET; inet_pton(AF_INET, SERVER_ADDR, serverAddr.sin_addr); serverAddr.sin_port htons(SERVER_PORT); if (bind(serverSocket, (sockaddr*)serverAddr, sizeof(serverAddr)) -1) { cout 绑定失败!!! endl; WSACleanup(); return 0; } //开始监听(listen()) if (listen(serverSocket, 5) INVALID_SOCKET) { cout 监听失败!!! endl; WSACleanup(); return 0; } int i 0; //记录客户端数量 while (true) { //循环接收数据 sockaddr_in clientAddr { 0 }; int iLen sizeof(sockaddr_in); //建立专用的通信套接字 SOCKET clientSocket accept(serverSocket, (sockaddr*)clientAddr, iLen); if (clientSocket INVALID_SOCKET) { cout 监听失败!!! endl; WSACleanup(); return 0; } i; cout 第 i 个客户端已连接! endl; char recvData[64] { 0 }; int ret recv(clientSocket, recvData, 64, 0); if (ret 0) { cout 接收到客户端数据: recvData endl; char sendData[] Welcome Client!!!; ret send(clientSocket, sendData, sizeof(sendData), 0); if (ret ! sizeof(sendData)) { cout 发送数据错误!!! endl; } } else if (ret 0) { cout 客户端已断开连接 endl; } else { cout 接受数据出错!!! endl; } closesocket(clientSocket); //关闭通信套接字 } closesocket(serverSocket); //关闭服务器套接字 WSACleanup(); return 0; }2.客户端编程#includeiostream #includeWinSock2.h #includeWS2tcpip.h #pragma comment(lib, ws2_32) //加载动态链接库 using namespace std; #define SERVER_ADDR 127.0.0.1 //服务器IP地址 #define SERVER_PORT 8000 //服务器端口 int main() { //初始化Winsock DLL库 WSADATA wsaData; if (WSAStartup(MAKEWORD(2, 2), wsaData)) { cout 网络初始化失败!!! endl; return 0; } //创建套接字 SOCKET s socket(AF_INET, SOCK_STREAM, 0); if (s INVALID_SOCKET) { cout 创建socket失败!!! endl; WSACleanup(); return 0; } //连接服务器(绑定socket到服务器的IP和端口) sockaddr_in serverAddr; serverAddr.sin_family AF_INET; inet_pton(AF_INET, SERVER_ADDR, serverAddr.sin_addr); serverAddr.sin_port htons(SERVER_PORT); if (connect(s, (sockaddr*)serverAddr, sizeof(serverAddr)) -1) { cout 连接失败!!! endl; WSACleanup(); return 0; } //发送数据 char sendData[] Hello Server!; int ret send(s, sendData, sizeof(sendData), 0); if (ret ! sizeof(sendData)) { cout 发送数据出错!!! endl; WSACleanup(); return 0; } //接收数据 char recvData[64] { 0 }; ret recv(s, recvData, 64, 0); if (ret 0) { cout 接收到服务器数据: recvData endl; } else if (ret 0) { cout 客户端已断开连接 endl; } else { cout 接收数据出错!!! endl; } closesocket(s); //关闭套接字 WSACleanup(); return 0; }运行时先运行服务器端程序然后多次运行客户端程序。结果如下。第1个客户端已连接! 接收到客户端数据:Hello Server! 第2个客户端已连接! 接收到客户端数据:Hello Server! 第3个客户端已连接! 接收到客户端数据:Hello Server! 第4个客户端已连接! 接收到客户端数据:Hello Server! 第5个客户端已连接! 接收到客户端数据:Hello Server! 第6个客户端已连接! 接收到客户端数据:Hello Server!接收到服务器数据:Welcome Client!!!写在最后非常感谢您能耐心读完这篇关于网络编程的入门分享。如果文中有任何表述不准确、代码不够优雅或逻辑有误之处恳请各位大佬在评论区不吝指正我一定虚心学习、及时修正。如果您觉得这篇文章对您有所帮助不妨点个赞 并关注我您的每一次鼓励都是我继续输出的动力。后续我还会分享更多的内容欢迎常来一起交流一起成长 ——不想干活的十一
Windows C++网络编程入门:Winsock 2.2 TCP通信核心函数与完整实例剖析
一.网络编程的基本概念1.IP地址IPInternet Protocol是网络之间互联的协议也就是为计算机网络相互连接进行通信而设计的协议。参与网络通信的计算机必须有一个唯一的编号以便能被其它计算机找到IP地址就是用来给Internet上的计算机一个唯一的编号目前IP地址有IPv4和IPv6两个版本。以IPv4为例介绍IP地址的格式IPv4版本的IP地址是一个32位的二进制数通常被分割为4个八位的二进制数每一段的取值为0-255如“61.135.32.26”“26.32.54.8”都是合法的IP地址。“127.0.0.0/8”是IPv4定义的本机环回地址也就是说操作系统内核检测到目标 IP 为127.x.x.x后数据包会从网络层IP层直接折返到接收队列完全不经过物理网卡、路由表转发以及防火墙的外部链路层规则——这意味着即使你拔掉网线、断开 WiFi127.0.0.1的通信依然畅通无阻。2.端口号一个IP地址对应网络上的一台计算机但是一台计算机上通常会运行多个网络程序因此只有IP地址还不够我们还需要能区分服务器上的每一个网络程序端口号就是用来区分这些不同的网络程序的。端口号用2个字节的整数表示所以每台计算机有个端口号其中系统占用0-1023端口号要避免使用。3.TCP传输控制协议Transmission Control ProtocolTCP是一种面向连接的、可靠的、基于字节流的传输层通信协议它用于在互联网上为两台设备之间建立可靠的、按顺序的数据传输通道。4.套接字套接字Socket是TCP/IP网络编程中的基本操作单元它通过“IP地址端口号”的组合唯一标识网络中的通信端点是不同主机进行双向通信的端点。分为流式套接字SOCK_STREAM和数据报套接字SOCK_DGRAM分别对应TCP和UDP协议。用打电话来来类比就是IP地址 对方的电话号码找到哪台设备。端口号Port 分机号或具体联系人找到设备上的哪个程序比如微信或浏览器。TCP 电话接通后双方反复确认“你听得清吗”的可靠通话机制。而套接字Socket就是你手里拿着的那个“电话听筒”——它是你用来发起通话、接听、挂断以及实际传输语音数据的那个操作工具。二.套接字编程的基本步骤网络上的两个程序通过一条双向通路实现数据交换这条双向通路的一端称为一个套接字。TCP套接字编程包括服务器端编程和客户端编程。1.服务器端编程步骤1加载套接字库创建服务器端套接字。2绑定套接字到一个IP地址和端口上。3将套接字设置为监听模式等待连接请求设置为监听套接字。4请求到来时接受连接请求返回一个新的专门对应此连接的套接字。5用这个新的套接字与客户进行通信通信结束就关闭这个套接字。6监听套接字继续处于监听状态等待其它连接请求。7退出程序先关闭监听套接字再释放套接字库。2.客户端编程步骤1加载套接字库创建客户端套接字。2向服务器发出连接请求。3连接成功后与服务器进行通信。4退出程序先关闭套接字再释放套接字库。三.网络编程的主要函数1.WSAStartup()WSAStartup()函数的功能是初始化Winsock DLL库动态链接库该库提供了所有Winsock函数原型如下。int WSAStartup(WORD wVersionRequested, LPWSADATA lpWSAData);参数wVersionRequested指明请求使用的Socket版本高字节指明副版本低字节指明主版本。当前主要使用2.2版本的Winsock对应ws2_32.dll库。参数lpWSAData用于保存返回的Socket版本信息。函数调用成功返回0否则返回错误码。例如WORD wVersionRequested MAKEWORD(2,0); WSADATA wsaData; int err WSAStartup(wVisionRequested, wsaData);MAKEWORD()函数用于将两个byte型数合并为word型数第一个参数为低8位第二个参数为高8位。程序请求2.0版本的Winsock。2.WSACleanup()WSACleanup()函数的功能是终止Winsock 2 DLLws2_32.dll的使用原型如下。int WSACleanup(void);函数没有参数。调用成功返回0失败返回SOCK_ERROR-1。其核心机制是引用计数Winsock 在进程中维护着一个全局引用计数器。每成功调用一次WSAStartup() 计数 1。每成功调用一次WSACleanup()计数 -1。只有当计数归零从 1 变为 0时系统才会真正执行以下操作撤销该进程注册的所有网络资源强制关闭该进程中所有仍处于打开状态的套接字类似于自动调用closesocket()卸载协议链释放底层内核对象。3.socket()socket()函数根据参数指定的地址簇Address Family决定套接字如何解读地址以及使用哪一类网络协议进行通信数据类型和协议来分配一个套接字及其所用的资源原型如下。SOCKET WSAAPI socket(int af, int type, int protocol);参数af为地址簇也就是IP地址类型常用的有AF_INETIPv4地址和AF_INET6IPv6地址。参数type指定套接字类型SOCKET_STREAM为流格式套接字SOCKET_DGRAM为数据报套接字SOCKET_RAW为原始套接字。参数protocol表示传输协议常用的IPPROTO_TCP指定为TCP传输协议IPPTOTO_UDP指定为UDP传输协议0表示由系统自行推导协议类型系统会根据af type自动匹配协议类型AF_INET SOCK_STREAM 0 TCPAF_INET SOCK_DGRAM 0 UDP。4.closesocket()closesocket()函数的功能是将一个套接字关闭原型如下。int closesocket(SOCKET s);参数s是要关闭的套接字。函数成功返回0失败返回SOCKET_ERROR。5.bind()bind()函数的功能是将IP地址和端口绑定到套接字上原型如下。int bind(SOCKET s, const struct sockaddr* name, int namelen);参数s指定要绑定的套接字。name是指向sockaddr结构体的指针可由sockaddr_in强制转换。参数namelen是name的长度。函数成功返回0失败返回SOCKET_ERROR。下面来看看sockaddr结构体其定义如下。struct sockaddr{ unsigned short sa_family; char sa_data[14]; }其中sa_family是两个字节的协议簇可以是AF_INET或AF_INET6表示IPv4和IPv6。sa_data用于存放IP地址和端口号一共14个字节。但是sa_data作为一个整体接受IP地址和端口号并不方便于是又定义了另一个结构体sockaddr_in定义如下。struct sockaddr_in{ short int sin_family; unsigned short int sin_port; struct in_addr sin_addr; unsigned char sin_zero[8]; };其中sin_family表示地址簇sin_port表示端口号sin_addr表示IP地址。其中sin_addr也是结构体占4个字节这三个成员总共占8个字节为了和sockaddr具有相同字节数在最后放8个字节的sin_zero。结构体in_addr定义如下。typedef struct in_addr{ union{ struct{ unsigned char s_b1, s_b2, s_b3, s_b4; }S_un_b; struct{ unsigned short s_w1,s_w2; }S_un_w; unsigned long S_addr; }S_un; }IN_ADDR;只有一个共用体成员分别表示三种存放IP地址的方法用四个字节用两个字用一个长整形。可以用inet_pton()函数将一个点分十进制文本的IP地址转化为in_addr类型函数原型如下。int inet_pton(int af, const char* src, void* dst);其中af是地址簇参数src是点分十进制文本的IP地址参数dst用于接收转换结果。函数成功返回1返回0表示格式无效即传入错误的IP地址文本返回-1发生错误。例如SOCKADDR_IN serverAddr; serverAddr.sin_family AF_INET; inet_pton(AF_INET, 127.0.0.1, serverAddr.sin_addr); serverAddr.sin_port htons(8000); if(bind(serverSocket, (sockaddr*)serverAddr, sizeof(serverAddr) -1){ cout绑定失败!!!endl; WSACleanup(); return 0; }其中htons()函数是将16位整数主机字节序大端序或小端序转换为网路字节序大端序。6.listen()服务器绑定套接字后再调用listen()函数使套接字进入监听状态用来监听用户发来的请求。原型如下。int listen(SOCKET s, int backlog);参数s为监听套接字backlog为客户端请求队列的最大长度。函数成功返回0失败返回SOCKET_ERROR。7.accept()在监听状态下调用accept()函数进行接受客户端的连接即取出队列最前面的客户端请求并创造一个套接字专门与该客户端通信。其函数原型如下。SOCKET accept(SOCKET s, struct sockaddr* addr, int* addrlen);参数s为监听套接字。参数addr用于返回新创建的套接字的地址结构。参数addlen返回新创建的套接字的地址长度。函数成功返回新的套接字句柄失败返回INVALIDE_SOCKET。8.connect()connect()函数用于客户端请求与服务器的监听套接字连接原型如下。int connect(SOCKET s, const struct sockaddr* name, int namelen);参数s是要建立连接的客户端套接字。参数name是服务器套接字的地址信息。参数namelen是地址结构体的长度。函数成功返回0失败返回SOCKET_ERROR。9.send()send()函数用于向TCP连接的另一端发送信息。实际上函数将信息送到socket的缓冲区里真正的发送由协议完成。原型如下。int send(SOCKET s, const char *buf, int len, int flags);参数s是发送端的套接字。参数buf是待发送信息的起始地址。参数len是请求发送的字节数。参数flags一般直接填0表示默认发送行为。发送成功返回发送的字节数失败返回SOCKET_ERROR。10.recv()recv()函数从TCP连接的另一端接送数据。实际上是从socket的缓冲区将数据复制出来。原型如下。int recv(SOCKET s, char *buf, int len, int flags);参数s是接收端的套接字。参数buf指向接收信息的缓冲区。参数len是缓冲区大小。参数flags一般是0。函数成功返回接收的字节数失败返回SOCKET_ERROR连接关闭返回0。四.服务器端与客户端实例1.服务器端编程#includeiostream #includeWinSock2.h #includeWS2tcpip.h #pragma comment(lib, ws2_32) //加载动态链接库 using namespace std; #define SERVER_ADDR 127.0.0.1 //IP地址 #define SERVER_PORT 8000 //端口号 int main() { //初始化Winsock DLL库 WSADATA wsaData; if (WSAStartup(MAKEWORD(2, 2), wsaData)) { cout 网络初始化失败!!! endl; return 0; } //校验版本 if (LOBYTE(wsaData.wVersion) ! 2 || HIBYTE(wsaData.wVersion) ! 2) { printf(Could not find a usable version of Winsock 2.2!!!\n); WSACleanup(); return 1; } //创建套接字 SOCKET serverSocket socket(AF_INET, SOCK_STREAM, 0); if (serverSocket INVALID_SOCKET) { cout 创建socket失败!!! endl; WSACleanup(); return 0; } //绑定IP和端口 sockaddr_in serverAddr; serverAddr.sin_family AF_INET; inet_pton(AF_INET, SERVER_ADDR, serverAddr.sin_addr); serverAddr.sin_port htons(SERVER_PORT); if (bind(serverSocket, (sockaddr*)serverAddr, sizeof(serverAddr)) -1) { cout 绑定失败!!! endl; WSACleanup(); return 0; } //开始监听(listen()) if (listen(serverSocket, 5) INVALID_SOCKET) { cout 监听失败!!! endl; WSACleanup(); return 0; } int i 0; //记录客户端数量 while (true) { //循环接收数据 sockaddr_in clientAddr { 0 }; int iLen sizeof(sockaddr_in); //建立专用的通信套接字 SOCKET clientSocket accept(serverSocket, (sockaddr*)clientAddr, iLen); if (clientSocket INVALID_SOCKET) { cout 监听失败!!! endl; WSACleanup(); return 0; } i; cout 第 i 个客户端已连接! endl; char recvData[64] { 0 }; int ret recv(clientSocket, recvData, 64, 0); if (ret 0) { cout 接收到客户端数据: recvData endl; char sendData[] Welcome Client!!!; ret send(clientSocket, sendData, sizeof(sendData), 0); if (ret ! sizeof(sendData)) { cout 发送数据错误!!! endl; } } else if (ret 0) { cout 客户端已断开连接 endl; } else { cout 接受数据出错!!! endl; } closesocket(clientSocket); //关闭通信套接字 } closesocket(serverSocket); //关闭服务器套接字 WSACleanup(); return 0; }2.客户端编程#includeiostream #includeWinSock2.h #includeWS2tcpip.h #pragma comment(lib, ws2_32) //加载动态链接库 using namespace std; #define SERVER_ADDR 127.0.0.1 //服务器IP地址 #define SERVER_PORT 8000 //服务器端口 int main() { //初始化Winsock DLL库 WSADATA wsaData; if (WSAStartup(MAKEWORD(2, 2), wsaData)) { cout 网络初始化失败!!! endl; return 0; } //创建套接字 SOCKET s socket(AF_INET, SOCK_STREAM, 0); if (s INVALID_SOCKET) { cout 创建socket失败!!! endl; WSACleanup(); return 0; } //连接服务器(绑定socket到服务器的IP和端口) sockaddr_in serverAddr; serverAddr.sin_family AF_INET; inet_pton(AF_INET, SERVER_ADDR, serverAddr.sin_addr); serverAddr.sin_port htons(SERVER_PORT); if (connect(s, (sockaddr*)serverAddr, sizeof(serverAddr)) -1) { cout 连接失败!!! endl; WSACleanup(); return 0; } //发送数据 char sendData[] Hello Server!; int ret send(s, sendData, sizeof(sendData), 0); if (ret ! sizeof(sendData)) { cout 发送数据出错!!! endl; WSACleanup(); return 0; } //接收数据 char recvData[64] { 0 }; ret recv(s, recvData, 64, 0); if (ret 0) { cout 接收到服务器数据: recvData endl; } else if (ret 0) { cout 客户端已断开连接 endl; } else { cout 接收数据出错!!! endl; } closesocket(s); //关闭套接字 WSACleanup(); return 0; }运行时先运行服务器端程序然后多次运行客户端程序。结果如下。第1个客户端已连接! 接收到客户端数据:Hello Server! 第2个客户端已连接! 接收到客户端数据:Hello Server! 第3个客户端已连接! 接收到客户端数据:Hello Server! 第4个客户端已连接! 接收到客户端数据:Hello Server! 第5个客户端已连接! 接收到客户端数据:Hello Server! 第6个客户端已连接! 接收到客户端数据:Hello Server!接收到服务器数据:Welcome Client!!!写在最后非常感谢您能耐心读完这篇关于网络编程的入门分享。如果文中有任何表述不准确、代码不够优雅或逻辑有误之处恳请各位大佬在评论区不吝指正我一定虚心学习、及时修正。如果您觉得这篇文章对您有所帮助不妨点个赞 并关注我您的每一次鼓励都是我继续输出的动力。后续我还会分享更多的内容欢迎常来一起交流一起成长 ——不想干活的十一