当前位置: 首页 > news >正文

Windows C++网络编程入门:Winsock 2.2 TCP通信核心函数与完整实例剖析

一.网络编程的基本概念

1.IP地址

IP(Internet 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层)直接折返到接收队列,完全不经过物理网卡、路由表转发以及防火墙的外部链路层规则——这意味着即使你拔掉网线、断开 WiFi,127.0.0.1的通信依然畅通无阻。

2.端口号

一个IP地址对应网络上的一台计算机,但是一台计算机上通常会运行多个网络程序,因此只有IP地址还不够,我们还需要能区分服务器上的每一个网络程序,端口号就是用来区分这些不同的网络程序的。端口号用2个字节的整数表示,所以每台计算机有个端口号,其中系统占用0-1023端口号,要避免使用。

3.TCP

传输控制协议(Transmission Control Protocol,TCP)是一种面向连接的、可靠的、基于字节流的传输层通信协议,它用于在互联网上,为两台设备之间建立可靠的、按顺序的数据传输通道。

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 DLL(ws2_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_INET(IPv4地址)和AF_INET6(IPv6地址)。

参数type指定套接字类型,SOCKET_STREAM为流格式套接字,SOCKET_DGRAM为数据报套接字,SOCKET_RAW为原始套接字。

参数protocol表示传输协议,常用的IPPROTO_TCP指定为TCP传输协议,IPPTOTO_UDP指定为UDP传输协议,0表示由系统自行推导协议类型,系统会根据af + type自动匹配协议类型(AF_INET + SOCK_STREAM + 0 = TCP,AF_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.服务器端编程

#include<iostream> #include<WinSock2.h> #include<WS2tcpip.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.客户端编程

#include<iostream> #include<WinSock2.h> #include<WS2tcpip.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!!!

写在最后

非常感谢您能耐心读完这篇关于网络编程的入门分享。如果文中有任何表述不准确、代码不够优雅或逻辑有误之处,恳请各位大佬在评论区不吝指正,我一定虚心学习、及时修正。

如果您觉得这篇文章对您有所帮助,不妨点个赞 👍 并关注我,您的每一次鼓励都是我继续输出的动力。后续我还会分享更多的内容,欢迎常来,一起交流,一起成长! 🚀

——不想干活的十一

http://www.jsqmd.com/news/1197854/

相关文章:

  • 浪琴中国官方售后服务中心|官方地址及服务热线权威信息公示(2026年7月更新) - 浪琴官方售后服务中心
  • 2026 年 7 月新发布:贵溪靠谱的冷拔无缝钢管定制厂家深度剖析,揭秘:顶级产品为何都选择这种无缝钢管? - 实业推荐官【官方】
  • 电脑自用自查小技巧,不用拆机!避开维修店漫天加价
  • 技术方案评审就用麦肯锡的结构化思考、SCQA表达框架
  • 2026年Hermes Agent全链路生产部署指南:ECS/Docker/Railway/Dify四方案实战
  • C++线程池工业级实现:从核心原理到避坑指南
  • EasyExplorer 一款多窗口、多页签、多工作区的资源管理器
  • AI 搜索摘要正在吃掉开放网页:Google 答案层背后的内容生态危机
  • 即梦Seedance 2.0去水印脚本原理与实战:前端内存级截流技术解析
  • 基于微信小程序的陪诊服务系统设计与实现
  • 2026年7月泊头异形铸铁/翻砂铸铁厂家推荐分析_泊头市嘉硕金属制品有限公司 - 品牌宣传支持者
  • 【ROBOGUIDE仿真】:双机器人协同搬运工作站中的信号交互与同步逻辑设计
  • Typora永久免费版Win7适配指南:离线可用的Markdown编辑器部署方案
  • ChatGPT会议记录整理:不是“复制粘贴”,而是构建可追溯、可审计、可追溯的数字会议资产(ISO 27001合规实践)
  • CodeGraph代码知识图谱:本地化AI编程加速原理与实践
  • 劳力士中国官方售后服务中心|服务电话及全部地址权威信息声明(2026年7月最新) - 劳力士服务中心
  • 模型剪枝经典论文精读:BlockDrop: Dynamic Inference Paths in Residual Networks
  • DeepSeek:低成本高性能AI代码助手实战指南
  • 胖头鱼的技术专栏-446 26ai新特性实战:JSON关系二元性内联写增强(20260715)
  • Pulsar生产集群部署与Pulsar Manager运维实践指南
  • B站视频数据分析终极指南:用Bilivideoinfo快速获取精确数据
  • (2026最新)辽阳防水补漏本地人必选的正规靠谱公司推荐-房屋漏水检测维修师傅上门-卫生间/厨房/阳台/房顶/外墙漏水检测精准测漏 - 即刻修防水
  • (2026最新)贵阳防水补漏本地人必选的正规靠谱公司推荐-房屋漏水检测维修师傅上门-卫生间/厨房/阳台/房顶/外墙漏水检测精准测漏 - 即刻修防水
  • WSL2 + Ollama 保姆级部署:Windows 本地大模型稳定运行方案
  • 浪琴中国官方售后服务中心|全新官方电话和维修地址权威信息通知(2026年7月最新) - 浪琴服务中心
  • 阿里云计算巢部署OpenClaw与Hermes Agent实战指南
  • GDPR/CCPA合规采集指南
  • 2026年7月球墨铸铁/泊头粘土沙工艺铸铁生产商推荐名单_泊头市嘉硕金属制品有限公司 - 行业平台推荐
  • 具身智能:人工智能的下一个范式革命
  • Go 学习笔记:结构体嵌入、sort.Interface 与 io.Reader/Writer