首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

Linux 下 GNU C 實現多線程聊天室:Socket + pthread 完整教程

     編輯:關於C語言

項目概覽

  • 架構:Server 監聽端口,每連接創建一線程處理收發
  • 協議:TCP,文本消息廣播
  • 並發模型:One Thread Per Connection(教學用,生產建議 epoll/IOCP)
  • 編譯:gcc -Wall -o server -lpthread server.c

服務端核心代碼


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define MAX_CLIENTS 5
#define BUF_SIZE 1024

int client_fds[MAX_CLIENTS]; // 客戶端 socket 數組
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; // 保護數組

void *handle_client(void *arg) {
    int fd = *(int *)arg;
    char name[32], buf[BUF_SIZE], msg[BUF_SIZE + 32];
    int n;
    
    // 1. 接收用戶名
    n = recv(fd, name, sizeof(name) - 1, 0);
    if (n <= 0) goto cleanup;
    name[n] = '\0';
    
    // 2. 廣播上線通知
    snprintf(msg, sizeof(msg), "[%s 加入聊天室]\n", name);
    broadcast(msg, fd);
    
    // 3. 消息循環
    while ((n = recv(fd, buf, BUF_SIZE - 1, 0)) > 0) {
        buf[n] = '\0';
        snprintf(msg, sizeof(msg), "%s: %s", name, buf);
        broadcast(msg, fd);
    }
    
    // 4. 下線廣播
    snprintf(msg, sizeof(msg), "[%s 退出聊天室]\n", name);
    broadcast(msg, fd);

cleanup:
    close(fd);
    pthread_mutex_lock(&lock);
    for (int i = 0; i < MAX_CLIENTS; ++i)
        if (client_fds[i] == fd) { client_fds[i] = -1; break; }
    pthread_mutex_unlock(&lock);
    return NULL;
}

void broadcast(const char *msg, int sender_fd) {
    pthread_mutex_lock(&lock);
    for (int i = 0; i < MAX_CLIENTS; ++i) {
        if (client_fds[i] != -1 && client_fds[i] != sender_fd)
            send(client_fds[i], msg, strlen(msg), 0);
    }
    pthread_mutex_unlock(&lock);
}

int main() {
    for (int i = 0; i < MAX_CLIENTS; ++i) client_fds[i] = -1;
    
    int server_fd = socket(AF_INET, SOCK_STREAM, 0);
    struct sockaddr_in addr = {0};
    addr.sin_family = AF_INET;
    addr.sin_port = htons(12345);
    addr.sin_addr.s_addr = INADDR_ANY;
    
    bind(server_fd, (struct sockaddr *)&addr, sizeof(addr));
    listen(server_fd, 10);
    printf("Server listening on port 12345...\n");
    
    while (1) {
        struct sockaddr_in cli_addr;
        socklen_t len = sizeof(cli_addr);
        int fd = accept(server_fd, (struct sockaddr *)&cli_addr, &len);
        if (fd < 0) continue;
        
        // 找空位
        pthread_mutex_lock(&lock);
        int idx = -1;
        for (int i = 0; i < MAX_CLIENTS; ++i)
            if (client_fds[i] == -1) { idx = i; break; }
        if (idx == -1) { close(fd); pthread_mutex_unlock(&lock); continue; }
        client_fds[idx] = fd;
        pthread_mutex_unlock(&lock);
        
        pthread_t tid;
        pthread_create(&tid, NULL, handle_client, &fd);
        pthread_detach(tid); // 自動回收資源
    }
    return 0;
}

客戶端代碼


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define BUF_SIZE 1024

void *recv_thread(void *arg) {
    int fd = *(int *)arg;
    char buf[BUF_SIZE];
    int n;
    while ((n = recv(fd, buf, BUF_SIZE - 1, 0)) > 0) {
        buf[n] = '\0';
        printf("%s", buf);
        fflush(stdout);
    }
    return NULL;
}

int main(int argc, char *argv[]) {
    if (argc != 3) { printf("Usage: %s  \n", argv[0]); return 1; }
    
    int fd = socket(AF_INET, SOCK_STREAM, 0);
    struct sockaddr_in addr = {0};
    addr.sin_family = AF_INET;
    addr.sin_port = htons(atoi(argv[2]));
    inet_pton(AF_INET, argv[1], &addr.sin_addr);
    
    connect(fd, (struct sockaddr *)&addr, sizeof(addr));
    
    // 發送用戶名
    char name[32];
    printf("輸入用戶名: "); fgets(name, sizeof(name), stdin);
    name[strcspn(name, "\n")] = '\0';
    send(fd, name, strlen(name), 0);
    
    pthread_t tid;
    pthread_create(&tid, NULL, recv_thread, &fd);
    pthread_detach(tid);
    
    // 主線程發送消息
    char buf[BUF_SIZE];
    while (fgets(buf, BUF_SIZE, stdin)) {
        send(fd, buf, strlen(buf), 0);
    }
    close(fd);
    return 0;
}

關鍵技術點解析

  1. 線程安全:client_fds 共享數組需互斥鎖保護
  2. 資源清理:斷開時關閉 fd、標記數組槽位、pthread_detach 避免殭屍線程
  3. 緩衝區處理:recv 返回長度,手動添加 \0 才能作為 C 字符串處理
  4. 廣播邏輯:排除發送者自己,錯誤處理簡化(生產需重試/移除)
  5. 編譯鏈接:-lpthread 必須放在源文件後

擴展方向

  • I/O 多路復用:epoll (Linux) / IOCP (Windows) 支持萬級連接
  • 協議升級:長度前綴/JSON/Protobuf 替換文本行
  • 心跳檢測:定時 Ping 檢測死連接
  • 私聊/群組:擴展消息格式帶目標 ID
  • SSL/TLS:OpenSSL 加密傳輸

總結

這個迷你聊天室雖簡單,但包含Socket 編程、多線程同步、網絡字節序、資源管理等系統級開發核心技能。從這裡出發,逐步替換為生產級組件,即可構建高性能網絡服務。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved