【嵌入式学习3】多用户多任务服务器实战

发布于:2025-04-01 ⋅ 阅读:(24) ⋅ 点赞:(0)

1、服务器

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <arpa/inet.h>

#define PORT 8080 //服务器监听的端口号
#define BUFFER_SIZE 1024
#define MAX_CLIENTS 100

typedef struct{
    int socket;
    struct sockaddr_in address;
    int addr_len;
    int index;//客户端在 clients 数组中的索引
}client_t;

client_t *clients[MAX_CLIENTS];
pthread_mutex_t clients_mutex = PTHREAD_MUTEX_INITIALIZER;//一个互斥锁,用于保护对 clients 数组的访问,确保线程安全

//将消息广播给所有其他客户端
void broadcast_message(char *message, int sender_index) {
    pthread_mutex_lock(&clients_mutex);

    for (int i = 0; i < MAX_CLIENTS; ++i) {
        if (clients[i] && i != sender_index) {
            if (send(clients[i]->socket, message, strlen(message), 0) < 0) {
                perror("send");
                continue;
            }
        }
    }

    pthread_mutex_unlock(&clients_mutex);
}

void *handle_client(void *arg) {
    char buffer[BUFFER_SIZE];
    int leave_flag = 0;//一个标志变量,用于指示是否需要退出循环。初始值为 0,表示不退出。
    client_t *cli = (client_t *)arg;

    while (1) {
        //recv(int sockfd, void *buf, size_t len, int flags);flags=0表示正常接收数据
        int receive = recv(cli->socket, buffer, BUFFER_SIZE, 0);//表示实际接收到的字节数
        if (receive > 0) {
            buffer[receive] = '\0';//在接收到的消息后面添加字符串结束符 \0,确保消息是一个有效的 C 字符串
            broadcast_message(buffer, cli->index);
        } else if (receive == 0 || strcmp(buffer, "exit") == 0) {//客户端正常关闭了连接(TCP 的 FIN 包),如果客户端发送了 "exit" 消息
            leave_flag = 1;
        } else {//表示接收失败
            perror("recv");
            leave_flag = 1;
        }

        if (leave_flag) {
            break;
        }
    }

    close(cli->socket);//关闭当前客户端的套接字,释放资源
    pthread_mutex_lock(&clients_mutex);
    clients[cli->index] = NULL;//将当前客户端从 clients 数组中移除
    pthread_mutex_unlock(&clients_mutex);
    free(cli);
    pthread_exit(NULL);//退出当前线程
}

int main() {
    /*
    server_socket:服务器端套接字描述符,用于监听客户端的连接请求。
    new_socket:新连接的客户端套接字描述符。
     */
    int server_socket, new_socket;
    /*
    struct sockaddr_in 是一个结构体
    server_addr:用于存储服务器的地址信息。
    client_addr:用于存储连接的客户端的地址信息。
    */
    struct sockaddr_in server_addr, client_addr;
    //socklen_t 是 POSIX 标准定义的数据类型,用于表示套接字地址的长度
    socklen_t addr_len = sizeof(client_addr);

    server_socket = socket(AF_INET, SOCK_STREAM, 0);
    if (server_socket < 0) {
        perror("socket");
        exit(EXIT_FAILURE);
    }

    server_addr.sin_family = AF_INET;
    /*
    sin_addr 是 struct sockaddr_in 结构体中的一个成员
    sin_addr 是一个结构体,而 s_addr 是该结构体中的实际数据字段。
    在设置或获取 IP 地址时,需要通过 s_addr 来访问或修改 IP 地址的值。
    */
    server_addr.sin_addr.s_addr = INADDR_ANY;// 绑定到所有可用的网络接口
    server_addr.sin_port = htons(PORT);//使用 htons 函数将端口号转换为网络字节序

    /*
    使用 bind 函数将套接字绑定到指定的地址和端口
    int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
    指向套接字地址结构体的指针。通常是一个 struct sockaddr_in 的地址,但需要通过强制类型转换为 struct sockaddr *
    */
    if (bind(server_socket, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
        perror("bind");
        close(server_socket);
        exit(EXIT_FAILURE);
    }
    /*
    使用 listen 函数将套接字设置为监听状态,最大等待队列长度为 10
    EXIT_FAILURE 是一个宏,通常定义为 1
    */
    if (listen(server_socket, 10) < 0) {
        perror("listen");
        close(server_socket);
        exit(EXIT_FAILURE);
    }

    printf("Server listening on port %d\n", PORT);

    while (1) {
        /*
        新连接的客户端套接字描述符
        int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
        返回值是一个新的套接字描述符,表示与客户端建立的连接
        */
        new_socket = accept(server_socket, (struct sockaddr *)&client_addr, &addr_len);
        if (new_socket < 0) {
            perror("accept");
            continue;
        }
        //使用 pthread_mutex_lock 锁定 clients 数组,确保线程安全
        pthread_mutex_lock(&clients_mutex);

        int i;
        for (i = 0; i < MAX_CLIENTS; ++i) {
            if (!clients[i]) {
                client_t *cli = (client_t *)malloc(sizeof(client_t));
                cli->socket = new_socket;
                cli->address = client_addr;
                cli->addr_len = addr_len;
                cli->index = i;
                clients[i] = cli;

                //使用 pthread_create 创建一个线程来处理客户端连接,调用 handle_client 函数
                pthread_t tid;
                /*
                &tid:指向线程标识符的指针,用于存储新创建的线程的标识符。
                NULL:使用默认的线程属性。
                handle_client:线程函数的指针,表示新线程将执行的函数。
                (void *)cli:传递给 handle_client 函数的参数,cli 是一个指向 client_t 结构体的指针,表示当前客户端的信息。
                 */
                pthread_create(&tid, NULL, handle_client, (void *)cli);
                /*将线程分离,使其在结束时自动释放资源*/
                pthread_detach(tid);

                break;
            }
        }

        if (i == MAX_CLIENTS) {
            printf("Max clients reached. Rejected: ");
            printf("%d.%d.%d.%d\n",
                   client_addr.sin_addr.s_addr & 0xff,
                   (client_addr.sin_addr.s_addr & 0xff00) >> 8,
                   (client_addr.sin_addr.s_addr & 0xff0000) >> 16,
                   (client_addr.sin_addr.s_addr & 0xff000000) >> 24);
            close(new_socket);
        }

        pthread_mutex_unlock(&clients_mutex);
    }

    return 0;
}

2、客户端

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <arpa/inet.h>

#define PORT 8080
#define BUFFER_SIZE 1024

void *receive_messages(void *arg) {
    int server_socket = *(int *)arg;
    char buffer[BUFFER_SIZE];

    while (1) {
        int receive = recv(server_socket, buffer, BUFFER_SIZE, 0);
        if (receive > 0) {
            buffer[receive] = '\0';
            printf("Server: %s", buffer); // 显示接收到的消息
        } else if (receive == 0) {
            printf("Server disconnected\n");
            break;
        } else {
            perror("recv");
            break;
        }
    }

    close(server_socket);
    pthread_exit(NULL);
}

int main() {
    int server_socket;
    struct sockaddr_in server_addr;
    char buffer[BUFFER_SIZE];

    // 创建套接字
    server_socket = socket(AF_INET, SOCK_STREAM, 0);
    if (server_socket < 0) {
        perror("socket");
        exit(EXIT_FAILURE);
    }

    // 初始化服务器地址
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(PORT);
    server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");

    // 连接到服务器
    if (connect(server_socket, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
        perror("connect");
        close(server_socket);
        exit(EXIT_FAILURE);
    }

    printf("Connected to server\n");

    // 创建接收消息的线程
    pthread_t receive_thread;
    pthread_create(&receive_thread, NULL, receive_messages, &server_socket);

    // 发送消息
    while (1) {
        fgets(buffer, BUFFER_SIZE, stdin);
        if (strcmp(buffer, "exit\n") == 0) {
            send(server_socket, buffer, strlen(buffer), 0);
            break;
        }
        send(server_socket, buffer, strlen(buffer), 0);
    }

    // 等待接收线程结束
    pthread_join(receive_thread, NULL);

    // 关闭套接字
    close(server_socket);

    return 0;
}