实验四:网络编程

发布于:2025-05-11 ⋅ 阅读:(16) ⋅ 点赞:(0)

文章目录

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 1024

int 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;

    // 查找接收者的socket
    for (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;
}


网站公告

今日签到

点亮在社区的每一天
去签到