Windows XP Windows 7 Windows 2003 Windows Vista Windows 綜合教學 Linux 系統教學
Windows 10 Windows 8 Windows 2008 Windows NT Windows Server 電腦軟體教學
您现在的位置: Windows教程網 >> Windows教程 >  >> Linux 系統教學 >> Linux 教學

Linux Socket 網絡編程完全指南:從基礎到實戰

編輯:Linux 教學

前言

Socket 是網絡編程的核心抽象,Linux 將其視為一種特殊的文件描述符,統一了本地進程通信與網絡通信的接口。本文系統講解 Socket 概念、類型、核心 API、地址結構、字節序轉換、連接建立流程,並提供完整的 TCP 客戶端/服務端範例。

一、Socket 基礎概念

屬性說明
本質內核中一個數據結構,用戶空間通過整數描述符訪問
類比類似文件描述符,支持 read/write/close 等系統調用
標識一個連接的五元組協議、本地 IP、本地端口、遠端 IP、遠端端口

二、Socket 類型

  • SOCK_STREAM:面向連接、可靠、雙向字節流(TCP)。
  • SOCK_DGRAM:無連接、不可靠、數據報(UDP)。
  • SOCK_RAW:原始 Socket,直接訪問協議頭(需 root)。

三、核心 API 詳解

1. socket() —— 創建 Socket

#include <sys/socket.h>
int socket(int domain, int type, int protocol);
  • domain:AF_INET (IPv4)、AF_INET6 (IPv6)、AF_UNIX (本地)。
  • type:SOCK_STREAM / SOCK_DGRAM / SOCK_RAW。
  • protocol:通常為 0(自動選擇默認協議)。
  • 返回:非負描述符,失敗返回 -1 並設 errno。

2. bind() —— 綁定本地地址

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
  • 服務端必須調用,將 Socket 綁定到具體 IP + 端口。
  • 客戶端通常不調用,由內核隱式綁定臨時端口。

3. listen() —— 監聽連接

int listen(int sockfd, int backlog);
  • 將主動 Socket 轉為被動監聽 Socket。
  • backlog:已完成連接隊列最大長度(內核會按 somaxconn 截取)。

4. accept() —— 接受連接

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
  • 阻塞等待客戶端連接,返回新的已連接 Socket 描述符。
  • 原監聽 Socket 繼續監聽新連接。

5. connect() —— 發起連接

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
  • 客戶端調用,主動發起 TCP 三次握手。
  • 阻塞直到連接建立或超時/錯誤。

6. read/write/recv/send —— 數據收發

ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
  • recv/send 支持 MSG_PEEK、MSG_OOB 等標志,功能更豐富。
  • TCP 為字節流,應用層需自行處理粘包/拆包(定長、分隔符、長度前綴)。

7. close() / shutdown() —— 關閉連接

  • close(fd):引用計數歸零時發送 FIN,雙向關閉。
  • shutdown(fd, SHUT_RD/WR/RDWR):單向關閉,不影響引用計數。

四、地址結構體

通用結構

struct sockaddr {
    sa_family_t sa_family;  // AF_INET, AF_INET6, AF_UNIX...
    char        sa_data[14]; // 協議地址(不透明)
};

IPv4 專用(推薦使用)

struct sockaddr_in {
    sa_family_t    sin_family; // AF_INET
    in_port_t      sin_port;   // 端口(網絡字節序)
    struct in_addr sin_addr;   // IP 地址
    unsigned char  sin_zero[8]; // 填充,對齊到 sizeof(sockaddr)
};

struct in_addr {
    in_addr_t s_addr; // 32 位 IP(網絡字節序)
};

IPv6 專用

struct sockaddr_in6 {
    sa_family_t     sin6_family; // AF_INET6
    in_port_t       sin6_port;
    uint32_t        sin6_flowinfo;
    struct in6_addr sin6_addr;   // 128 位 IP
    uint32_t        sin6_scope_id;
};

五、字節序轉換

網絡傳輸統一使用大端序,x86 等小端機器需轉換:

函數含義
htons()Host TO Network Short (16 位)
htonl()Host TO Network Long (32 位)
ntohs()Network TO Host Short
ntohl()Network TO Host Long
addr.sin_port = htons(8080);
addr.sin_addr.s_addr = inet_addr("192.168.1.100"); // 已是網絡序
// 或
inet_pton(AF_INET, "192.168.1.100", &addr.sin_addr);

六、自動獲取本機 IP 與空閒端口

addr.sin_addr.s_addr = INADDR_ANY; // 0.0.0.0,綁定所有本地接口
addr.sin_port = 0;                 // 內核自動分配空閒端口

綁定後可用 getsockname() 獲取實際分配的端口。

七、完整範例:TCP 回射服務端與客戶端

服務端 server.c

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

#define PORT 8080
#define BACKLOG 5
#define BUF_SIZE 1024

int main() {
    int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (listen_fd < 0) { perror("socket"); exit(1); }

    int opt = 1;
    setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); // 避免 TIME_WAIT 佔用端口

    struct sockaddr_in serv_addr = {0};
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = INADDR_ANY;
    serv_addr.sin_port = htons(PORT);

    if (bind(listen_fd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) < 0) {
        perror("bind"); exit(1);
    }
    if (listen(listen_fd, BACKLOG) < 0) {
        perror("listen"); exit(1);
    }
    printf("Server listening on port %d...\n", PORT);

    while (1) {
        struct sockaddr_in cli_addr;
        socklen_t cli_len = sizeof(cli_addr);
        int conn_fd = accept(listen_fd, (struct sockaddr*)&cli_addr, &cli_len);
        if (conn_fd < 0) { perror("accept"); continue; }

        char cli_ip[INET_ADDRSTRLEN];
        inet_ntop(AF_INET, &cli_addr.sin_addr, cli_ip, sizeof(cli_ip));
        printf("Accepted connection from %s:%d\n", cli_ip, ntohs(cli_addr.sin_port));

        char buf[BUF_SIZE];
        ssize_t n;
        while ((n = read(conn_fd, buf, BUF_SIZE)) > 0) {
            write(conn_fd, buf, n); // 回射
        }
        if (n < 0) perror("read");
        close(conn_fd);
        printf("Connection closed\n");
    }
    close(listen_fd);
    return 0;
}

客戶端 client.c

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

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

int main() {
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0) { perror("socket"); exit(1); }

    struct sockaddr_in serv_addr = {0};
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(PORT);
    if (inet_pton(AF_INET, SERVER_IP, &serv_addr.sin_addr) <= 0) {
        perror("inet_pton"); exit(1);
    }

    if (connect(sockfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) < 0) {
        perror("connect"); exit(1);
    }
    printf("Connected to server\n");

    char buf[BUF_SIZE];
    while (fgets(buf, BUF_SIZE, stdin)) {
        write(sockfd, buf, strlen(buf));
        ssize_t n = read(sockfd, buf, BUF_SIZE);
        if (n > 0) {
            buf[n] = '\0';
            printf("Echo: %s", buf);
        }
    }
    close(sockfd);
    return 0;
}

編譯測試

gcc server.c -o server
gcc client.c -o client
# 終端1
./server
# 終端2
./client
# 輸入任意文本,服務端原樣返回

八、常見陷阱與最佳實踐

問題原因對策
端口佔用 / Address already in use服務端重啟時前一實例處於 TIME_WAITsetsockopt(SO_REUSEADDR);或等待 2MSL
read 返回 0對端調用 close/shutdown(SHUT_WR),連接正常關閉作為 EOF 處理,退出讀循環
write 返回 EPIPE / SIGPIPE對端已關閉,本端繼續寫入忽略 SIGPIPE (signal(SIGPIPE, SIG_IGN)),檢查 write 返回值
粘包/拆包導致協議解析錯誤TCP 字節流無邊界應用層定義幀格式:長度前綴 / 分隔符 / 固定長度
accept 阻塞無法處理並發單線程循環多進程/多線程/IO 多路復用(select/poll/epoll)
大量 TIME_WAIT 耗盡端口高併發短連接主動關閉SO_REUSEADDR、啟用 tcp_tw_reuse/tcp_tw_recycle(內核參數)、長連接+心跳

九、進階方向

  • IO 多路復用:select/poll/epoll 實現單線程高併發(Nginx/Redis 模型)。
  • 非阻塞 IO:fcntl(fd, F_SETFL, O_NONBLOCK) 配合 epoll ET 模式。
  • 零拷貝:sendfile、splice、mmap 減少用戶/內核態拷貝。
  • TLS/SSL:OpenSSL SSL_CTX/SSL 封裝 Socket 實現加密傳輸。
  • Unix Domain Socket:AF_UNIX 本地進程通信,性能優於循環回 TCP。

結語

Socket 編程是 Linux 網絡開發的基石。熟練掌握 socket/bind/listen/accept/connect/read/write/close 生命週期、地址結構填充、字節序轉換、TCP 流式特性帶來的粘包問題及常見錯誤處理,即可構建穩健高效的網絡應用。進階掌握 epoll、非阻塞、零拷貝、TLS 等技術,可邁向高性能服務端開發領域。

Copyright © Windows教程網 All Rights Reserved