基于TCP的C/S通信仿真
一、实验要求
模拟实现TCP协议通信过程,要求编程实现。客户端向服务器端发送“我是集美大学网络空间安全专业学生”,服务器回应“我也是集美大学网络空间安全专业学生”。
二、 编程环境
Visual C++ 6.0
本次实验要创建两个Win32工程,分别为服务器的Server和客户端的Client。
三、流程图(TCP)
套接字相当于网络接口,将客户端的接口和服务器的接口连上,两者才能通信。
服务器是一个特殊电脑上提供某种服务的特殊应用。
每一台电脑都分配一个ip地址,电脑上的每一个应用对应分配一个端口号,即每一个服务器对应一个端口号,套接字和端口号绑定,从而抽象为这台电脑上的这个服务器。
当客户端向服务器发起通信时,通过ip地址指定连接的电脑,通过端口号指定该电脑上的哪个服务器。
则网络通信模型如下图:127.0.0.1(本地回环)意味着和本机上的服务器通信。
由此得出通信流程图为:
四、编程准备工作
在server和client两个网络工程中都应做到:
- 正确调用头文件和库
#include<stdio.h> #include<string.h> #include<WinSock2.h> #pragma comment(lib,"ws2_32.lib")
- 加载和释放控制台,并重定向标准输入输出
AllocConsole(); freopen("CONOUT$","w",stdout); freopen("CONIN$", "r", stdin); freopen("CONOUT$", "w", stderr); //程序 FreeConsole();
- 初始化和清理winsock
// 初始化 Winsock WSADATA wsaData; int result = WSAStartup(MAKEWORD(2, 2), &wsaData); if (result != 0) { printf("WSAStartup failed with error: %d\n", result); return 1; } //网络编程相关的操作,如创建套接字等 // 清理 Winsock WSACleanup();
五、Server端详解
1、在Server中新建server.cpp文件:
2、编程步骤
- 创建监听套接字
//创建监听套接字并判断是否创建成功 /* SOCKET socket( _In_ int af, //协议地址簇 IPv4:AF_INET/IPv6:AF_INET6 _In_ int type, //类型 流式套接字:SOCK_STREAM/数据报套接字:SOCK_DGRAM _In_ int protocol //保护协议 一般设置为 0,让系统自动选择合适的协议。 ); */ SOCKET listen_socket=socket(AF_INET, SOCK_STREAM, 0); if (INVALID_SOCKET==listen_socket) { printf("create listen socket failed!!! errcode: %d\n", GetLastError()); WSACleanup(); return -1; }
- 为监听套接字绑定端口号
//创建ip地址结构体并将监听套接字和ip地址结构体绑定 /* * ip地址结构体 struct sockaddr_in { ADDRESS_FAMILY sin_family; //协议地址簇 2字节 USHORT sin_port; //端口号 2字节 IN_ADDR sin_addr; //ip地址 4字节 CHAR sin_zero[8]; //保留字节 8字节 }; struct sockaddr { u_short sa_family; //协议地址簇 2字节 CHAR sa_data[14]; //14字节 } */ struct sockaddr_in local = {0}; local.sin_family = AF_INET; local.sin_port = htons(8080);//绑定端口 大小端转化:中间设备使用大端序(路由器),但是电脑使用的是小端序,所以要转化
/* 电脑上有多个网卡,服务器可以选择接收哪个或哪些网卡传过来的数据。 如:127.0.0.1(本地环回),则服务器只能和自己通信 INADDR_LOOPBACK 一般写全0地址(0.0.0.0),表示接收全部网卡的数据 INADDR_ANY */ //local.sin_addr.s_addr = htonl(INADDR_ANY);//接收全部网卡的数据 大小端转化 local.sin_addr.s_addr = inet_addr("0.0.0.0");//接收全部网卡的数据 字符串ip转成整数ip /* int bind( SOCKET s, //绑定的套接字 const struct sockaddr FAR * name, //ip地址结构体 int namelen //ip地址结构体的长度 ); */ bind(listen_socket,(struct sockaddr*)&local,sizeof(local));//sockaddr_in要转化为sockaddr创建ip地址结构体sockaddr_in,指定协议簇为ipv4(AF_INET),指定端口号为8080,指定服务器接收哪个网卡传来的数据;将监听套接字与转化后的ip地址结构sockaddr绑定
- 给监听套接字开启监听属性
//给监听套接字开启监听属性,只用来接收连接 /* int listen( _In_ SOCKET s, //监听套接字 _In_ int backlog //指定等待连接队列的最大长度 ); */ if (-1 == listen(listen_socket, 10)) { printf("start listen failed!!! errcode: %d\n", GetLastError()); WSACleanup(); return -1; }
- 等待客户端连接
//等待客户端连接 /* SOCKET accept( SOCKET s, //监听套接字 struct sockaddr * addr, //客户端的ip地址和端口号 int * addrlen //结构的大小 ); 成功时,accept 函数返回一个新的套接字用于与客户端进行通信。 失败时,返回 INVALID_SOCKET。 阻塞函数,等到有客户端连接进来就接受连接,然后返回,否则就阻塞 */ printf("This is a Server.\n"); while (1) { printf("\nWaiting for connect...\n"); SOCKET client_socket = accept(listen_socket, NULL, NULL); if (INVALID_SOCKET == client_socket) { printf("Connect invalid!!!\n"); continue; } printf("Connected successfully!\n\n"); //开始通信 }
- 开始通信
//5.开始通信(B/S) //浏览器向服务器发送http请求,http是tcp的上层协议,可以接受http的报文 /* int recv( SOCKET s, //与客户端进行通信的socket char * buf, //接收的数据的存储地址 int len, //接收的长度 int flags //标志位,填0 ); */ //接收信息 char rbuffer[1024] = { 0 }; int ret = recv(client_socket, rbuffer, 1024, 0); if (ret <= 0) { printf("The client has disconnected.\n"); break; } printf("recive:\t%s\n", rbuffer); //发送信息 char sbuffer[1024] = { 0 }; // 检查接收到的消息 if (strcmp(rbuffer, "我是集美大学网络空间安全专业学生") == 0) { sprintf(sbuffer, "我也是集美大学网络空间安全专业学生"); printf("send:\t我也是集美大学网络空间安全专业学生\n"); } else { printf("send:\t"); scanf("%s", sbuffer); } send(client_socket, sbuffer, strlen(sbuffer), 0);
- 关闭连接
//6.关闭读写通道和连接 shutdown(client_socket, SD_BOTH); closesocket(client_socket);
六、Client端详解
1、在Client中新建client.cpp文件:
2、编程步骤
- 创建socket套接字
//创建socket套接字并判断是否创建成功 SOCKET client_socket = socket(AF_INET, SOCK_STREAM, 0); if (INVALID_SOCKET == client_socket) { printf("create socket failed!!! errcode: %d\n", GetLastError()); WSACleanup(); return -1; }
- 连接服务器
//创建目标服务器的ip结构体,确定目标服务器的ip地址和端口号,发起连接 struct sockaddr_in target;//目标服务器的ip结构体 target.sin_family = AF_INET; target.sin_port = htons(8080); target.sin_addr.s_addr = inet_addr("127.0.0.1"); if (-1 == connect(client_socket, (struct sockaddr*)&target, sizeof(target))) { printf("connect server failed!!!\n"); shutdown(client_socket, SD_BOTH); closesocket(client_socket); WSACleanup(); return -1; }
- 开始通信
//开始通讯 printf("This is a Cilent.\n\n"); while (1) { //发送信息 printf("send:\t"); char sbuffer[1024] = { 0 }; scanf("%s", sbuffer); send(client_socket, sbuffer, strlen(sbuffer), 0); //接收消息 char rbuffer[1024] = { 0 }; int ret = recv(client_socket, rbuffer, 1024, 0); if (ret <= 0) { break;//断开连接 } printf("recive:\t%s\n", rbuffer); }
- 关闭连接
//4.关闭连接 shutdown(client_socket, SD_BOTH); shutdown(client_socket, SD_BOTH); closesocket(client_socket);
七、完整代码
server.cpp
#include<stdio.h> #include<string.h> #include<WinSock2.h> #pragma comment(lib,"ws2_32.lib") int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { AllocConsole(); freopen("CONOUT$","w",stdout); freopen("CONIN$", "r", stdin); freopen("CONOUT$", "w", stderr); // 初始化 Winsock WSADATA wsaData; int result=WSAStartup(MAKEWORD(2, 2), &wsaData); if (result != 0) { printf("WSAStartup failed with error: %d\n", result); return 1; } //1.创建socket套接字 SOCKET listen_socket=socket(AF_INET, SOCK_STREAM, 0); if (INVALID_SOCKET==listen_socket) { printf("create listen socket failed!!! errcode: %d\n", GetLastError()); WSACleanup(); return -1; } //2.给socket绑定端口号 struct sockaddr_in local = {0}; local.sin_family = AF_INET; local.sin_port = htons(8080);//绑定端口 //local.sin_addr.s_addr = htonl(INADDR_ANY);//接收全部网卡的数据 大小端转化 local.sin_addr.s_addr = inet_addr("0.0.0.0");//接收全部网卡的数据 字符串ip转成整数ip bind(listen_socket,(struct sockaddr*)&local,sizeof(local)); //3.给socke开启监听属性,只用来接收连接 if (-1 == listen(listen_socket, 10)) { printf("start listen failed!!! errcode: %d\n", GetLastError()); WSACleanup(); return -1; } //4.等待客户端连接 printf("This is a Server.\n"); while (1) { printf("\nWaiting for connect...\n"); SOCKET client_socket = accept(listen_socket, NULL, NULL); if (INVALID_SOCKET == client_socket) { printf("Connect invalid!!!\n"); continue; } printf("Connected successfully!\n\n"); while (1) { //5.开始通信 //接收信息 char rbuffer[1024] = { 0 }; int ret=recv(client_socket, rbuffer, 1024, 0); if (ret <= 0) { printf("The client has disconnected.\n"); break; } printf("recive:\t%s\n", rbuffer); //发送信息 char sbuffer[1024] = { 0 }; // 检查接收到的消息 if(strcmp(rbuffer, "我是集美大学网络空间安全专业学生") == 0) { sprintf(sbuffer, "我也是集美大学网络空间安全专业学生"); printf("send:\t我也是集美大学网络空间安全专业学生\n"); } else { printf("send:\t"); scanf("%s", sbuffer); } send(client_socket, sbuffer, strlen(sbuffer), 0); } //6.关闭连接 shutdown(client_socket, SD_BOTH); closesocket(client_socket); } FreeConsole(); // 清理 Winsock WSACleanup(); return 0; }
client.cpp
#include<stdio.h> #include<WinSock2.h> #pragma comment(lib,"ws2_32.lib") int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { AllocConsole(); freopen("CONOUT$","w",stdout); freopen("CONIN$", "r", stdin); freopen("CONOUT$", "w", stderr); // 初始化 Winsock WSADATA wsaData; int result=WSAStartup(MAKEWORD(2, 2), &wsaData); if (result != 0) { printf("WSAStartup failed with error: %d\n", result); return 1; } //1.创建socket套接字 SOCKET client_socket = socket(AF_INET, SOCK_STREAM, 0); if (INVALID_SOCKET == client_socket) { printf("create socket failed!!! errcode: %d\n", GetLastError()); WSACleanup(); return -1; } //2.连接服务器 struct sockaddr_in target;//目标服务器的ip结构体 target.sin_family = AF_INET; target.sin_port = htons(8080); target.sin_addr.s_addr = inet_addr("127.0.0.1"); if (-1 == connect(client_socket, (struct sockaddr*)&target, sizeof(target))) { printf("connect server failed!!!\n"); shutdown(client_socket, SD_BOTH); closesocket(client_socket); WSACleanup(); return -1; } //3.开始通讯 printf("This is a Cilent.\n\n"); while (1) { //发送信息 printf("send:\t"); char sbuffer[1024] = { 0 }; scanf("%s", sbuffer); send(client_socket, sbuffer, strlen(sbuffer),0); //接收消息 char rbuffer[1024] = { 0 }; int ret=recv(client_socket, rbuffer, 1024, 0); if (ret <= 0) { break;//断开连接 } printf("recive:\t%s\n", rbuffer); } //4.关闭连接 shutdown(client_socket, SD_BOTH);shutdown(client_socket, SD_BOTH); closesocket(client_socket); FreeConsole(); // 清理 Winsock WSACleanup(); return 0; }
八、调试结果
先后执行Server工程和Client工程,如果询问是否建立文件选择是即可。
客户端输入:我是集美大学网络空间安全专业学生,并发送给服务器,服务器自动回复:我也是集美大学网络空间安全专业学生。
客户端和服务器可以随意交流数据
关闭客户端,则服务器失去连接,等待下一个客户端的连接
九、总结
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!