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

C 語言 TCP Socket 編程:從零構建基礎服務器與客戶端

     編輯:關於C

Socket 編程核心流程

基於 TCP 的 Socket 通信遵循經典的「服務器被動打開,客戶端主動連接」模型。以下是 Linux 環境下的完整實現。

服務器端實現


#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

int main() {
    int res, listenfd, clientfd;
    char buff[BUFSIZ] = "";
    int len;
    
    struct sockaddr_in serveraddr;
    bzero(&serveraddr, sizeof(serveraddr));
    
    serveraddr.sin_family = AF_INET;                    // IPv4
    serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);     // 綁定所有本地接口
    serveraddr.sin_port = htons(50002);                 // 端口 50002
    
    len = sizeof(serveraddr);
    
    listenfd = socket(AF_INET, SOCK_STREAM, 0);         // 創建流式套接字
    bind(listenfd, (struct sockaddr*)&serveraddr, len); // 綁定地址
    listen(listenfd, 23);                               // 監聽,最大排隊 23
    
    printf("等待客戶端連接...\n");
    clientfd = accept(listenfd, NULL, NULL);            // 阻塞等待連接
    printf("已接受一個客戶端連接\n");
    
    while (strncmp(buff, "end", 3)) {
        res = read(clientfd, buff, BUFSIZ);             // 讀取客戶端數據
        if (res <= 0) break;                            // 連接關閉或出錯
        
        write(STDOUT_FILENO, buff, res);                // 輸出到屏幕(文件描述符 1)
        write(clientfd, "server", 6);                   // 回應客戶端
    }
    
    close(clientfd);
    close(listenfd);
    return 0;
}

關鍵函數解析

函數作用關鍵參數
socket()創建套接字AF_INET(IPv4), SOCK_STREAM(TCP)
bind()將套接字綁定到本地 IP/端口sockaddr_in 結構體
listen()轉為被動監聽狀態backlog: 內核排隊最大連接數
accept()從排隊中取出連接,返回新套接字新套接字用於具體通信
read()/write()數據收發返回實際字節數,0 表示對端關閉

網絡字節序轉換

  • htons() / htonl():主機序 → 網絡序(大端)
  • ntohs() / ntohl():網絡序 → 主機序
  • 端口號、IP 地址在網絡傳輸必須使用網絡字節序。

客戶端實現


#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(int argc, char *argv[]) {
    int res, connfd, len;
    struct sockaddr_in connaddr;
    char buff[BUFSIZ];
    
    if (argc < 2) {
        printf("用法: %s <服務器IP>\n", argv[0]);
        exit(1);
    }
    
    bzero(&connaddr, sizeof(connaddr));
    connaddr.sin_family = AF_INET;
    connaddr.sin_addr.s_addr = inet_addr(argv[1]);  // 命令行參數指定服務器 IP
    connaddr.sin_port = htons(50002);               // 必須與服務器一致
    
    connfd = socket(AF_INET, SOCK_STREAM, 0);
    len = sizeof(connaddr);
    
    if (connect(connfd, (struct sockaddr*)&connaddr, len) < 0) {
        perror("連接失敗");
        exit(1);
    }
    
    write(connfd, "Hello!", 7);  // 發送初始消息
    
    while ((res = read(connfd, buff, BUFSIZ)) > 0) {
        printf("收到 %d 字節: ", res);
        write(STDOUT_FILENO, buff, res);  // 顯示服務器回應
        
        res = read(STDIN_FILENO, buff, BUFSIZ);  // 從標準輸入讀取
        if (res > 0) write(connfd, buff, res);   // 發送給服務器
    }
    
    close(connfd);
    return 0;
}

客戶端關鍵點

  • inet_addr() 將點分十進制 IP 字符串轉為網絡序整數。
  • connect() 主動發起三次握手。
  • 雙向通信循環:讀服務器 → 讀鍵盤 → 寫服務器。

編譯與運行


# 服務器
gcc server.c -o server
./server

# 客戶端 (另一終端,假設服務器 IP 為 127.0.0.1)
gcc client.c -o client
./client 127.0.0.1

TCP 連接關閉與 read 返回值語義

文章正確指出:

  • 正常關閉(發送 FIN):read() 返回 0(EOF)。
  • 異常關閉(發送 RST):read() 返回 -1,errno 置 ECONNRESET。
  • ACK 確認包在內核層處理,不會 被應用層 read() 讀到。

因此循環條件 while (read(...) > 0) 是正確模式,0 即跳出關閉套接字。

進階擴展方向

  1. 並發服務器:fork()、pthread、select/poll/epoll 處理多客戶端。
  2. 黏包/拆包:TCP 是流式協議,應用層需自定義協議幀(長度前綴、分隔符)。
  3. 優雅關閉:shutdown(fd, SHUT_WR) 半關閉,配合讀取對端 EOF。
  4. 錯誤處理:EINTR 重試、EAGAIN/EWOULDBLOCK 非阻塞模式。

掌握此基礎模型,是深入網絡編程、理解高性能服務器架構的必經之路。

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