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

Linux Socket 網絡編程完整指南:從原理到實戰

     編輯:關於C

前言:進程間通信(IPC)回顧

本地 IPC 四大類:

  • 消息傳遞:管道、FIFO、消息隊列
  • 同步:互斥量、條件變量、讀寫鎖、信號量
  • 共享內存:匿名/具名
  • 遠程過程調用:Solaris Doors、Sun RPC

網絡通信核心問題:**如何唯一標識一個網絡進程?**

答案:三元組 (IP地址, 協議, 端口) — — TCP/IP 協議族已解決。

什麼是 Socket?

  • 起源:Unix,「一切皆文件」哲學
  • 本質:特殊文件描述符,支援 open → read/write → close 模式
  • 首次文獻:IETF RFC 33 (1970),Stephen Carr 等撰寫

Socket 基礎 API 詳解(以 TCP 為例)

1. socket() — 創建描述符


int socket(int domain, int type, int protocol);
參數常用值說明
domainAF_INET, AF_INET6, AF_UNIX協議族,決定地址格式
typeSOCK_STREAM, SOCK_DGRAM, SOCK_RAWSocket 類型
protocolIPPROTO_TCP, IPPROTO_UDP, 0具體協議,0 為自動選擇

注意:type 與 protocol 須匹配,如 SOCK_STREAM 不能配 IPPROTO_UDP。

2. bind() — 綁定地址


int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

IPv4 地址結構:


struct sockaddr_in {
    sa_family_t sin_family; // AF_INET
    in_port_t sin_port;     // 網絡字節序端口
    struct in_addr sin_addr; // IPv4 地址
};
struct in_addr { uint32_t s_addr; };

字節序陷阱:主機序(大/小端)≠ 網絡序(大端)。**務必用 htons()/htonl() 轉換**,否則會引發難以排查的 Bug。

3. listen() / connect() — 建立連接


int listen(int sockfd, int backlog);      // 服務端:被動監聽
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen); // 客戶端:主動連接
  • listen 將主動 socket 轉為被動,backlog 為等待隊列長度
  • connect 發起三次握手

4. accept() — 接受連接


int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
  • 返回**新**描述符(已連接 socket),原 sockfd 繼續監聽
  • 一個監聽 socket,多個已連接 socket

5. 數據收發


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);
// 更通用:
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);

推薦 send/recv 或 sendmsg/recvmsg,支援標誌位控制。

6. close() — 關閉

釋放描述符,TCP 發起四次揮手。

TCP 連接建立與釋放詳解

三次握手

Client                    Server
  |------------------------>| SYN (seq=x)
  |<------------------------| SYN+ACK (seq=y, ack=x+1)
  |------------------------>| ACK (ack=y+1)
  |        ESTABLISHED      |

四次揮手

Client                    Server
  |------------------------>| FIN (seq=u)
  |<------------------------| ACK (ack=u+1)
  |        CLOSE_WAIT       |
  |<------------------------| FIN (seq=v)
  |------------------------>| ACK (ack=v+1)
  |        TIME_WAIT        |        CLOSED

TIME_WAIT 作用:確保最後 ACK 到達,防止舊連接殘留包干擾新連接(持續 2MSL)。

完整實戰範例:Echo Server/Client

Server


#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define MAXLINE 4096
#define PORT 6666

int main() {
    int listenfd, connfd;
    struct sockaddr_in servaddr;
    char buff[MAXLINE];

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

    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(PORT);

    if (bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) == -1) {
        perror("bind"); exit(1);
    }
    if (listen(listenfd, 10) == -1) {
        perror("listen"); exit(1);
    }

    printf("====== waiting for client ======\n");
    while (1) {
        if ((connfd = accept(listenfd, NULL, NULL)) == -1) {
            perror("accept"); continue;
        }
        int n = recv(connfd, buff, MAXLINE, 0);
        buff[n] = '\0';
        printf("recv: %s\n", buff);
        send(connfd, buff, n, 0); // 回射
        close(connfd);
    }
    close(listenfd);
    return 0;
}

Client


#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define MAXLINE 4096

int main(int argc, char** argv) {
    if (argc != 2) { fprintf(stderr, "usage: %s \n", argv[0]); exit(1); }

    int sockfd;
    struct sockaddr_in servaddr;
    char sendline[MAXLINE], recvline[MAXLINE];

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

    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(6666);
    if (inet_pton(AF_INET, argv[1], &servaddr.sin_addr) <= 0) {
        perror("inet_pton"); exit(1);
    }

    if (connect(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0) {
        perror("connect"); exit(1);
    }

    printf("send msg: ");
    fgets(sendline, MAXLINE, stdin);
    send(sockfd, sendline, strlen(sendline), 0);

    int n = recv(sockfd, recvline, MAXLINE, 0);
    recvline[n] = '\0';
    printf("echo: %s\n", recvline);

    close(sockfd);
    return 0;
}

進階主題(拓展閱讀)

  • I/O 多路複用:select/poll/epoll 實現高併發
  • 非阻塞 I/O:fcntl(fd, F_SETFL, O_NONBLOCK)
  • 信號驅動 I/O:SIGIO
  • 零拷貝:sendfile/splice
  • UDP 編程:sendto/recvfrom,無連接
  • Unix Domain Socket:本機進程間高性能通信

思考題

編寫程序實現:
Server 綁定 192.168.100.168:6666,收到 "Client Query" 印 "Receive Query"
Client 向 192.168.100.168:6666 依次發送三條消息後退出

提示:修改上述範例的 IP/端口與收發邏輯即可。

總結

  • Socket 是網絡編程基石,掌握 socket/bind/listen/accept/connect/read/write/close 七大核心 API
  • 字節序轉換(htons/htonl)是避坑關鍵
  • 理解三次握手/四次揮手狀態機,排查連接異常
  • 實戰中需關注:併發模型、錯誤處理、緩衝區管理、優雅關閉

「一切皆 Socket」,深入內核協議棧,方得網絡編程真諦。

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