实验四:网络编程
文章目录
- 1. 实验四:网络编程
- 2. 客户端函数
- 2.1 `void *receive_message(void *arg)`
- 2.2 `void *send_message(void *arg)`
- 2.3 `int main(int argc, char *argv[])`
- 3. 服务端函数
- 3.1 `remove_client`
- 3.2 `send_private_message`
- 3.3 `handle_command`
- 3.4 `broadcast`
- 3.5 `client_thread`
- 3.6 `server_input_thread`
- 3.7 `main`
- 4. 运行结果及分析
- 4.1 服务端
- 4.2 客户端
- 4.2.1 客户端1:小林
- 4.2.1 客户端2:小红
- 4.2.1 客户端3:小蓝
- 4.3 结果分析
- 4.3.1 连接与初始化阶段
- (1)服务端输出:
- (2)客户端输出:
- (3)分析:
- 4.3.2 广播消息
- (1)发送与接收:
- 4.3.3 服务器广播
- (1)服务端手动输入:
- (2)客户端响应:
- (3)分析:
- 4.3.4 私聊功能
- (1)客户端3 小蓝输入:
- (2)服务端日志:
- (3)客户端2 小红收到:
- (4)分析:
- 5. 挑战性任务(实现简易版QQ功能,即客户端与客户端之间聊天,服务器只是作为中转)
- 5.1 服务器能够接收来自多个不同客户端的连接
- 5.1.1 关键代码片段:接收多个客户端连接并创建线程
- 5.1.2 功能说明:
- 5.2 私聊功能
- 5.2.1 命令识别入口:`handle_command()` 函数
- 5.2.2 私聊核心处理函数:`send_private_message()`
- 5.2.3 命令调用位置:在 `client_thread()` 中调用 `handle_command()`
- 5.3 服务器广播功能
- 5.3.1 广播函数定义:`broadcast()`
- 5.3.2 具体说明
- 6. 实验过程中遇到的问题及解决方法
- 6.1 终端中文编码错误
- 7. 附完整源代码
- 7.1 客户端源代码
- 7.2 服务端源代码
1. 实验四:网络编程
2. 客户端函数
2.1 void *receive_message(void *arg)
作用:从服务器接收消息并打印,根据消息类型进行格式化处理(如私聊、断开提示等)。
关键逻辑:
while ((read_size = recv(sock, buffer, BUFFER_SIZE, 0)) > 0) {buffer[read_size] = '\0';if (strncmp(buffer, "私聊来自", 12) == 0) {// 私聊格式化char *colon_pos = strchr(buffer, ':');if (colon_pos) {strncpy(sender_id, buffer, colon_pos - buffer);printf("%s: %s\n", sender_id, colon_pos + 1);}} else if (strcmp(buffer, "quit") == 0) {printf("您已断开连接。\n");break;} else {printf("%s\n", buffer);}
}
2.2 void *send_message(void *arg)
作用:从用户输入读取消息并发送到服务器,区分公聊、私聊、退出指令。
关键逻辑:
fgets(message, BUFFER_SIZE, stdin); // 读取输入
message[len - 1] = '\0'; // 去除换行if (strncmp(message, "/private", 8) == 0) {send(sock, message, strlen(message), 0); // 私聊命令
} else if (strcmp(message, "quit") == 0) {send(sock, message, strlen(message), 0); // 退出命令break;
} else {snprintf(full_message, sizeof(full_message), "%s: %s", username, message);send(sock, full_message, strlen(full_message), 0); // 普通消息
}
2.3 int main(int argc, char *argv[])
作用:程序入口,连接服务器,初始化用户名,创建接收与发送线程,等待线程结束并关闭连接。
关键逻辑:
(1)创建并连接 socket:
client_socket = socket(AF_INET, SOCK_STREAM, 0);
connect(client_socket, (struct sockaddr *)&server_addr, sizeof(server_addr));
(2)输入用户名并发送:
fgets(username, sizeof(username), stdin);
username[username_len - 1] = '\0';
snprintf(username_message, sizeof(username_message), "用户名 %s", username);
send(client_socket, username_message, strlen(username_message), 0);
(3)创建两个线程:
pthread_create(&receive_thread_id, NULL, receive_message, (void *)&client_socket);
pthread_create(&send_thread_id, NULL, send_message, (void *)&client_socket);
(4)等待线程并关闭连接:
pthread_join(send_thread_id, NULL);
close(client_socket);
3. 服务端函数
3.1 remove_client
作用:从服务器的客户端数组中移除断开的客户端,并重置群主(如果是群主离线)。
关键代码:
for (int i = 0; i < MAX_CLIENTS; i++) {if (clients[i].id == client_id) {clients[i].id = -1;clients[i].socket = -1;break;}
}
if (owner_id == client_id) {owner_id = -1;
}
3.2 send_private_message
作用:发送私聊消息到指定客户端。
关键代码:
for (int i = 0; i < MAX_CLIENTS; i++) {if (clients[i].id == receiver_id) {receiver_socket = clients[i].socket;break;}
}
snprintf(formatted_message, BUFFER_SIZE, "私聊来自 %d: %s", sender_id, message);
send(receiver_socket, formatted_message, strlen(formatted_message), 0);
3.3 handle_command
作用:解析客户端发来的命令(目前只实现了 /private
)。
关键代码:
if (strncmp(message, "/private", 8) == 0) {int receiver_id;char *msg_start = strchr(message + 9, ' ');if (msg_start && sscanf(message + 9, "%d", &receiver_id) == 1) {msg_start++; // 跳过空格send_private_message(msg_start, receiver_id, client_id);}
}
3.4 broadcast
作用:将普通消息广播给所有客户端(不包括发送者)。
关键代码:
for (int i = 0; i < MAX_CLIENTS; i++) {if (clients[i].socket != -1 && clients[i].socket != sender_socket) {send(clients[i].socket, message, strlen(message), 0);}
}
3.5 client_thread
作用:处理每个客户端的通信,包括初始编号、群主分配、消息收发与断开处理。
关键代码:
snprintf(id_message, sizeof(id_message), "您是客户端编号 %d", client_id);
send(conn, id_message, strlen(id_message), 0);if (client_id == 1) {owner_id = client_id;send(conn, "您是群主", 10, 0);
}while ((read_size = recv(conn, buffer, BUFFER_SIZE, 0)) > 0) {buffer[read_size] = '\0';if (buffer[0] == '/') {handle_command(buffer, conn, client_id);} else {broadcast(buffer, conn);}
}
3.6 server_input_thread
作用:允许服务器手动输入信息并群发公告。
关键代码:
fgets(message, BUFFER_SIZE, stdin);
message[len - 1] = '\0';snprintf(server_message, sizeof(server_message), "服务器公告: %s", message);for (int i = 0; i < MAX_CLIENTS; i++) {if (clients[i].socket != -1) {send(clients[i].socket, server_message, strlen(server_message), 0);}
}
3.7 main
作用:服务器主入口,初始化服务器、监听客户端连接、创建新线程处理每个连接。
关键代码:
(1)创建监听 Socket:
server_socket = socket(AF_INET, SOCK_STREAM, 0);
bind(server_socket, (struct sockaddr *)&server_addr, sizeof(server_addr));
listen(server_socket, 100);
(2)接受客户端并分配线程:
client_socket = accept(server_socket, (struct sockaddr *)&client_addr, &addr_size);clients[slot].socket = client_socket;
clients[slot].id = client_count;pthread_create(&tid, NULL, client_thread, (void *)&clients[slot]);
pthread_detach(tid);
4. 运行结果及分析
4.1 服务端
u202321331023@jmu-cs:~/net_exp$ ./server 30023
聊天室已启动,监听端口: 30023
服务器消息: 客户端 1 已连接,地址: 127.0.0.1:33310
客户端 1: 小林
客户端 2 已连接,地址: 127.0.0.1:60706
客户端 2: 小红
客户端 3 已连接,地址: 127.0.0.1:42474
客户端 3: 小蓝
客户端 1: 小林: Hello everyone!
客户端 2: 小红: 你好1号小林
客户端 3: 小蓝: 大家好
欢迎来到聊天室各位
广播: 服务器公告: 欢迎来到聊天室各位
服务器消息: 私聊: 客户端 3 到 客户端 2: 你好,这是私聊信息
4.2 客户端
4.2.1 客户端1:小林
u202321331023@jmu-cs:~/net_exp$ ./client 127.0.0.1 30023
ӵ 127.0.0.1:30023
û: 小林
您是客户端编号 1 您是群主Hello everyone!
小红: 你好1号小林
小蓝: 大家好服务器公告: 欢迎来到聊天室各位
4.2.1 客户端2:小红
u202321331023@jmu-cs:~/net_exp$ ./client 127.0.0.1 30023
ӵ 127.0.0.1:30023
û: 小红
您是客户端编号 2 小林: Hello everyone!
小蓝: 大家好 服务器公告: 欢迎来到聊天室各位 私聊来自 3: 你好,这是私聊信息
4.2.1 客户端3:小蓝
u202321331023@jmu-cs:~/net_exp$ ./client 127.0.0.1 30023
ӵ 127.0.0.1:30023
û: 小蓝
您是客户端编号 3小林: Hello everyone!
小红: 你好1号小林
大家好服务器公告: 欢迎来到聊天室各位/private 2 你好,这是私聊信息
4.3 结果分析
4.3.1 连接与初始化阶段
(1)服务端输出:
聊天室已启动,监听端口: 30023
客户端 1 已连接,地址: 127.0.0.1:33310
客户端 2 已连接,地址: 127.0.0.1:60706
客户端 3 已连接,地址: 127.0.0.1:42474
(2)客户端输出:
您是客户端编号 1 您是群主
您是客户端编号 2
您是客户端编号 3
(3)分析:
- 服务端监听端口 30023 成功。
- 客户端连接顺序决定了编号:小林是客户端 1,因此被设为群主。
- 每个客户端都成功收到了自己的编号,
client_thread
中的初始化逻辑工作正常。 - 群主识别逻辑也生效,
owner_id = client_id;
触发成功。
4.3.2 广播消息
(1)发送与接收:
小林: Hello everyone!
小红: 你好1号小林
小蓝: 大家好
-
服务端记录:
客户端 1: 小林: Hello everyone! 客户端 2: 小红: 你好1号小林 客户端 3: 小蓝: 大家好
-
客户端显示:
- 每位用户都能看到其他人发的内容(说明广播功能正常)。
- 消息格式也被正常拼接为
用户名: 消息
,符合客户端send_message
函数逻辑。
4.3.3 服务器广播
(1)服务端手动输入:
服务器消息: 欢迎来到聊天室各位
广播: 服务器公告: 欢迎来到聊天室各位
(2)客户端响应:
服务器公告: 欢迎来到聊天室各位
(3)分析:
- 服务端控制台广播成功发送,并能被所有客户端接收。
server_input_thread
中的格式拼接和循环send()
均执行正确。
4.3.4 私聊功能
(1)客户端3 小蓝输入:
/private 2 你好,这是私聊信息
(2)服务端日志:
服务器消息: 私聊: 客户端 3 到 客户端 2: 你好,这是私聊信息
(3)客户端2 小红收到:
私聊来自 3: 你好,这是私聊信息
(4)分析:
- 私聊命令
/private <ID> <消息>
被成功解析。 - 服务端
handle_command
识别/private
并调用send_private_message
。 - 接收端能正确格式化并显示“私聊来自 X: 消息”。
5. 挑战性任务(实现简易版QQ功能,即客户端与客户端之间聊天,服务器只是作为中转)
5.1 服务器能够接收来自多个不同客户端的连接
5.1.1 关键代码片段:接收多个客户端连接并创建线程
while ((client_socket = accept(server_socket, (struct sockaddr *)&client_addr, &addr_size)))
{client_count++;pthread_mutex_lock(&clients_mutex);// 寻找空闲槽位int slot = -1;for (int i = 0; i < MAX_CLIENTS; i++){if (clients[i].socket == -1){slot = i;break;}}if (slot != -1){// 保存客户端信息clients[slot].socket = client_socket;clients[slot].id = client_count;printf("客户端 %d 已连接,地址: %s:%d\n",client_count,inet_ntoa(client_addr.sin_addr),ntohs(client_addr.sin_port));// 创建线程处理客户端if (pthread_create(&tid, NULL, client_thread, (void *)&clients[slot]) < 0){perror("创建线程失败");close(client_socket);clients[slot].socket = -1;clients[slot].id = -1;}else{pthread_detach(tid); // 自动释放资源}}else{printf("客户端连接已达上限,拒绝连接\n");close(client_socket);}pthread_mutex_unlock(&clients_mutex);
}
5.1.2 功能说明:
服务器通过 accept()
接收多个客户端连接,并为每个连接创建一个独立线程 client_thread()
来并发处理,从而实现了多客户端同时接入与通信。
代码位置 | 功能 |
---|---|
accept(...) | 等待并接收新的客户端连接请求 |
clients[slot] | 将新连接保存到客户端数组中 |
pthread_create(...) | 为每个客户端连接创建独立线程,调用 client_thread 处理收发数据 |
pthread_detach(...) | 设置线程为分离状态,避免内存泄漏 |
MAX_CLIENTS 限制 | 控制服务器最多可同时处理的连接数 |
5.2 私聊功能
5.2.1 命令识别入口:handle_command()
函数
服务器检测和处理 /private
私聊命令
void handle_command(char *message, int conn, int client_id)
{if (strncmp(message, "/private", 8) == 0) // 识别私聊命令{int receiver_id;char *msg_start = strchr(message + 9, ' '); // 查找消息正文起始if (msg_start && sscanf(message + 9, "%d", &receiver_id) == 1){msg_start++; // 跳过空格send_private_message(msg_start, receiver_id, client_id); // 调用私聊函数}}
}
5.2.2 私聊核心处理函数:send_private_message()
发送私聊消息给指定客户端的核心实现:
void send_private_message(char *message, int receiver_id, int sender_id)
{pthread_mutex_lock(&clients_mutex);int receiver_socket = -1;for (int i = 0; i < MAX_CLIENTS; i++){if (clients[i].id == receiver_id) // 查找目标ID{receiver_socket = clients[i].socket;break;}}if (receiver_socket != -1){char formatted_message[BUFFER_SIZE];snprintf(formatted_message, BUFFER_SIZE, "私聊来自 %d: %s", sender_id, message); // 构造私聊消息格式send(receiver_socket, formatted_message, strlen(formatted_message), 0); // 发送私聊消息printf("私聊: 客户端 %d 到 客户端 %d: %s\n", sender_id, receiver_id, message); // 服务器日志}pthread_mutex_unlock(&clients_mutex);
}
5.2.3 命令调用位置:在 client_thread()
中调用 handle_command()
确保客户端发送的 /private
命令被处理:
while ((read_size = recv(conn, buffer, BUFFER_SIZE, 0)) > 0)
{buffer[read_size] = '\0';if (buffer[0] == '/'){handle_command(buffer, conn, client_id); // 识别是否为私聊命令}else{broadcast(buffer, conn); // 普通消息广播}
}
5.3 服务器广播功能
5.3.1 广播函数定义:broadcast()
c复制编辑void broadcast(char *message, int sender_socket)
{pthread_mutex_lock(&clients_mutex);for (int i = 0; i < MAX_CLIENTS; i++){if (clients[i].socket != -1 && clients[i].socket != sender_socket){if (send(clients[i].socket, message, strlen(message), 0) < 0){perror("广播消息失败");close(clients[i].socket);remove_client(clients[i].socket, clients[i].id);}}}pthread_mutex_unlock(&clients_mutex);
}
5.3.2 具体说明
代码位置 | 功能 |
---|---|
for 循环遍历所有客户端 | 找到所有在线客户端 |
clients[i].socket != sender_socket | 避免给自己重复发送消息 |
send() | 向其他客户端广播消息 |
错误处理 | 如果发送失败,关闭连接并清除客户端信息 |
6. 实验过程中遇到的问题及解决方法
6.1 终端中文编码错误
**原因:**服务器端的编码是独立的,即使在笔记本上处理好编码格式,复制到服务器上会依旧按照服务器的编码格式
解决方法: 在服务器上直接设置对应的编码保存格式,设置成UTF-8(默认是ISO-8859)
7. 附完整源代码
7.1 客户端源代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <signal.h>#define BUFFER_SIZE 1024int client_socket;
char username[50];
pthread_t receive_thread_id, send_thread_id;// 接收消息的线程函数
void *receive_message(void *arg)
{int sock = *((int *)arg);char buffer[BUFFER_SIZE];int read_size;while ((read_size = recv(sock, buffer, BUFFER_SIZE, 0)) > 0){buffer[read_size] = '\0';if (strncmp(buffer, "私聊来自", 12) == 0){// 格式化私聊信息char *colon_pos = strchr(buffer, ':');if (colon_pos){char sender_id[BUFFER_SIZE];int sender_id_length = colon_pos - buffer;strncpy(sender_id, buffer, sender_id_length);sender_id[sender_id_length] = '\0';printf("%s: %s\n", sender_id, colon_pos + 1);}else{printf("%s\n", buffer);}}else if (strcmp(buffer, "quit") == 0){printf("您已断开连接。\n");break;}else{printf("%s\n", buffer);}}if (read_size == 0){printf("连接已断开。\n");}else if (read_size == -1){perror("接收失败");}pthread_exit(NULL);
}// 发送消息的线程函数
void *send_message(void *arg)
{int sock = *((int *)arg);char message[BUFFER_SIZE];char full_message[BUFFER_SIZE + 100];while (1){if (fgets(message, BUFFER_SIZE, stdin) == NULL){break;}// 移除换行符size_t len = strlen(message);if (len > 0 && message[len - 1] == '\n'){message[len - 1] = '\0';}if (strncmp(message, "/private", 8) == 0){// 直接发送私聊命令if (send(sock, message, strlen(message), 0) < 0){printf("发送消息失败,可能已断开连接\n");break;}}else if (strcmp(message, "quit") == 0){if (send(sock, message, strlen(message), 0) < 0){printf("发送消息失败,可能已断开连接\n");}break;}else{// 拼接用户名和消息snprintf(full_message, sizeof(full_message), "%s: %s", username, message);if (send(sock, full_message, strlen(full_message), 0) < 0){printf("发送消息失败,可能已断开连接\n");break;}}}pthread_exit(NULL);
}int main(int argc, char *argv[])
{struct sockaddr_in server_addr;// 检查命令行参数if (argc != 3){printf("用法: ./client <服务器IP> <端口号>\n");printf("示例: ./client 127.0.0.1 30023\n");return 1;}char *ip_address = argv[1];int port;// 检查端口是否为数字if (sscanf(argv[2], "%d", &port) != 1){printf("错误: 端口号必须是整数\n");return 1;}// 创建套接字client_socket = socket(AF_INET, SOCK_STREAM, 0);if (client_socket == -1){perror("无法创建套接字");return 1;}// 设置服务器地址server_addr.sin_family = AF_INET;server_addr.sin_addr.s_addr = inet_addr(ip_address);server_addr.sin_port = htons(port);// 连接到服务器if (connect(client_socket, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0){perror("连接失败");printf("无法连接到服务器 %s:%d\n", ip_address, port);return 1;}printf("已连接到服务器 %s:%d\n", ip_address, port);// 输入用户名printf("请输入您的用户名: ");fgets(username, sizeof(username), stdin);// 移除换行符size_t username_len = strlen(username);if (username_len > 0 && username[username_len - 1] == '\n'){username[username_len - 1] = '\0';}// 发送用户名char username_message[BUFFER_SIZE];snprintf(username_message, sizeof(username_message), "用户名 %s", username);if (send(client_socket, username_message, strlen(username_message), 0) < 0){perror("发送用户名失败");return 1;}// 创建接收消息线程if (pthread_create(&receive_thread_id, NULL, receive_message, (void *)&client_socket) != 0){perror("创建接收线程失败");return 1;}// 创建发送消息线程if (pthread_create(&send_thread_id, NULL, send_message, (void *)&client_socket) != 0){perror("创建发送线程失败");return 1;}// 等待发送线程结束pthread_join(send_thread_id, NULL);// 关闭连接close(client_socket);printf("连接已关闭\n");return 0;
}
7.2 服务端源代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <signal.h>#define BUFFER_SIZE 1024
#define MAX_CLIENTS 100// 客户端结构体
typedef struct
{int id;int socket;
} Client;// 全局变量
int client_count = 0;
Client clients[MAX_CLIENTS];
int owner_id = -1;
pthread_mutex_t clients_mutex = PTHREAD_MUTEX_INITIALIZER;// 删除客户端
void remove_client(int socket, int client_id)
{pthread_mutex_lock(&clients_mutex);// 从客户端列表中删除for (int i = 0; i < MAX_CLIENTS; i++){if (clients[i].id == client_id){clients[i].id = -1;clients[i].socket = -1;break;}}// 如果是群主离开,重置群主if (owner_id == client_id){owner_id = -1;}pthread_mutex_unlock(&clients_mutex);
}// 向指定客户端发送私聊信息
void send_private_message(char *message, int receiver_id, int sender_id)
{pthread_mutex_lock(&clients_mutex);int receiver_socket = -1;// 查找接收者的socketfor (int i = 0; i < MAX_CLIENTS; i++){if (clients[i].id == receiver_id){receiver_socket = clients[i].socket;break;}}if (receiver_socket != -1){char formatted_message[BUFFER_SIZE];snprintf(formatted_message, BUFFER_SIZE, "私聊来自 %d: %s", sender_id, message);if (send(receiver_socket, formatted_message, strlen(formatted_message), 0) < 0){perror("发送私聊消息失败");// 考虑是否需要删除连接失败的客户端}else{printf("私聊: 客户端 %d 到 客户端 %d: %s\n", sender_id, receiver_id, message);}}pthread_mutex_unlock(&clients_mutex);
}// 处理命令
void handle_command(char *message, int conn, int client_id)
{// 私聊命令if (strncmp(message, "/private", 8) == 0){int receiver_id;char *msg_start = strchr(message + 9, ' ');if (msg_start && sscanf(message + 9, "%d", &receiver_id) == 1){// 跳过空格msg_start++;send_private_message(msg_start, receiver_id, client_id);}}
}// 广播消息
void broadcast(char *message, int sender_socket)
{pthread_mutex_lock(&clients_mutex);for (int i = 0; i < MAX_CLIENTS; i++){if (clients[i].socket != -1 && clients[i].socket != sender_socket){if (send(clients[i].socket, message, strlen(message), 0) < 0){perror("广播消息失败");// 考虑是否需要删除连接失败的客户端close(clients[i].socket);remove_client(clients[i].socket, clients[i].id);}}}pthread_mutex_unlock(&clients_mutex);
}// 客户端线程函数
void *client_thread(void *arg)
{Client *client = (Client *)arg;int conn = client->socket;int client_id = client->id;char buffer[BUFFER_SIZE];int read_size;// 发送客户端编号char id_message[50];snprintf(id_message, sizeof(id_message), "您是客户端编号 %d", client_id);send(conn, id_message, strlen(id_message), 0);// 如果是第一个连接的客户端,设置为群主if (client_id == 1){owner_id = client_id;send(conn, "您是群主", 10, 0);}// 消息处理循环while ((read_size = recv(conn, buffer, BUFFER_SIZE, 0)) > 0){buffer[read_size] = '\0';if (buffer[0] == '/'){handle_command(buffer, conn, client_id);}else{printf("客户端 %d: %s\n", client_id, buffer);broadcast(buffer, conn);}}if (read_size == 0){printf("客户端 %d 已断开连接\n", client_id);close(conn);remove_client(conn, client_id);}else if (read_size == -1){perror("接收失败");}pthread_exit(NULL);
}// 服务器输入线程函数
void *server_input_thread(void *arg)
{char message[BUFFER_SIZE];char server_message[BUFFER_SIZE + 50];while (1){printf("服务器消息: ");if (fgets(message, BUFFER_SIZE, stdin) == NULL){break;}// 移除换行符size_t len = strlen(message);if (len > 0 && message[len - 1] == '\n'){message[len - 1] = '\0';len--;}if (len > 0){snprintf(server_message, sizeof(server_message), "服务器公告: %s", message);printf("广播: %s\n", server_message);pthread_mutex_lock(&clients_mutex);for (int i = 0; i < MAX_CLIENTS; i++){if (clients[i].socket != -1){if (send(clients[i].socket, server_message, strlen(server_message), 0) < 0){perror("发送服务器消息失败");close(clients[i].socket);clients[i].socket = -1;clients[i].id = -1;}}}pthread_mutex_unlock(&clients_mutex);}}pthread_exit(NULL);
}int main(int argc, char *argv[])
{int server_socket, client_socket;struct sockaddr_in server_addr, client_addr;socklen_t addr_size = sizeof(struct sockaddr_in);pthread_t tid;// 初始化客户端数组for (int i = 0; i < MAX_CLIENTS; i++){clients[i].id = -1;clients[i].socket = -1;}// 检查命令行参数if (argc != 2){printf("用法: ./server <端口号>\n");printf("示例: ./server 30023\n");return 1;}int port;// 检查端口是否为数字if (sscanf(argv[1], "%d", &port) != 1){printf("错误: 端口号必须是整数\n");return 1;}// 创建服务器套接字server_socket = socket(AF_INET, SOCK_STREAM, 0);if (server_socket == -1){perror("无法创建套接字");return 1;}// 设置服务器地址server_addr.sin_family = AF_INET;server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");server_addr.sin_port = htons(port);// 绑定套接字if (bind(server_socket, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0){perror("绑定失败");return 1;}// 监听连接if (listen(server_socket, 100) < 0){perror("监听失败");return 1;}printf("聊天室已启动,监听端口: %d\n", port);// 创建服务器输入线程pthread_t server_thread;if (pthread_create(&server_thread, NULL, server_input_thread, NULL) < 0){perror("创建服务器输入线程失败");return 1;}// 接受连接循环while ((client_socket = accept(server_socket, (struct sockaddr *)&client_addr, &addr_size))){client_count++;pthread_mutex_lock(&clients_mutex);// 寻找空闲的客户端槽位int slot = -1;for (int i = 0; i < MAX_CLIENTS; i++){if (clients[i].socket == -1){slot = i;break;}}if (slot != -1){clients[slot].socket = client_socket;clients[slot].id = client_count;printf("客户端 %d 已连接,地址: %s:%d\n",client_count,inet_ntoa(client_addr.sin_addr),ntohs(client_addr.sin_port));// 创建新的客户端线程if (pthread_create(&tid, NULL, client_thread, (void *)&clients[slot]) < 0){perror("创建线程失败");close(client_socket);clients[slot].socket = -1;clients[slot].id = -1;}else{// 分离线程,让它自己结束pthread_detach(tid);}}else{printf("客户端连接已达上限,拒绝连接\n");close(client_socket);}pthread_mutex_unlock(&clients_mutex);}if (client_socket < 0){perror("接受连接失败");return 1;}close(server_socket);return 0;
}