基于TCP的C/S通信程序设计|网络协议分析实验
一、实验目的
熟悉TCP协议下通信程序的编写、编译和执行过程;掌握基于 Winsock 套接字 API 的 C/S(客户机/服务器)双向通信程序的设计与开发方法。
通过编程实践理解 TCP 三次握手建立连接、可靠数据传输、四次挥手释放连接的基本过程,学会利用错误码定位网络程序故障。
二、实验内容与设计思想
1、实验内容
模拟实现 TCP 协议通信过程,编程实现服务器端与客户端之间的双向数据传递。
- 客户端向服务器端发送:
我是集美大学网络空间安全专业学生 - 服务器端接收后回应:
我也是集美大学网络空间安全专业学生
2、设计思想
服务器端创建监听套接字,绑定本地IP与端口,进入监听状态等待客户端连接,调用accept()接收客户端连接请求。
客户端创建套接字,调用connect()主动向服务器发起连接。
accept()与connect()返回代表TCP连接建立完成;服务器socket得到的是监听套接字,accept返回新套接字用于实际收发数据。
服务器调用顺序:
socket → bind → listen → accept,必须先运行服务器,再启动客户端。
三、实验使用环境
- 操作系统:Windows 10
- 编程环境:Visual C++ 6.0,Winsock2 API,链接
ws2_32.lib库 - 通信参数:本机回环地址
127.0.0.1,端口8888
四、实验步骤和调试过程
1、实验步骤
服务器端流程
WSAStartup()初始化Winsock库socket()创建TCP监听套接字bind()绑定本机地址与8888端口listen()开启监听accept()阻塞等待客户端连接(三次握手完成)recv()接收客户端消息send()回复应答消息closesocket()关闭套接字WSACleanup()释放Winsock库资源
客户端流程
WSAStartup()初始化Winsock库socket()创建客户端套接字- 设置服务器地址
127.0.0.1:8888 connect()主动连接服务器send()发送消息recv()接收服务器回复closesocket()释放套接字WSACleanup()释放库资源
编译注意:定义宏
_WINSOCK_DEPRECATED_NO_WARNINGS消除API废弃警告,链接ws2_32.lib库;运行顺序:先启动服务器,后运行客户端。
2、服务器端代码 server.c
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <stdio.h>
#include <winsock2.h>
#include <string.h>
#pragma comment(lib, "ws2_32.lib")
#define PORT 8888
#define BUF_SIZE 1024
int main()
{
// 修复控制台中文乱码
SetConsoleOutputCP(936);
WSADATA wsaData;
SOCKET sockServer, sockConn;
struct sockaddr_in serAddr, cliAddr;
int addrLen = sizeof(struct sockaddr_in);
char recvBuf[BUF_SIZE];
char sendBuf[] = "我也是集美大学网络空间安全专业学生";
// 1. 初始化Winsock库
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
printf("WSA初始化失败!错误码:%d\n", WSAGetLastError());
system("pause");
return -1;
}
// 2. 创建TCP监听套接字
sockServer = socket(AF_INET, SOCK_STREAM, 0);
if (sockServer == INVALID_SOCKET) {
printf("创建监听套接字失败!错误码:%d\n", WSAGetLastError());
WSACleanup();
system("pause");
return -1;
}
// 3. 配置服务器地址:监听本机所有网卡,端口8888
serAddr.sin_family = AF_INET;
serAddr.sin_addr.s_addr = htonl(INADDR_ANY);
serAddr.sin_port = htons(PORT);
// 4. 绑定地址与端口
if (bind(sockServer, (struct sockaddr*)&serAddr, sizeof(serAddr)) == SOCKET_ERROR) {
printf("bind绑定失败!错误码:%d\n", WSAGetLastError());
closesocket(sockServer);
WSACleanup();
system("pause");
return -1;
}
// 5. 开启监听
if (listen(sockServer, 5) == SOCKET_ERROR) {
printf("listen监听失败!错误码:%d\n", WSAGetLastError());
closesocket(sockServer);
WSACleanup();
system("pause");
return -1;
}
printf("服务器启动成功,正在监听端口 %d...\n等待客户端连接...\n", PORT);
// 6. 阻塞等待客户端连接(三次握手在此完成)
sockConn = accept(sockServer, (struct sockaddr*)&cliAddr, &addrLen);
if (sockConn == INVALID_SOCKET) {
printf("接收连接失败!错误码:%d\n", WSAGetLastError());
closesocket(sockServer);
WSACleanup();
system("pause");
return -1;
}
printf("客户端连接成功!\n");
// 7. 接收客户端发来的消息
memset(recvBuf, 0, BUF_SIZE);
int recvLen = recv(sockConn, recvBuf, BUF_SIZE - 1, 0);
if (recvLen > 0) {
printf("收到客户端消息:%s\n", recvBuf);
}
// 8. 向客户端回复指定消息
send(sockConn, sendBuf, strlen(sendBuf), 0);
printf("已向客户端回复消息\n");
// 9. 释放套接字与库资源
closesocket(sockConn);
closesocket(sockServer);
WSACleanup();
printf("\n通信结束,按任意键退出...\n");
system("pause");
return 0;
}
3、客户端代码 client.c
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <stdio.h>
#include <winsock2.h>
#include <string.h>
#pragma comment(lib, "ws2_32.lib")
#define PORT 8888
#define BUF_SIZE 1024
int main()
{
// 修复控制台中文乱码
SetConsoleOutputCP(936);
WSADATA wsaData;
SOCKET sockClient;
struct sockaddr_in serAddr;
char recvBuf[BUF_SIZE];
char sendBuf[] = "我是集美大学网络空间安全专业学生";
// 1. 初始化Winsock库
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
printf("WSA初始化失败!错误码:%d\n", WSAGetLastError());
system("pause");
return -1;
}
// 2. 创建客户端TCP套接字
sockClient = socket(AF_INET, SOCK_STREAM, 0);
if (sockClient == INVALID_SOCKET) {
printf("创建客户端套接字失败!错误码:%d\n", WSAGetLastError());
WSACleanup();
system("pause");
return -1;
}
// 3. 配置服务器地址(本机回环测试用127.0.0.1)
serAddr.sin_family = AF_INET;
inet_pton(AF_INET, "127.0.0.1", &serAddr.sin_addr);
serAddr.sin_port = htons(PORT);
// 4. 主动连接服务器
if (connect(sockClient, (struct sockaddr*)&serAddr, sizeof(serAddr)) == SOCKET_ERROR) {
printf("连接服务器失败!错误码:%d\n请先启动服务器程序\n", WSAGetLastError());
closesocket(sockClient);
WSACleanup();
system("pause");
return -1;
}
printf("成功连接到服务器!\n");
// 5. 向服务器发送指定消息
send(sockClient, sendBuf, strlen(sendBuf), 0);
printf("已发送消息:%s\n", sendBuf);
// 6. 接收服务器的回复
memset(recvBuf, 0, BUF_SIZE);
int recvLen = recv(sockClient, recvBuf, BUF_SIZE - 1, 0);
if (recvLen > 0) {
printf("收到服务器回复:%s\n", recvBuf);
}
// 7. 释放资源
closesocket(sockClient);
WSACleanup();
printf("\n通信结束,按任意键退出...\n");
system("pause");
return 0;
}
4、运行结果

图1 服务器端与客户端运行结果截图
5、调试遇到的问题与解决
- 混淆监听套接字与通信套接字:直接拿监听套接字收发数据,收不到消息。
解决:socket()得到监听套接字,只负责接收连接;accept()返回新套接字用于收发业务数据。 - 编译报Winsock API废弃警告:缺少宏定义。
解决:代码开头定义#define _WINSOCK_DEPRECATED_NO_WARNINGS。 - 控制台中文乱码。
解决:SetConsoleOutputCP(936);设置GBK输出编码。 - 先运行客户端,服务器未启动,connect连接失败。
解决:严格顺序,先运行服务器程序,再运行客户端。 - 未解析外部符号报错:没有链接套接字库。
解决:添加#pragma comment(lib, "ws2_32.lib")。 - 接收缓冲区出现垃圾乱码字符:缓冲区没有清零。
解决:使用memset()将接收缓冲区预先置0。 - 故障排查技巧:每个套接字函数调用后判断返回值,使用
WSAGetLastError()打印错误码快速定位端口占用、连接拒绝等问题。
五、TCP工作过程分析
TCP 是面向连接、可靠的传输层协议,整个通信过程分为三个阶段:三次握手建立连接、数据传输、四次挥手释放连接。本实验基于 Windows Winsock 套接字 API 实现 C/S 双向通信,代码中的函数调用与 TCP 协议过程一一对应。
1、三次握手(建立连接)
- 客户端调用
connect(),发出SYN报文请求建立连接; - 服务器完成
socket-bind-listen,accept()阻塞等待,收到SYN回复SYN+ACK; - 客户端收到
SYN+ACK回复ACK报文;
连接进入ESTABLISHED就绪状态,握手完成,可以传输业务数据。
注意:服务器两种套接字区分:
- 监听套接字(socket创建):只负责接收连接请求,不收发业务数据;
- 通信套接字(accept返回):专门和客户端进行send/recv数据交互。
2、数据传输阶段
连接就绪后,客户端send()发送字符串;服务器recv()接收,再send()回复应答消息;客户端recv()拿到服务器回复。
TCP依靠序号、确认应答、重传机制保证数据有序、不丢失。程序通过WSAGetLastError()获取错误码,便于网络故障定位。
3、四次挥手(释放连接)
调用closesocket()关闭套接字触发四次挥手:
- 一方发送
FIN报文,表示不再发送数据; - 对方回复
ACK确认FIN; - 对方数据发送完毕,发送自己的
FIN报文; - 收到FIN的一方回复
ACK,双向连接彻底释放。
最后调用WSACleanup()释放Winsock库资源。
4、TCP通信工作流程示意图

六、实验小结
组员分工小结
- 黄一华(服务器端代码编写调试)
主要完成server.c编写调试。一开始混淆监听套接字与通信套接字导致无法接收消息。学会使用WSAGetLastError()错误码排查bind、accept失败;牢记服务器函数调用顺序socket‑bind‑listen‑accept,服务器必须优先启动;网络编程要做好资源释放,closesocket、WSACleanup()防止资源泄漏。把课本TCP三次握手理论落地到代码,加深套接字编程理解。 - 吴雨桦(客户端代码、运行测试)
负责client.c编写与程序测试。踩坑包括缺少宏定义编译警告、中文乱码、启动顺序错误连接失败。理解connect()对应三次握手客户端发起连接;明白回环地址127.0.0.1、端口的作用;遇到报错不再盲目修改代码,依靠错误码分析定位问题。 - 胡磊(原理分析、流程图、报告整理)
梳理TCP通信原理,将系统调用和三次握手、四次挥手做对应;绘制TCP流程示意图。理解TCP面向连接与UDP无连接的本质差异;认识到网络学习不能只背理论,编码实践才能真正理解协议运行。负责汇总截图、改写内容降低查重。 - 宫静茹(整体测试、错误复盘、拓展学习)
整体联调,复现各类错误场景。发现缺少库链接、缓冲区不清零、异常退出未释放资源等问题;意识到网络程序健壮性的重要性,每个套接字调用都要做返回值判断,打印错误信息。小组分模块开发再联调效率更高;后续希望学习多线程服务器,实现同时服务多个客户端。
整体心得
本次TCP的C/S套接字实验,把课堂的三次握手、四次挥手理论和Winsock系统调用对应起来:connect()触发客户端握手,accept()完成服务器握手,closesocket()触发四次挥手。
小组分工协作,分模块开发再联合调试;遇到故障依靠返回值、错误码定位问题,而不是盲目修改代码;深刻体会网络编程中资源释放、异常判断的重要性。计算机网络不能只停留在理论记忆,动手编码才可以真正理解协议运行。后续希望拓展多线程服务器,实现同时处理多个客户端连接。
浙公网安备 33010602011771号