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

Beej's Guide to Network Programming 系列連載 09:socket() — 取得檔案描述符的藝術

     編輯:關於C語言

前言:socket() 的真正用法

許多教學讓初學者死記 PF_INET、SOCK_STREAM、0 三個參數,實則硬編碼是大忌。現代正確做法:直接使用 getaddrinfo() 回傳的結果填充 socket() 參數,達成完全的協議無關性。

函數原型

#include <sys/types.h>
#include <sys/socket.h>

int socket(int domain, int type, int protocol);

傳統硬編碼參數對照表

參數IPv4 值IPv6 值說明
domainPF_INETPF_INET6協議族
typeSOCK_STREAMSOCK_STREAMTCP 串流
typeSOCK_DGRAMSOCK_DGRAMUDP 資料報
protocol0 或 IPPROTO_TCP0 或 IPPROTO_TCP通常填 0 讓系統自選

關鍵釐清:PF_* vs AF_*

  • PF_INET / PF_INET6:Protocol Family,用於 socket() 的 domain 參數
  • AF_INET / AF_INET6:Address Family,用於 sockaddr 結構的 sa_family 欄位
  • 實務上兩者數值相同,可互換使用,但語意上區分「協議族」與「位址族」
  • 詳見《Unix Network Programming》卷一 第 4 章第 2 節 "AF_xxx Versus PF_xxx"

現代最佳實踐:零硬編碼

int s;
struct addrinfo hints, *res;

// 1. 設定 hints 並呼叫 getaddrinfo()
memset(&hints, 0, sizeof(hints));
hints.ai_family   = AF_UNSPEC;     // 不指定 IPv4/IPv6
hints.ai_socktype = SOCK_STREAM;   // TCP
getaddrinfo("www.example.com", "http", &hints, &res);

// 2. 直接使用 res 內容建立 socket
// 實務上應遍歷 res 鏈結串列尋找可用節點,此處簡化示意
s = socket(res->ai_family, res->ai_socktype, res->ai_protocol);

if (s == -1) {
    perror("socket");
    // 應嘗試 res->ai_next 節點...
}

為什麼這樣做更好?

  1. 簡潔:一行程式碼取代三個魔術數字
  2. 正確:ai_protocol 可能非 0(某些特殊協議),直接使用避免假設
  3. 可移植:同一套程式碼在 IPv4、IPv6、甚至未來新協議皆可運作
  4. 可維護:協議細節集中在 hints 設定處

回傳值與錯誤處理

  • 成功:回傳非負整數的檔案描述符 (socket descriptor)
  • 失敗:回傳 -1,並設定全域 errno
  • 常見錯誤:EMFILE (進程檔案描述符耗盡)、ENFILE (系統檔案表滿)、EPROTONOSUPPORT (協議不支援)

結語

socket() 只是取得一個「通訊端點代號」,真正的協議綁定發生在 bind()/connect() 階段。永遠從 getaddrinfo() 取得參數,是寫出現代、可維護、支援雙協議棧網路程式的鐵律。下一章將探討 bind() — 如何將這個描述符綁定到特定埠口。

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