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

網絡編程實戰:從零手寫 TCP Client 與 Server 完整代碼

     編輯:關於C

Client 端流程與代碼

調用鏈:getaddrinfo() -> socket() -> connect() -> recv()


#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netdb.h>

#define SERVIP "192.168.56.112" // 示例 IP
#define SERVPORT "2349"

int main(int argc, char *argv[])
{
    struct addrinfo hints, *res;
    int status, sockfd, connSta, recvSta;
    char buffer[1024];
    char ipstr[INET_ADDRSTRLEN];
    void *addr;

    // 建議從命令行傳入 IP 和 Port
    if (argc != 3) { fprintf(stderr, "Usage: %s  \n", argv[0]); return 1; }

    memset(&hints, 0, sizeof(hints));
    hints.ai_family = AF_UNSPEC;    // IPv4/IPv6 自適應
    hints.ai_socktype = SOCK_STREAM; // TCP

    status = getaddrinfo(argv[1], argv[2], &hints, &res);
    if (status != 0) { fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(status)); return 2; }

    sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
    if (sockfd < 0) { perror("socket"); return 3; } // socket 失敗返回 -1,非 0

    connSta = connect(sockfd, res->ai_addr, res->ai_addrlen);
    if (connSta != 0) { perror("connect"); return 4; }

    // 顯示連接信息
    struct sockaddr_in *ipv4 = (struct sockaddr_in *)res->ai_addr;
    addr = &(ipv4->sin_addr);
    inet_ntop(res->ai_family, addr, ipstr, sizeof(ipstr));
    printf("Linked: Host = %s, Port = %d\n", ipstr, ntohs(ipv4->sin_port));

    recvSta = recv(sockfd, buffer, sizeof(buffer), 0);
    if (recvSta > 0) buffer[recvSta] = '\0'; // 確保字串結束
    printf("\n%s\n", buffer);

    freeaddrinfo(res);
    close(sockfd);
    return 0;
}

Server 端流程與代碼

調用鏈:getaddrinfo() -> socket() -> bind() -> listen() -> accept() -> send()


#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <arpa/inet.h>

#define SERVPORT "2349"

int main(int argc, char *argv[])
{
    struct addrinfo hints, *res;
    int status, sockfd, connFd, sendSta;
    struct sockaddr_in cliAddr;
    char *msg = "Hello world!";

    if (argc != 2) { fprintf(stderr, "Usage: %s \n", argv[0]); return 1; } // 原代碼邏輯保留

    memset(&hints, 0, sizeof(hints));
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_flags = AI_PASSIVE; // 綁定 0.0.0.0

    status = getaddrinfo(NULL, SERVPORT, &hints, &res);
    if (status != 0) { fprintf(stderr, "getaddrinfo fail\n"); return 2; }

    sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
    bind(sockfd, res->ai_addr, res->ai_addrlen);
    listen(sockfd, 5);
    printf("======== Please Wait Client =========\n");

    while (1)
    {
        socklen_t len = sizeof(cliAddr);
        connFd = accept(sockfd, (struct sockaddr *)&cliAddr, &len);

        char ipstr[INET_ADDRSTRLEN];
        inet_ntop(AF_INET, &(cliAddr.sin_addr), ipstr, sizeof(ipstr));
        printf("client: %s\n", ipstr);

        sendSta = send(connFd, msg, strlen(msg), 0);
        if (sendSta == -1) perror("send");

        close(connFd); // 簡單模型:發送完即關閉連接
    }

    close(sockfd);
    return 0;
}

核心函數詳解

函數角色關鍵點
getaddrinfo地址解析現代替代 gethostbyname/inet_addr,支援 IPv6、服務名解析、線程安全。記得 freeaddrinfo。
socket創建端點返回文件描述符。失敗返回 -1。
bind綁定地址Server 必須。Client 通常隱式綁定 (connect 時自動分配臨時端口)。
listen監聽隊列第二參數 backlog 為內核已完成連接隊列上限 (Linux 實際為 min(backlog, somaxconn))。
accept取出連接阻塞直到新連接到達。返回**新**描述符 connFd,原 sockfd 繼續監聽。
connect發起連接Client 呼叫。阻塞直到三次握手完成或超時/拒絕。
send/recv數據傳輸TCP 流式,可能**分包/黏包**。send 返回實際發送位元組 (可能 < 請求),需循環發送;recv 同理需循環接收直到預期長度或關閉。
close關閉描述符引用計數歸零時發送 FIN 包。shutdown 可單向關閉。

進階建議

  1. 錯誤處理:所有系統調用必須檢查返回值,perror 輸出 errno 信息。
  2. 黏包處理:應用層協議需定義邊界 (長度前綴、分隔符、固定長度)。
  3. 并發化:Server 端加入 fork()、pthread_create() 或 epoll 事件循環。
  4. 優雅關閉:捕獲 SIGINT/SIGTERM,關閉監聽套接字,等待子進程/線程退出。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved