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

Socket 網絡編程:TCP 服務端/客戶端完整示例(Linux C)

     編輯:關於C語言

Socket 網絡編程:TCP 服務端/客戶端完整示例(Linux C)

Socket 是網絡通信的基石。本文提供基於 Linux C 的並發 TCP 服務端(fork 多進程)與阻塞客戶端完整代碼,詳細註釋關鍵系統調用、參數含義、錯誤處理、並發模型,並解析 connect/accept 參數易混淆點。

一、服務端代碼(server.c)

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

#define PORT 5050
#define BACKLOG 1024
#define BUF_SIZE 1024

void handle_client(int client_fd) {
    char buf[BUF_SIZE];
    ssize_t n;
    while ((n = read(client_fd, buf, BUF_SIZE - 1)) > 0) {
        buf[n] = '\0';
        printf("Client: %s", buf);
        if (strcmp(buf, "exit\n") == 0) break;
        write(client_fd, buf, n);  // 回射
    }
    close(client_fd);
    printf("Client disconnected\n");
}

int main() {
    int server_fd, client_fd;
    struct sockaddr_in server_addr, client_addr;
    socklen_t addr_len = sizeof(client_addr);
    pid_t pid;

    // 1. 創建 Socket
    if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        perror("socket");
        exit(EXIT_FAILURE);
    }

    // 2. 設置地址復用(避免 TIME_WAIT 導致重啟失敗)
    int opt = 1;
    setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

    // 3. 綁定地址
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(PORT);
    server_addr.sin_addr.s_addr = INADDR_ANY;  // 監聽所有網卡
    if (bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
        perror("bind");
        exit(EXIT_FAILURE);
    }

    // 4. 監聽
    if (listen(server_fd, BACKLOG) < 0) {
        perror("listen");
        exit(EXIT_FAILURE);
    }
    printf("Server listening on port %d...\n", PORT);

    // 5. 主循環:accept + fork
    while (1) {
        client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &addr_len);
        if (client_fd < 0) {
            perror("accept");
            continue;
        }
        char ip[INET_ADDRSTRLEN];
        inet_ntop(AF_INET, &client_addr.sin_addr, ip, INET_ADDRSTRLEN);
        printf("Accepted: %s:%d\n", ip, ntohs(client_addr.sin_port));

        pid = fork();
        if (pid < 0) {
            perror("fork");
            close(client_fd);
        } else if (pid == 0) {  // 子進程
            close(server_fd);   // 子進程不需要監聽套接字
            handle_client(client_fd);
            exit(EXIT_SUCCESS);
        } else {  // 父進程
            close(client_fd);   // 父進程不需要連接套接字
            // 可選:安裝 SIGCHLD 處理器回收僵屍進程
        }
    }
    close(server_fd);
    return 0;
}

二、客戶端代碼(client.c)

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

#define PORT 5050
#define BUF_SIZE 1024
#define SERVER_IP "127.0.0.1"

int main() {
    int sockfd;
    struct sockaddr_in server_addr;
    char send_buf[BUF_SIZE], recv_buf[BUF_SIZE];

    // 1. 創建 Socket
    if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        perror("socket");
        exit(EXIT_FAILURE);
    }

    // 2. 設置服務端地址
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(PORT);
    if (inet_pton(AF_INET, SERVER_IP, &server_addr.sin_addr) <= 0) {
        perror("inet_pton");
        exit(EXIT_FAILURE);
    }

    // 3. 連接
    if (connect(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
        perror("connect");
        exit(EXIT_FAILURE);
    }
    printf("Connected to server. Type 'exit' to quit.\n");

    // 4. 通信循環
    while (fgets(send_buf, BUF_SIZE, stdin)) {
        write(sockfd, send_buf, strlen(send_buf));
        ssize_t n = read(sockfd, recv_buf, BUF_SIZE - 1);
        if (n <= 0) break;
        recv_buf[n] = '\0';
        printf("Server echo: %s", recv_buf);
        if (strcmp(send_buf, "exit\n") == 0) break;
    }
    close(sockfd);
    return 0;
}

三、編譯與運行

gcc server.c -o server
gcc client.c -o client

# 終端1
./server
# 終端2
./client

四、關鍵點深度解析

1. connect vs accept 參數陷阱

  • connect(sockfd, (struct sockaddr*)&addr, sizeof(addr)):第三參數是值 socklen_t(地址結構體大小)。
  • accept(sockfd, (struct sockaddr*)&addr, &addrlen):第三參數是指針 socklen_t*(輸入輸出參數,傳入緩衝區大小,返回實際地址長度)。
    這是極易寫錯導致段錯誤的地方!

2. 並發模型對比

模型優點缺點適用場景
fork 多進程隔離性好、簡單穩定進程開銷大、數量受限連接數較少、計算密集型
pthread 多線程輕量、共享內存易同步複雜、一崩全崩高並發、IO 密集型
I/O 多路復用單線程高並發、無上下文切換邏輯複雜、無法利用多核極高並發(C10K/C100K)
Reactor/Proactor事件驅動、可擴展框架學習成本高生產級高性能服務器

3. 僵屍進程處理

父進程需安裝 SIGCHLD 處理器調用 waitpid(-1, NULL, WNOHANG) 回收子進程,或雙重 fork 技巧。

4. 粘包/拆包

TCP 是流式協議,read/write 不保證消息邊界。實際項目需應用層協議定義消息長度(定長/長度前綴/分隔符)。

五、Windows 版本差異

  • 頭文件:<Winsock2.h>,鏈接 ws2_32.lib
  • 啟動:WSAStartup(MAKEWORD(2,2), &wsaData)
  • 錯誤碼:WSAGetLastError() 而非 errno
  • 關閉:closesocket() 而非 close()
  • 類型:SOCKET 而非 int,INVALID_SOCKET 判斷失敗

掌握 Socket 基礎 API 與並發模型,是進入網絡編程、分布式系統、高性能服務器開發的敲門磚。

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