Socket API 抽象了網絡協議差異,適用於 IPv4、IPv6、Unix Domain Socket 等。不同協議地址格式不同:
所有 `sockaddr` 結構體開頭相同:前 16 位長度(Linux 無)、後 16 位地址族(`AF_INET`/`AF_INET6`/`AF_UNIX`)。API 參數用 `struct sockaddr*` 接收各類指針,調用時需強轉。
// 字符串 → 二進制 int inet_aton(const char *str, struct in_addr *addr); in_addr_t inet_addr(const char *str); // 已棄用 int inet_pton(int family, const char *str, void *addr); // 支持 IPv4/IPv6 // 二進制 → 字符串 char *inet_ntoa(struct in_addr addr); // 非線程安全 const char *inet_ntop(int family, const void *addr, char *str, size_t len); // 推薦
建立連接(三次握手):
Server: `socket()` → `bind()` → `listen()` → `accept()` 阻塞等待
Client: `socket()` → `connect()` 發送 SYN 阻塞
Server 收到 SYN,回 SYN-ACK,`accept()` 返回
Client 收到 SYN-ACK,回 ACK,`connect()` 返回
雙方建立全雙工連接。
數據傳輸: 典型請求-響應模式:Server `read()` 阻塞 → Client `write()` 請求 → Server 處理 → `write()` 響應 → Client `read()` 接收 → 循環。
關閉連接(四次揮手):
Client `close()` → Server `read()` 返回 0 → Server `close()`。任意一方 `close()` 後雙向關閉;`shutdown()` 可半關閉。
/* server.c — 接收字符轉大寫返回 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define MAXLINE 80
#define SERV_PORT 8000
int main() {
struct sockaddr_in servaddr, cliaddr;
socklen_t cliaddr_len;
int listenfd, connfd;
char buf[MAXLINE];
char str[INET_ADDRSTRLEN];
listenfd = socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY); // 監聽所有網卡
servaddr.sin_port = htons(SERV_PORT);
bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr));
listen(listenfd, 20);
printf("Accepting connections...\n");
while (1) {
cliaddr_len = sizeof(cliaddr);
connfd = accept(listenfd, (struct sockaddr*)&cliaddr, &cliaddr_len);
int n = read(connfd, buf, MAXLINE);
printf("Received from %s at PORT %d\n",
inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
ntohs(cliaddr.sin_port));
for (int i = 0; i < n; i++) buf[i] = toupper(buf[i]);
write(connfd, buf, n);
close(connfd); // 斷開當前連接,listenfd 繼續監聽
}
return 0;
}
/* client.c — 發送命令行參數字符串,顯示響應 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define MAXLINE 80
#define SERV_PORT 8000
int main(int argc, char *argv[]) {
if (argc != 2) {
fprintf(stderr, "usage: ./client message\n");
exit(1);
}
struct sockaddr_in servaddr;
char buf[MAXLINE];
int sockfd, n;
sockfd = socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
servaddr.sin_port = htons(SERV_PORT);
connect(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr));
write(sockfd, argv[1], strlen(argv[1]));
n = read(sockfd, buf, MAXLINE);
printf("Response from server:\n");
write(STDOUT_FILENO, buf, n);
close(sockfd);
return 0;
}
| 函數 | 作用 | 關鍵參數 |
|---|---|---|
| `socket(family, type, protocol)` | 創建套接字,返回文件描述符 | TCP: `AF_INET, SOCK_STREAM, 0` |
| `bind(sockfd, addr, addrlen)` | 綁定固定地址/端口 | 服務端必調用,客戶端通常不調用(內核自動分配) |
| `listen(sockfd, backlog)` | 監聽,設置等待隊列長度 | `backlog` 為內核排隊上限 |
| `accept(sockfd, cliaddr, addrlen)` | 接受連接,返回新 fd `connfd` | 阻塞直到有連接;`cliaddr` 輸出客戶端地址 |
| `connect(sockfd, servaddr, addrlen)` | 主動發起連接 | 客戶端調用,三次握手完成返回 |
| `read/write(fd, buf, len)` | 數據收發 | 同文件 IO,阻塞模式 |
| `close(fd)` | 關閉連接 | 引用計數歸零時發送 FIN |
gcc server.c -o server gcc client.c -o client ./server & # 後台運行服務端 ./client "hello world" # 客戶端發送消息
輸出示例:
Server: `Received from 127.0.0.1 at PORT 12345`
Client: `Response from server: HELLO WORLD`