实现2个客户端之间互相聊天
服务器要求:使用 select 模型实现接受多个客户端连接,以及转发消息
客户端要求:使用 poll 模型解决 技能够 read 读取服务器发来的消息,又能够scanf读取键盘输入的信息 客户端服务器不允许开启额外线程和进程
服务器代码:
#include
#include
#include
#include
#include
#include
#include
// 最大客户端数量
#define MAX_CLIENTS 10
// 缓冲区大小
#define BUF_SIZE 1024
// 向客户端数组中插入新客户端
void insert_client(int client_arr[], int *len, int new_fd) {
for (int i = 0; i < MAX_CLIENTS; i++) {
if (client_arr[i] == -1) {
client_arr[i] = new_fd;
(*len)++;
break;
}
}
}
// 从客户端数组中移除客户端
void remove_client(int client_arr[], int *len, int target_fd) {
for (int i = 0; i < MAX_CLIENTS; i++) {
if (client_arr[i] == target_fd) {
client_arr[i] = -1;
for (int j = i; j < MAX_CLIENTS - 1; j++) {
client_arr[j] = client_arr[j + 1];
}
(*len)--;
break;
}
}
}
int main(int argc, const char *argv[]) {
// 检查参数
if (argc < 2) {
printf("请输入: %s 端口号\n", argv[0]);
return 1;
}
short port = atoi(argv[1]);
// 1. 创建服务器套接字
int server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd == -1) {
perror("socket error");
return 1;
}
// 2. 绑定地址
struct sockaddr_in server_addr = {0};
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(port);
server_addr.sin_addr.s_addr = inet_addr{"0.0.0.0"};
if (bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
perror("bind error");
close(server_fd);
return 1;
}
// 3. 监听连接
if (listen(server_fd, 10) == -1) {
perror("listen error");
close(server_fd);
return 1;
}
printf("服务器启动,端口: %d,等待客户端连接...\n", port);
// select 相关变量
fd_set read_fds, temp_fds;
FD_ZERO(&read_fds);
FD_SET(server_fd, &read_fds);
FD_SET(STDIN_FILENO, &read_fds);
int max_fd = server_fd;
// 客户端管理数组
int client_fds[MAX_CLIENTS];
for (int i = 0; i < MAX_CLIENTS; i++) {
client_fds[i] = -1;
}
int client_count = 0;
while (1) {
// 每次 select 前复制集合(select 会修改集合)
temp_fds = read_fds;
// 4. 等待事件发生
int activity = select(max_fd + 1, &temp_fds, NULL, NULL, NULL);
if (activity == -1) {
perror("select error");
break;
}
// 5. 处理服务器套接字(新客户端连接)
if (FD_ISSET(server_fd, &temp_fds)) {
struct sockaddr_in client_addr;
socklen_t client_addr_len = sizeof(client_addr);
int client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &client_addr_len);
if (client_fd == -1) {
perror("accept error");
continue;
}
printf("客户端连接: %s:%d\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
// 将新客户端加入监听集合和管理数组
FD_SET(client_fd, &read_fds);
if (client_fd > max_fd) {
max_fd = client_fd;
}
insert_client(client_fds, &client_count, client_fd);
}
// 处理标准输入(服务器手动发消息,可选逻辑)
else if (FD_ISSET(STDIN_FILENO, &temp_fds)) {
char buf[BUF_SIZE] = {0};
if (fgets(buf, BUF_SIZE, stdin) != NULL) {
// 去掉换行符
buf[strcspn(buf, "\n")] = '\0';
}
}
// 处理客户端消息
else {
for (int i = 0; i < client_count; i++) {
int curr_fd = client_fds[i];
if (curr_fd == -1) continue;
if (FD_ISSET(curr_fd, &temp_fds)) {
char buf[BUF_SIZE] = {0};
int len = recv(curr_fd, buf, BUF_SIZE, 0);
// 客户端断开
if (len <= 0) {
printf("客户端断开: %d\n", curr_fd);
FD_CLR(curr_fd, &read_fds);
close(curr_fd);
remove_client(client_fds, &client_count, curr_fd);
continue;
}
// 简单转发:发给其他所有客户端
printf("收到客户端消息: %s (来自 fd: %d)\n", buf, curr_fd);
for (int j = 0; j < client_count; j++) {
int target_fd = client_fds[j];
if (target_fd != -1 && target_fd != curr_fd) {
send(target_fd, buf, len, 0);
}
}
}
}
}
}
// 关闭资源
for (int i = 0; i < client_count; i++) {
close(client_fds[i]);
}
close(server_fd);
return 0;
}
客户端代码:
#include
#include
#include
#include
#include
#include
#include
#include
// 缓冲区大小
#define BUF_SIZE 1024
int main(int argc, const char *argv[]) {
// 检查参数
if (argc < 3) {
printf("请输入: %s 服务器IP 端口号\n", argv[0]);
return 1;
}
const char *server_ip = argv[1];
short port = atoi(argv[2]);
// 1. 创建客户端套接字
int sock_fd = socket(AF_INET, SOCK_STREAM, 0);
if (sock_fd == -1) {
perror("socket error");
return 1;
}
// 2. 连接服务器
struct sockaddr_in server_addr = {0};
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(port);
if (inet_pton(AF_INET, server_ip, &server_addr.sin_addr) <= 0) {
perror("inet_pton error");
close(sock_fd);
return 1;
}
if (connect(sock_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
perror("connect error");
close(sock_fd);
return 1;
}
printf("已连接服务器: %s:%d\n", server_ip, port);
// poll 相关设置
struct pollfd fds[2];
// 监听套接字
fds[0].fd = sock_fd;
fds[0].events = POLLIN;
fds[0].revents = 0;
// 监听标准输入
fds[1].fd = STDIN_FILENO;
fds[1].events = POLLIN;
fds[1].revents = 0;
while (1) {
// 3. 等待事件(套接字或键盘输入)
int ret = poll(fds, 2, -1);
if (ret == -1) {
perror("poll error");
break;
}
// 4. 处理套接字消息(服务器发来的数据)
if (fds[0].revents & POLLIN) {
char buf[BUF_SIZE] = {0};
int len = recv(sock_fd, buf, BUF_SIZE, 0);
// 服务器断开
if (len <= 0) {
printf("服务器断开连接\n");
close(sock_fd);
return 0;
}
printf("服务器说: %s\n", buf);
}
// 5. 处理标准输入(客户端发消息给服务器)
if (fds[1].revents & POLLIN) {
char buf[BUF_SIZE] = {0};
if (fgets(buf, BUF_SIZE, stdin) != NULL) {
// 去掉换行符
buf[strcspn(buf, "\n")] = '\0';
// 发送给服务器
send(sock_fd, buf, strlen(buf), 0);
}
}
}
// 关闭套接字
close(sock_fd);
return 0;
}
最终效果: