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

Linux UDP 網絡編程:從回顯服務器到多人聊天室

     編輯:關於C語言

Linux UDP 網絡編程:從回顯服務器到多人聊天室

UDP(用戶數據報協議)無連接、不可靠、無序,但開銷小、速度快、支援廣播/多播。本文從基礎 API 入手,逐步實現簡易 UDP 回顯服務器,再擴展為多進程多人聊天室。

核心 API 對比

特性TCPUDP
連接建立三次握手無連接
可靠性可靠、有序不可靠、無序
APIsocket/listen/accept/read/writesocket/bind/recvfrom/sendto
適用場景文件傳輸、Web、郵件視頻流、遊戲、DNS、廣播

關鍵函數原型

#include <sys/socket.h>
int socket(int domain, int type, int protocol);  // type=SOCK_DGRAM
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
                 struct sockaddr *src_addr, socklen_t *addrlen);
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
               const struct sockaddr *dest_addr, socklen_t addrlen);
  • recvfrom:接收數據並獲取發送方地址(輸出參數)。
  • sendto:發送數據到指定地址。若預先 connect(),可用 send/recv。

第一步:UDP 回顯服務器 udpsrv.c

#include <stdio.h>, <stdlib.h>, <string.h>, <unistd.h>, <arpa/inet.h>, <sys/socket.h>
#define PORT 8088
#define BUFSZ 1024

int main() {
    int sd = socket(PF_INET, SOCK_DGRAM, 0);
    struct sockaddr_in addr = { .sin_family = AF_INET, .sin_port = htons(PORT), .sin_addr.s_addr = INADDR_ANY };
    bind(sd, (struct sockaddr*)&addr, sizeof(addr));
    printf("UDP server listening on port %d\n", PORT);
    char msg[BUFSZ];
    socklen_t alen = sizeof(addr);
    while (1) {
        int n = recvfrom(sd, msg, BUFSZ-1, 0, (struct sockaddr*)&addr, &alen);
        if (n <= 0) continue;
        msg[n] = '\0';
        printf("Received from %s:%d: %s\n", inet_ntoa(addr.sin_addr), ntohs(addr.sin_port), msg);
        sendto(sd, msg, n, 0, (struct sockaddr*)&addr, alen);  // 原樣回發
    }
    close(sd);
    return 0;
}

第二步:UDP 客戶端 udpclt.c

#include ... // 同上頭文件
#define PORT 8088
#define BUFSZ 1024

int main() {
    int sd = socket(PF_INET, SOCK_DGRAM, 0);
    struct sockaddr_in srv = { .sin_family = AF_INET, .sin_port = htons(PORT) };
    inet_aton("127.0.0.1", &srv.sin_addr);
    char msg[BUFSZ];
    socklen_t alen = sizeof(srv);
    while (fgets(msg, BUFSZ, stdin)) {
        sendto(sd, msg, strlen(msg), 0, (struct sockaddr*)&srv, alen);
        int n = recvfrom(sd, msg, BUFSZ-1, 0, (struct sockaddr*)&srv, &alen);
        if (n > 0) { msg[n] = '\0'; printf("Echo: %s", msg); }
    }
    close(sd);
    return 0;
}

第三步:多人聊天室 —— 多進程架構

設計:

  • 服務器維護客戶端地址鏈表(struct sockaddr_in + next)。
  • 協議簡單:type=1 登錄、2 說話、3 退出。消息結構體 umsg 含類型、用戶名、文本。
  • 服務器單進程循環 recvfrom,根據類型廣播/登錄/退出。
  • 客戶端 fork():子進程讀 stdin 發送,父進程 recvfrom 顯示。

服務器核心代碼片段 udpmulsrv.c

typedef struct ucnode { struct sockaddr_in addr; struct ucnode *next; } *ucnode_t;

void broadcast(ucnode_t head, int sd, struct sockaddr_in *src, struct umsg *msg) {
    for (ucnode_t p = head->next; p; p = p->next) {
        if (memcmp(&p->addr, src, sizeof(*src)) != 0)  // 不發給自己
            sendto(sd, msg, sizeof(*msg), 0, (struct sockaddr*)&p->addr, sizeof(p->addr));
    }
}

int main(int argc, char *argv[]) {
    // ... 初始化 socket, bind ...
    ucnode_t head = calloc(1, sizeof(*head));  // 頭節點
    while (1) {
        struct umsg msg; struct sockaddr_in cli; socklen_t alen = sizeof(cli);
        recvfrom(sd, &msg, sizeof(msg), 0, (struct sockaddr*)&cli, &alen);
        switch (msg.type) {
            case '1':  // 登錄
                insert_node(head, &cli);
                broadcast(head, sd, &cli, &msg);  // 通知舊用戶有新用戶
                break;
            case '2': broadcast(head, sd, &cli, &msg); break;  // 轉發消息
            case '3':  // 退出
                broadcast(head, sd, &cli, &msg);
                delete_node(head, &cli);
                break;
        }
    }
}

客戶端核心 udpmulclt.c

pid_t pid = fork();
if (pid == 0) {  // 子進程:發送
    signal(SIGCHLD, SIG_IGN);
    while (fgets(msg.text, sizeof(msg.text), stdin)) {
        if (strcasecmp(msg.text, "quit\n") == 0) { msg.type = '3'; sendto(...); break; }
        msg.type = '2'; sendto(sd, &msg, sizeof(msg), 0, (struct sockaddr*)&srv, alen);
    }
    close(sd); kill(getppid(), SIGKILL); exit(0);
}
// 父進程:接收顯示
while (1) {
    recvfrom(sd, &msg, sizeof(msg), 0, NULL, NULL);
    switch (msg.type) {
        case '1': printf("%s joined!\n", msg.name); break;
        case '2': printf("%s: %s", msg.name, msg.text); break;
        case '3': printf("%s left!\n", msg.name); break;
    }
}
kill(pid, SIGKILL); waitpid(pid, NULL, 0);

編譯運行

gcc -g -Wall -o udpmulsrv.out udpmulsrv.c
gcc -g -Wall -o udpmulclt.out udpmulclt.c
# 終端1:服務器
./udpmulsrv.out 0.0.0.0 8088
# 終端2/3/...:客戶端
./udpmulclt.out 127.0.0.1 8088 Alice
./udpmulclt.out 127.0.0.1 8088 Bob

關鍵技術點總結

  1. struct sockaddr_in addr = { AF_INET } 等價於 memset(&addr,0,sizeof(addr)); addr.sin_family=AF_INET;。
  2. 消息結構體固定大小,sendto 發送整個結構體,接收端按結構體解析,字段末尾手動加 \0 保險。
  3. bzero 非標準(BSD),跨平台用 memset(&v,0,sizeof(v)) 封裝宏 BZERO(v)。
  4. 鏈表節點用 calloc 零初始化,刪除時 free 防洩漏。
  5. UDP 無連接,服務器無需 listen/accept,天然支援多客戶端。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved