当前位置: 首页 > backend >正文

实验四:网络编程

文章目录

  • 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;
}
http://www.xdnf.cn/news/5208.html

相关文章:

  • localStorage和sessionStorage
  • Day28 -js开发01 -JS三个实例:文件上传 登录验证 购物商城 ---逻辑漏洞复现 及 判断js的payload思路
  • [Linux网络_71] NAT技术 | 正反代理 | 网络协议总结 | 五种IO模型
  • 好用的播放器推荐
  • 蓝桥杯嵌入式第十一届省赛真题
  • Python企业级OCR实战开发:从基础识别到智能应用
  • 健康养生:开启活力生活的密码
  • JGL066生活垃圾滚筒筛分选机实验装置
  • MAD-TD: MODEL-AUGMENTED DATA STABILIZES HIGH UPDATE RATIO RL
  • Ubuntu22.04安装显卡驱动/卸载显卡驱动
  • JDBC工具类的三个版本
  • Windows系统Jenkins企业级实战
  • Redis经典面试题
  • 数据库实验10
  • 【经验总结】Ubuntu 22.04.5 LTS 将内核从5.15.0-140 升级到6.8.0-60后纽曼无线网卡无法使用解决措施
  • C++ 命令模式详解
  • R 语言科研绘图 --- 桑基图-汇总
  • Python网络爬虫:从入门到实践
  • uniapp-商城-51-后台 商家信息(logo处理)
  • 33号远征队 - SDKDump
  • Spring 必会之微服务篇(2)
  • 前端进化论·JavaScript 篇 · 数据类型
  • [学习]RTKLib详解:sbas.c与rtcm.c
  • Linux 阻塞和非阻塞 I/O 简明指南
  • [架构之美]linux常见故障问题解决方案(十九)
  • 数据结构与算法分析实验11 实现顺序查找表
  • vue注册用户使用v-model实现数据双向绑定
  • 202536 | KafKa生产者分区写入策略+消费者分区分配策略
  • 0.环境初始化
  • 基于Spring Boot + Vue的高校心理教育辅导系统