首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 綜合教學

Linux Socket 網絡編程全景:TCP 協議、三次握手、四次揮手、C 代碼實戰

     編輯:Java 綜合教學

Linux Socket 網絡編程全景:TCP 協議、三次握手、四次揮手、C 代碼實戰

Socket 是網絡編程的基石。本文系統梳理 **TCP/IP 協議分層、Socket 封裝位置、三次握手建立連接、四次揮手斷開連接、核心 API (socket/bind/listen/accept/connect/send/recv/close)、服務端/客戶端完整 C 代碼實戰**,並附帶狀態遷移圖解與常見坑點。

一、TCP/IP 協議棧與 Socket 位置

應用層 (HTTP, FTP, DNS, 自定義協議)
    │
    ▼
傳輸層 (TCP/UDP)  ←── Socket API 位於此層與應用層交界
    │
    ▼
網絡層 (IP, ICMP, ARP)
    │
    ▼
數據鏈路層 (Ethernet, WiFi)
    │
    ▼
物理層

Socket 封裝了 TCP/UDP 協議細節,提供**文件描述符**式的統一接口:`read/write/send/recv/close`。

二、TCP 連接建立:三次握手

客戶端                    服務端
  │                         │
  │----- SYN (seq=x) ----->│  SYN_SENT
  │                         │
  │<--- SYN+ACK (seq=y, ack=x+1) ---│  SYN_RCVD
  │                         │
  │----- ACK (ack=y+1) --->│  ESTABLISHED
  │                         │  ESTABLISHED
  • SYN 佔用 1 個序號,ACK 確認號 = 對方序號 + 1
  • 服務端 `listen()` 後維護**半連接隊列** (SYN_RCVD) 和**全連接隊列** (ESTABLISHED)
  • 客戶端 `connect()` 發起 SYN,阻塞至收到 SYN+ACK 並發送 ACK 返回

三、TCP 連接斷開:四次揮手

客戶端 (主動關閉)          服務端 (被動關閉)
  │                         │
  │----- FIN (seq=u) ----->│  FIN_WAIT_1
  │                         │
  │<--- ACK (ack=u+1) ----│  CLOSE_WAIT
  │      (服務端仍可發數據)   │
  │                         │
  │<--- FIN (seq=v) ------│  LAST_ACK
  │                         │
  │----- ACK (ack=v+1) --->│  TIME_WAIT (2MSL)
  │                         │
  │      (等待 2MSL)         │  CLOSED
  │                         │
  ▼ CLOSED                  ▼ CLOSED
  • **全雙工**特性:每個方向獨立關閉。收到 FIN 表示「對方無數據發送」,但本方仍可發送數據。
  • TIME_WAIT 目的:① 確保最後 ACK 到達 (防丟失重傳) ② 讓網絡中殘留報文過期消失
  • 對應 API:`close()` / `shutdown(SHUT_WR)` 發起主動關閉

四、核心 Socket API 詳解

函數原型關鍵說明
socket`int socket(int domain, int type, int protocol)`創建套接字,返回 fd。`AF_INET` + `SOCK_STREAM` = TCP
bind`int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen)`綁定本地 IP:Port。**服務端必須調用**,客戶端通常不調用 (內核自動分配臨時端口)
listen`int listen(int sockfd, int backlog)`轉為被動套接字,`backlog` 為全連接隊列最大長度 (內核 2.2+ 含半連接隊列)
accept`int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen)`從全連接隊列取出連接,**返回新 fd** (connfd) 用於通信,原 fd 繼續監聽
connect`int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen)`客戶端主動發起連接,阻塞至三次握手完成
send/recv`ssize_t send(int fd, const void *buf, size_t len, int flags)`
`ssize_t recv(int fd, void *buf, size_t len, int flags)`
發送/接收數據。`recv` 返回 0 表示對方關閉 (收到 FIN),-1 表示錯誤
close`int close(int fd)`關閉 fd,引用計數歸零時發起四次揮手

五、服務端完整代碼 (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 8887
#define BACKLOG 20
#define BUF_SIZE 1024

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

    struct sockaddr_in server_addr = {0};
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(PORT);
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 綁定所有網卡

    if (bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {
        perror("bind"); close(server_fd); exit(1);
    }

    if (listen(server_fd, BACKLOG) == -1) {
        perror("listen"); close(server_fd); exit(1);
    }

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

    struct sockaddr_in client_addr;
    socklen_t client_len = sizeof(client_addr);
    int conn_fd = accept(server_fd, (struct sockaddr*)&client_addr, &client_len);
    if (conn_fd == -1) { perror("accept"); close(server_fd); exit(1); }

    printf("Client connected: %s:%d\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));

    char buf[BUF_SIZE];
    while (1) {
        memset(buf, 0, BUF_SIZE);
        ssize_t len = recv(conn_fd, buf, BUF_SIZE, 0);
        if (len == 0) { printf("Client disconnected\n"); break; }
        if (len == -1) { perror("recv"); break; }

        buf[len] = '\0';
        printf("Received: %s", buf);
        if (strcmp(buf, "exit\n") == 0) break;

        send(conn_fd, buf, len, 0); // 回射
    }

    close(conn_fd);
    close(server_fd);
    return 0;
}

六、客戶端完整代碼 (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 8887
#define BUF_SIZE 1024

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

    struct sockaddr_in server_addr = {0};
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(PORT);
    inet_pton(AF_INET, "127.0.0.1", &server_addr.sin_addr); // 服務端 IP

    if (connect(sock_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {
        perror("connect"); close(sock_fd); exit(1);
    }

    printf("Connected to server\n");

    char send_buf[BUF_SIZE], recv_buf[BUF_SIZE];
    while (fgets(send_buf, BUF_SIZE, stdin) != NULL) {
        send(sock_fd, send_buf, strlen(send_buf), 0);
        if (strcmp(send_buf, "exit\n") == 0) break;

        ssize_t len = recv(sock_fd, recv_buf, BUF_SIZE, 0);
        if (len <= 0) break;
        recv_buf[len] = '\0';
        printf("Server echo: %s", recv_buf);
    }

    close(sock_fd);
    return 0;
}

七、編譯運行與驗證

# 編譯
gcc server.c -o server
gcc client.c -o client

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

# 測試
# 客戶端輸入: hello
# 服務端輸出: Received: hello
# 服務端回射: hello
# 客戶端顯示: Server echo: hello

八、進階必知:阻塞 vs 非阻塞、I/O 多路複用

  • 上述代碼為**阻塞模式**:`accept/recv/send/connect` 默認阻塞
  • 高併發服務器需使用 **I/O 多路複用**:`select` / `poll` / `epoll` (Linux) / `kqueue` (BSD) / `IOCP` (Windows)
  • 非阻塞模式:`fcntl(fd, F_SETFL, O_NONBLOCK)`,配合多路複用實現單線程處理萬級連接
  • 現代 C++ 可用 `libevent`、`libuv`、`Boost.Asio`、`muduo` 等高級網絡庫

九、總結

Linux Socket 編程核心掌握:**TCP 狀態機 (三次握手/四次揮手)、核心 API 語義與調用順序、阻塞模式完整流程、服務端/客戶端代碼模板**。在此基礎上,理解 `listen` 隊列、TIME_WAIT 影響、Nagle 算法、滑動窗口、零拷貝 (sendfile/splice)、epoll 邊緣/水平觸發,即可構建高性能網絡服務器。

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