本文完整呈現一個基於 select() 多路復用、fork() 多進程並發、支援連線超時的 TCP 伺服器與對應客戶端。涵蓋 Socket 建立、綁定、監聽、超時檢測、資料收發封裝、進程管理等核心環節,程式碼可直接編譯執行。
| 項目 | 說明 |
|---|---|
| 埠號範圍 | 0-1023 保留(需 root);1024-49151 登記;49152-65535 動態/私有 |
| Socket 類型 | SOCK_STREAM (TCP) / SOCK_DGRAM (UDP) / SOCK_RAW |
| 建立 Socket | socket(AF_INET, SOCK_STREAM, 0) 最後參數通常為 0 |
// 確保讀滿 n 位元組或遇到 EOF/錯誤
ssize_t readn(int fd, void *ptr, size_t n) {
size_t nleft = n;
ssize_t nread;
char *buf = ptr;
while (nleft > 0) {
if ((nread = read(fd, buf, nleft)) < 0) {
if (errno == EINTR) { nread = 0; } // 被訊號中斷,重試
else return -1; // 硬錯誤
} else if (nread == 0) break; // EOF
nleft -= nread;
buf += nread;
if (*buf == '\0') break; // 遇字串結束符提前退出(視協定而定)
}
return n - nleft;
}
// 確保寫滿 n 位元組
ssize_t writen(int fd, const void *ptr, size_t n) {
size_t nleft = n;
ssize_t nwritten;
const char *buf = ptr;
while (nleft > 0) {
if ((nwritten = write(fd, buf, nleft)) <= 0) {
if (errno == EINTR) { nwritten = 0; }
else return -1;
}
nleft -= nwritten;
buf += nwritten;
}
return n - nleft;
}
int main() {
int listenfd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in servaddr = {0};
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(1253);
bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr));
listen(listenfd, 32); // 佇列長度 32
printf("server wait....\n");
fd_set readset;
struct timeval timeout = {1, 0}; // 1 秒超時
while (1) {
FD_ZERO(&readset);
FD_SET(listenfd, &readset);
// select 監聽新連線,帶超時
if (select(listenfd + 1, &readset, NULL, NULL, &timeout) < 0) {
perror("select error");
continue;
}
if (FD_ISSET(listenfd, &readset)) {
struct sockaddr_in cliaddr;
socklen_t clilen = sizeof(cliaddr);
int connfd = accept(listenfd, (struct sockaddr*)&cliaddr, &clilen);
pid_t pid = fork();
if (pid == 0) { // 子進程處理業務
close(listenfd); // 關閉監聽描述符
char buf[100];
bzero(buf, sizeof(buf));
readn(connfd, buf, sizeof(buf));
printf("server read: %s", buf);
const char *resp = "hello\n";
writen(connfd, resp, strlen(resp) + 1);
printf("server send: %s", resp);
close(connfd);
exit(0);
} else if (pid > 0) { // 父進程
close(connfd); // 父進程不處理連線,關閉連線描述符
} else {
perror("fork error");
close(connfd);
}
} else {
// 超時:印一個點示意存活
putchar('.');
fflush(stdout);
}
}
}
int main() {
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in servaddr = {0};
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
servaddr.sin_port = htons(1253);
connect(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr));
const char *msg = "myhaspl\n";
writen(sockfd, msg, strlen(msg) + 1);
printf("client send: %s", msg);
char buf[100];
bzero(buf, sizeof(buf));
readn(sockfd, buf, sizeof(buf));
printf("client read: %s", buf);
close(sockfd);
return 0;
}
$ ./server
server wait....
.........
server read: myhaspl
server send: hello
$ ./client
client send....
client send: myhaspl
client read: hello
accept() 永久阻塞,定期輪詢實現心跳、優雅關閉等邏輯。epoll + 線程池。EINTR,保證應用層協定完整性。connfd、子關 listenfd,避免洩漏與干擾。gcc -o server server.c
gcc -o client client.c
./server &
./client
man 2 select、man 2 poll、man 2 epoll