先叠甲,只能实现客户端到服务端一对一聊天,要支持多人的话,得使用多线程,这个答主还不会,可以去问问gpt帮你添加一下这个功能。
目前是基于visual studio 2022软件,仅支持终端通话,想要获得精美的图形化界面,问gpt,或者用Qt去写,这个只是一个小作业,仅供初学者参考,或者应付学校的计网作业,想要写一个大项目,拥有完整的功能的话,推荐去b站找找,咱这你懂的,开vip去吧
一、项目简介
我会给出代码思路,供大家写项目实验报告书的,也方便大家理解代码怎么来的。
代码分为两个部分,分别为服务端(Server)和客户端(Client),注意,大家需要在vs中创建两个项目,分别来新建空项目,可以直接粘贴代码,看好多答主直接给代码的,又不教怎么弄,运行还全是报错😓😓 分别新建两个cpp文件,server.cpp 和 client.cpp 。
二、代码部分讲解
以下是对服务端和客户端代码的逐步解释,帮助你理解代码的功能,便于讲解和复现。
(一)服务端代码讲解
1. 初始化 Winsock
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
std::cerr << "WSAStartup failed.\n";
return 1;
}
- Winsock 是 Windows 网络编程的核心库。
WSAStartup
初始化网络功能,MAKEWORD(2, 2)
指定使用 Winsock 2.2 版本。 - 如果初始化失败,会返回错误。
2. 创建服务器套接字
serverSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
AF_INET
:指定使用 IPv4。SOCK_STREAM
:使用面向连接的 TCP 协议。IPPROTO_TCP
:明确使用 TCP 协议。
如果返回值是 INVALID_SOCKET
,说明套接字创建失败。
3. 绑定服务器地址和端口
serverAddr.sin_family = AF_INET;
serverAddr.sin_addr.s_addr = INADDR_ANY; // 接收来自任何 IP 的连接
serverAddr.sin_port = htons(PORT); // 指定监听端口
bind(serverSocket, (sockaddr*)&serverAddr, sizeof(serverAddr));
sin_family
:设置协议族(IPv4)。sin_addr.s_addr
:指定可以接收任何 IP 的连接。sin_port
:通过htons
转换为网络字节序。
bind
:将套接字和服务器地址绑定起来。
4. 开始监听
listen(serverSocket, 5);
listen
函数使服务器进入监听状态。- 参数
5
表示最多能排队的连接数。
5. 接受客户端连接
clientSocket = accept(serverSocket, (sockaddr*)&clientAddr, &clientAddrSize);
accept
等待客户端连接,当有客户端连接时,返回一个新的clientSocket
,用于与该客户端通信。
6. 通信逻辑
int bytesReceived = recv(clientSocket, buffer, BUFFER_SIZE, 0);
send(clientSocket, reply.c_str(), reply.length(), 0);
recv
:接收客户端发送的数据,存入buffer
。send
:将数据发送回客户端。
循环监听直到客户端断开连接。
7. 清理资源
closesocket(clientSocket);
closesocket(serverSocket);
WSACleanup();
closesocket
:关闭套接字,释放资源。WSACleanup
:清理 Winsock 库。
(二)客户端代码讲解
1. 初始化 Winsock
WSADATA wsaData;
WSAStartup(MAKEWORD(2, 2), &wsaData);
- 和服务端一样,初始化 Winsock。
2. 创建客户端套接字
clientSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
- 创建 TCP 套接字,用于与服务端通信。
3. 连接到服务器
serverAddr.sin_family = AF_INET;
serverAddr.sin_addr.s_addr = inet_addr(SERVER_IP);
serverAddr.sin_port = htons(PORT);
connect(clientSocket, (sockaddr*)&serverAddr, sizeof(serverAddr));
inet_addr
:将服务器的 IP 地址(字符串)转换为in_addr
格式。connect
:尝试连接到指定 IP 和端口的服务器。
4. 通信逻辑
send(clientSocket, message.c_str(), message.length(), 0);
recv(clientSocket, buffer, BUFFER_SIZE, 0);
send
:将用户输入的数据发送给服务端。recv
:接收服务端返回的消息并打印。
5. 清理资源
closesocket(clientSocket);
WSACleanup();
- 关闭套接字,释放 Winsock 资源。
运行代码的步骤
- 启动服务端:
- 先运行服务端程序,等待客户端连接。
- 启动客户端:
- 在同一台电脑或局域网中运行客户端程序,连接到服务端。
- 输入消息,观察双方通信。
(三)运行截图
三、原代码(可以直接粘贴)
如果有报错,先问gpt,21世纪了要学会自己解决问题了,当然只要答主没毕业,理论上一直会在,最多隔一两天回你😀😀
1、服务端(server.cpp)
#include <iostream>
#include <winsock2.h>
#include <string>
#pragma comment(lib, "ws2_32.lib") // 链接 Winsock 库
#define PORT 12345
#define BUFFER_SIZE 1024
int main() {
WSADATA wsaData;
SOCKET serverSocket, clientSocket;
sockaddr_in serverAddr, clientAddr;
char buffer[BUFFER_SIZE];
// 初始化 Winsock
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
std::cerr << "WSAStartup failed.\n";
return 1;
}
// 创建套接字
serverSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (serverSocket == INVALID_SOCKET) {
std::cerr << "Socket creation failed.\n";
WSACleanup();
return 1;
}
// 设置服务器地址和端口
serverAddr.sin_family = AF_INET;
serverAddr.sin_addr.s_addr = INADDR_ANY; // 监听所有 IP 地址
serverAddr.sin_port = htons(PORT);
// 绑定套接字
if (bind(serverSocket, (sockaddr*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR) {
std::cerr << "Bind failed.\n";
closesocket(serverSocket);
WSACleanup();
return 1;
}
// 开始监听
if (listen(serverSocket, 5) == SOCKET_ERROR) {
std::cerr << "Listen failed.\n";
closesocket(serverSocket);
WSACleanup();
return 1;
}
std::cout << "Server is listening on port " << PORT << "...\n";
// 接受客户端连接
int clientAddrSize = sizeof(clientAddr);
clientSocket = accept(serverSocket, (sockaddr*)&clientAddr, &clientAddrSize);
if (clientSocket == INVALID_SOCKET) {
std::cerr << "Accept failed.\n";
closesocket(serverSocket);
WSACleanup();
return 1;
}
std::cout << "Client connected.\n";
// 通信
while (true) {
memset(buffer, 0, BUFFER_SIZE);
int bytesReceived = recv(clientSocket, buffer, BUFFER_SIZE, 0);
if (bytesReceived <= 0) {
std::cout << "Connection closed by client.\n";
break;
}
std::cout << "Client: " << buffer << "\n";
std::string reply;
std::cout << "Server: ";
std::getline(std::cin, reply);
send(clientSocket, reply.c_str(), reply.length(), 0);
}
// 关闭套接字
closesocket(clientSocket);
closesocket(serverSocket);
WSACleanup();
return 0;
}
2、客户端(client.cpp)
#include <iostream>
#include <winsock2.h>
#include <string>
#pragma comment(lib, "ws2_32.lib") // 链接 Winsock 库
#define SERVER_IP "127.0.0.1" // 服务器 IP 地址
#define PORT 12345
#define BUFFER_SIZE 1024
int main() {
WSADATA wsaData;
SOCKET clientSocket;
sockaddr_in serverAddr;
char buffer[BUFFER_SIZE];
// 初始化 Winsock
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
std::cerr << "WSAStartup failed.\n";
return 1;
}
// 创建套接字
clientSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (clientSocket == INVALID_SOCKET) {
std::cerr << "Socket creation failed.\n";
WSACleanup();
return 1;
}
// 设置服务器地址和端口
serverAddr.sin_family = AF_INET;
serverAddr.sin_addr.s_addr = inet_addr(SERVER_IP);
serverAddr.sin_port = htons(PORT);
// 连接到服务器
if (connect(clientSocket, (sockaddr*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR) {
std::cerr << "Connection to server failed.\n";
closesocket(clientSocket);
WSACleanup();
return 1;
}
std::cout << "Connected to server.\n";
// 通信
while (true) {
std::string message;
std::cout << "Client: ";
std::getline(std::cin, message);
send(clientSocket, message.c_str(), message.length(), 0);
memset(buffer, 0, BUFFER_SIZE);
int bytesReceived = recv(clientSocket, buffer, BUFFER_SIZE, 0);
if (bytesReceived <= 0) {
std::cout << "Connection closed by server.\n";
break;
}
std::cout << "Server: " << buffer << "\n";
}
// 关闭套接字
closesocket(clientSocket);
WSACleanup();
return 0;
}
注意!!
某些编译器会认为client中的引用链接库错误,给出警告:
点击编译器中的调试,再点击调试属性
进入页面后
点击c/c++,将右侧的SDL 检查 改为否,禁止弹出警告,即可无报错运行
理论上现在你就可以直接运行了,如果觉得还不错,麻烦各位看官老爷点个赞呗🙏🙏