connect() 是客戶端主動發起 TCP 連線的關鍵系統呼叫。與伺服器端的 bind()+listen()+accept() 不同,客戶端不需 bind(),作業系統會自動挑選可用的暫時埠號 (ephemeral port) 並綁定。
#include <sys/types.h>
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *serv_addr, socklen_t addrlen);
sockfd:socket() 回傳的檔案描述符serv_addr:目標伺服器位址結構 (含 IP 與埠號)addrlen:結構大小struct addrinfo hints, *res;
int sockfd;
// 1. 設定 hints (客戶端不需 AI_PASSIVE)
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC; // IPv4 或 IPv6
hints.ai_socktype = SOCK_STREAM; // TCP
// 2. 解析目標位址
getaddrinfo("www.example.com", "3490", &hints, &res);
// 3. 建立 socket
sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
// 4. 發起連線 - 直接使用 res 內容
connect(sockfd, res->ai_addr, res->ai_addrlen);
// 5. 連線成功!現在可用 send()/recv() 通訊
freeaddrinfo(res);
connect() 時自動從暫時埠號範圍 (通常 32768-60999) 挑選一個未使用的埠號getaddrinfo() 可能回傳多個位址 (IPv4 + IPv6),健壯的客戶端應依序嘗試直到成功:
int sockfd = -1;
for (struct addrinfo *p = res; p != NULL; p = p->ai_next) {
sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol);
if (sockfd == -1) continue;
if (connect(sockfd, p->ai_addr, p->ai_addrlen) == 0) {
break; // 成功
}
close(sockfd);
sockfd = -1;
}
if (sockfd == -1) {
// 所有位址都連線失敗
}
freeaddrinfo(res);
errnoECONNREFUSED:目標埠口無程式監聽ETIMEDOUT:連線逾時 (網路不通、防火牆阻擋)ENETUNREACH:網路不可達EADDRNOTAVAIL:無法指派來源位址若不想讓 connect() 阻塞主執行緒:
// 設定非阻塞
int flags = fcntl(sockfd, F_GETFL, 0);
fcntl(sockfd, F_SETFL, flags | O_NONBLOCK);
int ret = connect(sockfd, res->ai_addr, res->ai_addrlen);
if (ret == -1 && errno == EINPROGRESS) {
// 連線進行中,使用 select()/poll()/epoll() 等待可寫事件
// 可寫時表示連線完成 (或失敗),需用 getsockopt(SO_ERROR) 檢查
}
connect() 是客戶端「撥號」的動作。現代寫法同樣依賴 getaddrinfo() 取得位址資訊,**零硬編碼、自動支援 IPv4/IPv6、健壯遍歷多重位址**。掌握同步與非阻塞兩種模式,能應對從簡單 CLI 工具到高併發伺服器的各種場景。下一章將探討伺服器端的 listen() 與 accept()。