纯C语言实现网络聊天室的核心是基于TCP/IP协议的套接字(Socket)编程,仅使用标准C库和操作系统提供的底层Socket接口(如Linux的 sys/socket.h )。以下是实现的核心思路、关键模块及简化代码示例。
一、核心原理与架构
网络聊天室本质是“服务器-客户端”模型,核心依赖Socket通信和多线程/多进程处理并发连接。
- 服务器:绑定固定IP和端口,监听客户端连接,为每个客户端创建独立线程处理消息收发,并将消息转发给所有在线客户端(群聊逻辑)。
- 客户端:连接服务器,创建两个线程(一个收消息、一个发消息),实现“边发边收”的实时交互。
关键概念:
- Socket:网络通信的“端点”,类似电话的“听筒+话筒”,用于收发数据。
- TCP协议:可靠的面向连接协议,确保消息不丢失、不混乱,适合聊天室场景。
- 多线程:服务器用主线程监听连接,子线程处理单个客户端;客户端用线程分离“收”和“发”的逻辑。
二、环境依赖
仅支持Linux/macOS(依赖POSIX标准的Socket接口和线程库 pthread ),Windows需用Winsock但题目禁止,故不支持。编译时需链接线程库(加 -pthread 参数)。
三、核心模块实现(简化代码)
1. 公共头文件(common.h)
定义常量和函数声明,统一服务器IP、端口等配置。
#ifndef COMMON_H
#define COMMON_H
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
#define SERVER_IP "127.0.0.1" // 服务器IP(本地测试用)
#define SERVER_PORT 8888 // 服务器端口
#define BUF_SIZE 1024 // 消息缓冲区大小
// 服务器:客户端信息结构体(含Socket和线程ID)
typedef struct {
int sockfd;
pthread_t tid;
} ClientInfo;
// 服务器:全局客户端数组(简化版,实际需动态管理)
ClientInfo clients[1024];
int client_count = 0;
pthread_mutex_t mutex; // 保护客户端数组的互斥锁
// 函数声明
void *server_handle_client(void *arg); // 服务器处理单个客户端
void *client_recv_msg(void *arg); // 客户端接收消息
void client_send_msg(int sockfd); // 客户端发送消息
#endif
2. 服务器端(server.c)
核心逻辑:创建Socket → 绑定端口 → 监听连接 → 接受客户端并创建线程处理。
#include "common.h"
// 转发消息给所有在线客户端(群聊核心)
void broadcast_msg(int sender_fd, const char *msg) {
pthread_mutex_lock(&mutex); // 加锁,避免并发修改客户端数组
for (int i = 0; i < client_count; i++) {
if (clients[i].sockfd != sender_fd) { // 不发给自己
send(clients[i].sockfd, msg, strlen(msg), 0);
}
}
pthread_mutex_unlock(&mutex);
}
// 处理单个客户端的消息(子线程函数)
void *server_handle_client(void *arg) {
int sockfd = *(int *)arg;
char buf[BUF_SIZE];
char msg[BUF_SIZE + 32]; // 附加发送者标识
int n;
// 1. 接收客户端的用户名
memset(buf, 0, BUF_SIZE);
if ((n = recv(sockfd, buf, BUF_SIZE, 0)) <= 0) {
printf("Client disconnected (no username)\n");
close(sockfd);
free(arg);
return NULL;
}
char username[64];
strncpy(username, buf, sizeof(username)-1);
printf("New client connected: %s (sockfd: %d)\n", username, sockfd);
// 2. 循环接收消息并转发
while (1) {
memset(buf, 0, BUF_SIZE);
n = recv(sockfd, buf, BUF_SIZE, 0);
if (n <= 0) { // 客户端断开
printf("Client disconnected: %s\n", username);
break;
}
// 拼接消息格式:[用户名] 消息内容
snprintf(msg, sizeof(msg), "[%s] %s", username, buf);
printf("%s", msg); // 服务器打印日志
broadcast_msg(sockfd, msg); // 转发给所有人
}
// 3. 清理:从客户端数组移除(简化版,实际需遍历删除)
pthread_mutex_lock(&mutex);
for (int i = 0; i < client_count; i++) {
if (clients[i].sockfd == sockfd) {
clients[i] = clients[--client_count]; // 用最后一个覆盖
break;
}
}
pthread_mutex_unlock(&mutex);
close(sockfd);
free(arg);
return NULL;
}
int main() {
int server_fd, *client_sockfd;
struct sockaddr_in server_addr, client_addr;
socklen_t client_addr_len = sizeof(client_addr);
// 1. 初始化互斥锁
pthread_mutex_init(&mutex, NULL);
// 2. 创建TCP Socket(AF_INET:IPv4,SOCK_STREAM:TCP,0:默认协议)
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
perror("socket create failed");
exit(1);
}
// 3. 设置Socket选项(允许端口复用,避免重启服务器报错)
int opt = 1;
setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
// 4. 绑定IP和端口
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 监听所有网卡
server_addr.sin_port = htons(SERVER_PORT); // 端口转换为网络字节序
if (bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
perror("bind failed");
close(server_fd);
exit(1);
}
// 5. 开始监听(backlog=10:最多缓存10个未处理连接)
if (listen(server_fd, 10) == -1) {
perror("listen failed");
close(server_fd);
exit(1);
}
printf("Server started, listening on %s:%d...\n", SERVER_IP, SERVER_PORT);
// 6. 循环接受客户端连接
while (1) {
// 接受连接,返回客户端专属Socket
client_sockfd = malloc(sizeof(int));
*client_sockfd = accept(server_fd, (struct sockaddr *)&client_addr, &client_addr_len);
if (*client_sockfd == -1) {
perror("accept failed");
free(client_sockfd);
continue;
}
// 7. 将客户端加入数组(加锁保护)
pthread_mutex_lock(&mutex);
clients[client_count++].sockfd = *client_sockfd;
pthread_mutex_unlock(&mutex);
// 8. 创建线程处理该客户端
pthread_t tid;
if (pthread_create(&tid, NULL, server_handle_client, (void *)client_sockfd) != 0) {
perror("pthread create failed");
close(*client_sockfd);
free(client_sockfd);
pthread_mutex_lock(&mutex);
client_count--;
pthread_mutex_unlock(&mutex);
}
// 分离线程(无需主线程等待,线程结束后自动释放资源)
pthread_detach(tid);
}
// 理论上不会执行到这里
pthread_mutex_destroy(&mutex);
close(server_fd);
return 0;
}
3. 客户端(client.c)
核心逻辑:创建Socket → 连接服务器 → 输入用户名 → 启动收消息线程 → 循环发送消息。
#include "common.h"
// 接收服务器消息的线程(后台运行,实时显示)
void *client_recv_msg(void *arg) {
int sockfd = *(int *)arg;
char buf[BUF_SIZE];
int n;
while (1) {
memset(buf, 0, BUF_SIZE);
n = recv(sockfd, buf, BUF_SIZE, 0);
if (n <= 0) { // 服务器断开或出错
printf("\nServer disconnected, exit.\n");
exit(0);
}
printf("%s", buf); // 直接打印收到的消息(含其他用户的发言)
}
}
// 发送消息到服务器(主线程处理,读取用户输入)
void client_send_msg(int sockfd) {
char buf[BUF_SIZE];
while (1) {
memset(buf, 0, BUF_SIZE);
// 读取用户输入(fgets会保留换行符,无需额外加)
if (fgets(buf, BUF_SIZE, stdin) == NULL) {
printf("Input error, exit.\n");
break;
}
// 发送消息到服务器
if (send(sockfd, buf, strlen(buf), 0) == -1) {
perror("send failed");
break;
}
}
}
int main() {
int sockfd;
struct sockaddr_in server_addr;
char username[64];
// 1. 创建TCP Socket
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
perror("socket create failed");
exit(1);
}
// 2. 填写服务器地址信息
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = inet_addr(SERVER_IP); // 服务器IP转换
server_addr.sin_port = htons(SERVER_PORT); // 服务器端口
// 3. 连接服务器
if (connect(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
perror("connect failed (server not running?)");
close(sockfd);
exit(1);
}
printf("Connected to server %s:%d\n", SERVER_IP, SERVER_PORT);
// 4. 输入用户名并发送给服务器
printf("Please enter your username: ");
if (fgets(username, sizeof(username), stdin) == NULL) {
printf("Invalid username.\n");
close(sockfd);
exit(1);
}
// 去掉用户名后的换行符(fgets会读入)
username[strcspn(username, "\n")] = '\0';
send(sockfd, username, strlen(username), 0);
printf("Welcome %s! Type your message (enter to send):\n", username);
// 5. 创建线程接收服务器消息(后台运行)
pthread_t tid;
if (pthread_create(&tid, NULL, client_recv_msg, (void *)&sockfd) != 0) {
perror("pthread create failed");
close(sockfd);
exit(1);
}
pthread_detach(tid); // 分离线程
// 6. 主线程处理发送消息
client_send_msg(sockfd);
// 清理
close(sockfd);
return 0;
}
四、编译与运行
1. 编译服务器: gcc server.c -o server -pthread
2. 编译客户端: gcc client.c -o client -pthread
3. 运行:
- 先启动服务器: ./server
- 再启动多个客户端(新终端): ./client
- 输入用户名后即可群聊,消息会同步给所有在线客户端。
五、简化说明与优化方向
简化点:
- 客户端数组用固定大小(1024),实际需用链表/动态数组+互斥锁管理。
- 无异常处理(如用户强制退出、网络中断),无私聊功能。
- 未做消息长度校验,超过 BUF_SIZE 会截断。
优化方向:
1. 用I/O多路复用( select / poll / epoll )替代多线程,减少资源消耗。
2. 实现客户端动态管理(链表+互斥锁/信号量)。
3. 增加私聊(指定用户名发送)、消息历史、在线用户列表等功能。
4. 加入消息校验(如结尾标识),避免粘包问题。
1449

被折叠的 条评论
为什么被折叠?



