如何用C语言Socket实现多客户端接入及消息互看的CLI通信程序
多客户端CLI即时通信改造方案
我正在用C语言开发一个简单的CLI即时通信程序,已经基于Socket实现了单客户端向服务器发消息、服务器显示消息的功能,但这个程序只能接入一个客户端。我想改造它,让它支持多客户端接入,并且客户端能看到其他客户端发送的消息。以下是我现有的server.c和client.c代码:
现有server.c代码
/* server.c */ #include <stdio.h> #include <unistd.h> #include <strings.h> #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <arpa/inet.h> #define MAX 256 #define PORT 8080 int main() { int socket_return, accept_return, bind_return, listen_return; struct sockaddr_in server_address, peer_address; // 创建socket socket_return = socket(AF_INET, SOCK_STREAM, 0); if (socket_return == -1) { perror("socket creation failed"); return -1; } else { printf("socket created\n"); } // 清空服务器地址 bzero(&server_address, sizeof(server_address)); server_address.sin_family = AF_INET; server_address.sin_addr.s_addr = htonl(INADDR_ANY); server_address.sin_port = htons(PORT); // 绑定socket bind_return = bind(socket_return, (struct sockaddr *)&server_address, sizeof(server_address)); if (bind_return == -1) { perror("binding failed"); close(socket_return); return -1; } else { printf("binding done\n"); } // 监听socket listen_return = listen(socket_return, 5); if (listen_return == -1) { perror("listening failed"); close(socket_return); return -1; } else { printf("server is listening...\n"); } socklen_t peer_address_len = sizeof(peer_address); // 接受连接 accept_return = accept(socket_return, (struct sockaddr *)&peer_address, &peer_address_len); if (accept_return == -1) { perror("accepting failed"); close(socket_return); return -1; } else { printf("connection accepted\n"); } char msg[MAX]; while (1) { bzero(msg, sizeof(msg)); // 读取消息 ssize_t bytes_read = read(accept_return, msg, sizeof(msg)); if (bytes_read == 0) { printf("client disconnected\n"); break; } else if (bytes_read < 0) { perror("failed to read message"); break; } // 显示客户端消息 printf("From client: %s", msg); } // 关闭连接 close(accept_return); close(socket_return); return 0; }
现有client.c代码
/* client.c */ #include <stdio.h> #include <strings.h> #include <unistd.h> #include <string.h> #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <arpa/inet.h> #define MAX 256 #define PORT 8080 int main() { int socket_return, connect_return; struct sockaddr_in server_address; // 创建socket socket_return = socket(AF_INET, SOCK_STREAM, 0); if (socket_return == -1) { printf("socket creation failed\n"); return -1; } else { printf("socket created\n"); } bzero(&server_address, sizeof(server_address)); server_address.sin_family = AF_INET; server_address.sin_addr.s_addr = inet_addr("127.0.0.1"); server_address.sin_port = htons(PORT); // 连接服务器 connect_return = connect(socket_return, (struct sockaddr *)&server_address, sizeof(server_address)); if (connect_return == -1) { printf("connection failed\n"); close(socket_return); return -1; } else { printf("connected to server\n"); } char msg[MAX]; int i; while (1) { bzero(msg, sizeof(msg)); printf("Enter string: "); i = 0; while ((msg[i++] = getchar()) != '\n'); // 当输入exit时退出客户端 if ((strncmp(msg, "exit", 4)) == 0) { printf("Exiting client\n"); close(socket_return); break; } // 发送消息 write(socket_return, msg, sizeof(msg)); printf("You wrote %s\n",msg); } // 关闭连接 close(socket_return); return 0; }
改造方案实现
核心思路
要支持多客户端,服务器需要同时处理多个连接,这里采用多线程方案(实现简单、适合小规模场景),同时维护客户端连接列表实现消息转发;客户端需要新增接收线程,同时处理输入和消息接收。
改造后server.c代码
#include <stdio.h> #include <unistd.h> #include <strings.h> #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <arpa/inet.h> #include <pthread.h> #include <stdlib.h> #define MAX 256 #define PORT 8080 #define MAX_CLIENTS 100 // 存储客户端socket的全局列表 int client_sockets[MAX_CLIENTS]; // 保证列表线程安全的互斥锁 pthread_mutex_t client_mutex = PTHREAD_MUTEX_INITIALIZER; // 将客户端socket加入列表 void add_client(int sock) { pthread_mutex_lock(&client_mutex); for (int i = 0; i < MAX_CLIENTS; i++) { if (client_sockets[i] == 0) { client_sockets[i] = sock; break; } } pthread_mutex_unlock(&client_mutex); } // 从列表移除客户端socket void remove_client(int sock) { pthread_mutex_lock(&client_mutex); for (int i = 0; i < MAX_CLIENTS; i++) { if (client_sockets[i] == sock) { client_sockets[i] = 0; break; } } pthread_mutex_unlock(&client_mutex); } // 转发消息给所有其他客户端 void broadcast_message(int sender_sock, char *msg, ssize_t len) { pthread_mutex_lock(&client_mutex); for (int i = 0; i < MAX_CLIENTS; i++) { if (client_sockets[i] != 0 && client_sockets[i] != sender_sock) { write(client_sockets[i], msg, len); } } pthread_mutex_unlock(&client_mutex); } // 线程函数:处理单个客户端的消息读写 void *handle_client(void *arg) { int client_sock = *(int *)arg; free(arg); char msg[MAX]; ssize_t bytes_read; while (1) { bzero(msg, sizeof(msg)); bytes_read = read(client_sock, msg, sizeof(msg)-1); // 预留终止符空间 if (bytes_read <= 0) { printf("客户端断开连接\n"); remove_client(client_sock); close(client_sock); pthread_exit(NULL); } // 服务器打印收到的消息 printf("来自客户端: %s", msg); // 转发消息给其他客户端 broadcast_message(client_sock, msg, bytes_read); } } int main() { int server_sock, bind_return, listen_return; struct sockaddr_in server_address, peer_address; // 初始化客户端列表为0(0表示未使用) bzero(client_sockets, sizeof(client_sockets)); // 创建服务器socket server_sock = socket(AF_INET, SOCK_STREAM, 0); if (server_sock == -1) { perror("socket创建失败"); return -1; } printf("socket创建成功\n"); bzero(&server_address, sizeof(server_address)); server_address.sin_family = AF_INET; server_address.sin_addr.s_addr = htonl(INADDR_ANY); server_address.sin_port = htons(PORT); // 绑定socket bind_return = bind(server_sock, (struct sockaddr *)&server_address, sizeof(server_address)); if (bind_return == -1) { perror("绑定失败"); close(server_sock); return -1; } printf("绑定完成\n"); // 监听socket listen_return = listen(server_sock, 5); if (listen_return == -1) { perror("监听失败"); close(server_sock); return -1; } printf("服务器正在监听...\n"); // 循环接受新连接 while (1) { socklen_t peer_address_len = sizeof(peer_address); int *client_sock = malloc(sizeof(int)); *client_sock = accept(server_sock, (struct sockaddr *)&peer_address, &peer_address_len); if (*client_sock == -1) { perror("接受连接失败"); free(client_sock); continue; } printf("接受新连接,客户端IP: %s\n", inet_ntoa(peer_address.sin_addr)); add_client(*client_sock); // 创建线程处理该客户端 pthread_t thread_id; if (pthread_create(&thread_id, NULL, handle_client, client_sock) != 0) { perror("创建线程失败"); close(*client_sock); remove_client(*client_sock); free(client_sock); } pthread_detach(thread_id); // 分离线程,避免资源泄漏 } close(server_sock); return 0; }
改造后client.c代码
#include <stdio.h> #include <strings.h> #include <unistd.h> #include <string.h> #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <arpa/inet.h> #include <pthread.h> #include <stdlib.h> #define MAX 256 #define PORT 8080 int client_sock; // 线程函数:接收服务器转发的消息 void *receive_messages(void *arg) { char msg[MAX]; ssize_t bytes_read; while (1) { bzero(msg, sizeof(msg)); bytes_read = read(client_sock, msg, sizeof(msg)-1); if (bytes_read <= 0) { printf("\n服务器断开连接,即将退出\n"); close(client_sock); exit(EXIT_SUCCESS); } // 打印收到的消息,同时刷新输出缓冲区保证输入提示正常显示 printf("\n收到消息: %s输入内容: ", msg); fflush(stdout); } } int main() { int connect_return; struct sockaddr_in server_address; // 创建客户端socket client_sock = socket(AF_INET, SOCK_STREAM, 0); if (client_sock == -1) { printf("socket创建失败\n"); return -1; } printf("socket创建成功\n"); bzero(&server_address, sizeof(server_address)); server_address.sin_family = AF_INET; server_address.sin_addr.s_addr = inet_addr("127.0.0.1"); server_address.sin_port = htons(PORT); // 连接服务器 connect_return = connect(client_sock, (struct sockaddr *)&server_address, sizeof(server_address)); if (connect_return == -1) { printf("连接服务器失败\n"); close(client_sock); return -1; } printf("已连接到服务器\n"); printf("输入内容: "); fflush(stdout); // 创建接收消息的线程 pthread_t thread_id; if (pthread_create(&thread_id, NULL, receive_messages, NULL) != 0) { perror("创建接收线程失败"); close(client_sock); return -1; } pthread_detach(thread_id); char msg[MAX]; int i; while (1) { bzero(msg, sizeof(msg)); i = 0; while ((msg[i++] = getchar()) != '\n'); if (strncmp(msg, "exit", 4) == 0) { printf("正在退出客户端\n"); close(client_sock); exit(EXIT_SUCCESS); } write(client_sock, msg, strlen(msg)); printf("你发送了: %s输入内容: ", msg); fflush(stdout); } close(client_sock); return 0; }
编译与运行说明
- 编译服务器:
gcc server.c -o server -lpthread - 编译客户端:
gcc client.c -o client -lpthread - 启动服务器:
./server - 启动多个客户端:在不同终端窗口运行
./client
关键注意事项
- 线程安全:客户端列表的增删操作必须加互斥锁,避免多线程冲突
- 资源清理:客户端断开时要及时从列表移除并关闭socket,线程需分离避免内存泄漏
- IO处理:客户端接收消息后要刷新输出缓冲区,保证输入提示正常显示
内容的提问来源于stack exchange,提问作者IllTime00qw
相关产品推荐
相关产品推荐

