本文介绍如何使用C++编写一个基本的客户端-服务端通信系统。通过这个例子,你将学到如何建立TCP连接、发送和接收消息,以及如何处理多个客户端连接。
客户端代码:
#include <stdio.h> // 标准输入输出库,提供基本的输入输出功能
#include <stdlib.h> // 标准库,包含了一些通用的函数和动态内存分配函数
#include <string.h> // 字符串处理库,提供字符串操作的各种函数
#include <unistd.h> // Linux系统调用接口,包含了一些常用的系统调用函数
#include <arpa/inet.h> // 提供了一些函数,用于对IPv4和IPv6地址进行转换
#include <errno.h> // 用于获取错误码,提供 perror 函数来输出错误信息
int main(int argc, char *argv[]) {
if (argc != 4) {
fprintf(stderr, "Usage: %s <server_ip> <server_port> <message>\n", argv[0]);
return EXIT_FAILURE;
}
int sockfd;
struct sockaddr_in servaddr;
// 创建套接字
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
perror("create socket error");
return EXIT_FAILURE;
}
// 初始化服务器地址结构体
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(atoi(argv[2])); // 将端口号从字符串转换为整数
// 将IP地址从字符串转换为网络地址
if (inet_pton(AF_INET, argv[1], &servaddr.sin_addr) <= 0) {
perror("inet_pton error");
close(sockfd);
return EXIT_FAILURE;
}
// 发起连接请求
if (connect(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0) {
perror("connect error");
close(sockfd);
return EXIT_FAILURE;
}
printf("Connected to the server. Sending message: %s\n", argv[3]);
// 发送消息到服务器
if (send(sockfd, argv[3], strlen(argv[3]), 0) < 0) {
perror("send error");
close(sockfd);
return EXIT_FAILURE;
}
// 接收服务器的响应
char buffer[1024];
ssize_t recv_len = recv(sockfd, buffer, sizeof(buffer), 0);
if (recv_len < 0) {
perror("recv error");
close(sockfd);
return EXIT_FAILURE;
} else if (recv_len == 0) {
printf("Connection closed by the server\n");
} else {
buffer[recv_len] = '\0';
printf("Received from server: %s\n", buffer);
}
// 关闭套接字
close(sockfd);
return EXIT_SUCCESS;
}
服务端代码:
#include <iostream> // 输入输出流库,提供了输入输出的各种功能
#include <cstring> // 字符串处理库,提供了字符串操作的各种函数
#include <thread> // 多线程支持库,用于创建和管理线程
#include <vector> // 动态数组容器,提供了对动态数组的支持
#include <mutex> // 互斥锁库,提供了对互斥锁的支持
#include <queue> // 队列容器,提供了对队列的支持
#include <netinet/in.h> // 网络编程库,包含了与网络相关的数据结构和函数
#include <unistd.h> // Linux系统调用接口,包含了一些常用的系统调用函数
const int MAXBUFF = 1024;
std::mutex g_mx; // 用于保护共享资源的互斥锁
std::queue<std::pair<int, std::string>> g_dataQue; // 存储客户端套接字和消息的队列
// 在单独的线程中处理从客户端接收到的消息
void writeThread() {
while (true) {
g_mx.lock(); // 上锁以确保安全访问共享资源
if (!g_dataQue.empty()) {
int clientFd = g_dataQue.front().first;
std::string data = g_dataQue.front().second;
std::cout << "从客户端接收到的消息:" << data << std::endl;
// 假设有一个处理消息并返回响应的函数
std::string response = "Server response: 你好,客户端!";
send(clientFd, response.c_str(), response.size(), 0);
g_dataQue.pop(); // 从队列中移除已处理的消息
}
g_mx.unlock(); // 解锁
}
}
int main() {
int listenFd, clientFd;
struct sockaddr_in servaddr;
if ((listenFd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
std::cerr << "创建套接字错误" << std::endl;
return -1;
}
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(1121);
if (bind(listenFd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) {
std::cerr << "绑定套接字地址和端口错误" << std::endl;
return -1;
}
if (listen(listenFd, 10) < 0) {
std::cerr << "开启监听错误" << std::endl;
return -1;
}
std::thread write_thread(writeThread); // 创建一个线程来处理从客户端接收到的消息
size_t readLen = 0;
while (true) {
struct sockaddr_in client_addr;
socklen_t size = sizeof(client_addr);
if ((clientFd = accept(listenFd, (struct sockaddr *)&client_addr, &size)) < 0) {
std::cerr << "建立连接错误" << std::endl;
return -1;
}
// 假设有一个读取函数来从 clientFd 中读取数据
char buff[MAXBUFF] = {0};
readLen = read(clientFd, buff, MAXBUFF);
if (readLen <= 0) {
close(clientFd); // 在读取到数据后关闭客户端连接
break;
}
std::string data(buff, readLen);
g_mx.lock(); // 上锁以确保安全访问共享资源
g_dataQue.push(std::make_pair(clientFd, data)); // 将接收到的消息和客户端套接字放入队列
g_mx.unlock(); // 解锁
}
write_thread.join(); // 等待写线程结束
close(listenFd);
return 0;
}
客户端使用方法:
打开终端,进入客户端代码所在的目录。使用以下命令运行客户端程序:
gcc client.cpp -o client
./client <server_ip> <server_port> <message>
替换 <server_ip>
、<server_port>
和 <message>
分别为服务器的IP地址、端口号和要发送的消息。
服务端使用方法:
打开终端,进入服务端代码所在的目录。使用以下命令编译并运行服务端程序:
g++ server.cpp -o server -lpthread
./server
服务端将开始监听连接。
主要功能:
客户端:
- 接受命令行参数,包括服务器IP、端口号和要发送的消息。
- 创建套接字,连接到服务器。
- 发送消息到服务器,接收并打印服务器的响应。
服务端:
- 创建套接字,绑定地址和端口,开始监听。
- 接受客户端连接,将客户端套接字和消息放入队列。
- 在单独的线程中处理队列中的消息,发送响应到客户端。
注意事项:
- 通过互斥锁保护共享资源,确保线程安全的访问。
- 在服务端中,处理客户端消息后返回一个固定的响应。
结论:
通过这个简单的例子,你学到了如何使用C++创建一个基本的客户端-服务端通信系统。这是一个基础框架,可以根据实际需求进行扩展和改进,用于构建更复杂的网络应用。