socket → bind → listen → accept 迴圈,連線數計數、超限拒絕、建立工作者執行緒。WaitForMultipleObjects 等待所有工作者結束。#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include
#include
#include
#include // _beginthreadex
#pragma comment(lib, "ws2_32.lib")
#define MAX_CLIENTS 3
#define BUF_SIZE 4096
// 執行緒參數結構
typedef struct {
SOCKET clientSock;
int clientId;
} ThreadArg;
unsigned __stdcall WorkerThread(void* arg)
{
ThreadArg* ta = (ThreadArg*)arg;
SOCKET sock = ta->clientSock;
int id = ta->clientId;
free(ta); // 參數結構體在執行緒內釋放
char buf[BUF_SIZE];
int recvLen;
// 歡迎訊息
const char* welcome = "Welcome! Type 'exit' to quit.\r\n";
send(sock, welcome, (int)strlen(welcome), 0);
printf("[SYS] Client #%d connected.\n", id);
while (1) {
recvLen = recv(sock, buf, BUF_SIZE - 1, 0);
if (recvLen <= 0) break; // 斷線或錯誤
buf[recvLen] = '\0';
printf("[Client #%d] %s", id, buf);
if (strncmp(buf, "exit", 4) == 0) {
const char* bye = "Goodbye!\r\n";
send(sock, bye, (int)strlen(bye), 0);
break;
}
// 回聲
send(sock, buf, recvLen, 0);
}
closesocket(sock);
printf("[SYS] Client #%d disconnected.\n", id);
_endthreadex(0);
return 0;
}
int main()
{
WSADATA wsa;
if (WSAStartup(MAKEWORD(2,2), &wsa) != 0) {
fprintf(stderr, "WSAStartup failed: %d\n", WSAGetLastError());
return 1;
}
SOCKET listenSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (listenSock == INVALID_SOCKET) {
fprintf(stderr, "socket failed: %d\n", WSAGetLastError());
WSACleanup(); return 1;
}
struct sockaddr_in addr = {0};
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY); // 0.0.0.0
addr.sin_port = htons(9000);
if (bind(listenSock, (struct sockaddr*)&addr, sizeof(addr)) == SOCKET_ERROR) {
fprintf(stderr, "bind failed: %d\n", WSAGetLastError());
closesocket(listenSock); WSACleanup(); return 1;
}
if (listen(listenSock, SOMAXCONN) == SOCKET_ERROR) {
fprintf(stderr, "listen failed: %d\n", WSAGetLastError());
closesocket(listenSock); WSACleanup(); return 1;
}
printf("Server listening on 0.0.0.0:9000, max clients=%d\n", MAX_CLIENTS);
HANDLE threads[MAX_CLIENTS] = {0};
int clientCount = 0;
while (1) {
struct sockaddr_in clientAddr;
int addrLen = sizeof(clientAddr);
SOCKET clientSock = accept(listenSock, (struct sockaddr*)&clientAddr, &addrLen);
if (clientSock == INVALID_SOCKET) {
fprintf(stderr, "accept failed: %d\n", WSAGetLastError());
continue;
}
if (clientCount >= MAX_CLIENTS) {
const char* msg = "Server busy: max clients reached.\r\n";
send(clientSock, msg, (int)strlen(msg), 0);
closesocket(clientSock);
printf("[SYS] Rejected connection (max clients).\n");
continue;
}
ThreadArg* arg = (ThreadArg*)malloc(sizeof(ThreadArg));
arg->clientSock = clientSock;
arg->clientId = clientCount + 1;
HANDLE hThread = (HANDLE)_beginthreadex(NULL, 0, WorkerThread, arg, 0, NULL);
if (!hThread) {
free(arg);
closesocket(clientSock);
fprintf(stderr, "_beginthreadex failed\n");
continue;
}
threads[clientCount++] = hThread;
printf("[SYS] Accepted client #%d, total=%d\n", arg->clientId, clientCount);
}
// 正常情況不會到達這裡(無限迴圈),關閉邏輯省略
WaitForMultipleObjects(clientCount, threads, TRUE, INFINITE);
for (int i = 0; i < clientCount; i++) CloseHandle(threads[i]);
closesocket(listenSock);
WSACleanup();
return 0;
}
| 技術點 | 說明 |
|---|---|
_beginthreadex vs CreateThread | **必須用 _beginthreadex**,它會正確初始化 CRT 執行緒區域儲存 (TLS),避免記憶體洩漏與 errno 汙染。 |
| 參數傳遞 | 動態配置 ThreadArg,執行緒入口第一時間 free,主執行緒不負責釋放。 |
| 連線數限制 | 原子讀寫 clientCount(單執行緒 accept 迴圈天然安全),超限即拒絕並回應。 |
| 優雅關閉 | 工作者執行緒收到 exit 或 recv==0 即關閉 Socket、結束執行緒。 |
| 資源清理 | 主執行緒 WaitForMultipleObjects 等待所有工作者,再 CloseHandle、closesocket、WSACleanup。 |
// gcc -pthread server.c -o server
#include
#include
#include
#include
#include
#include
#include
#define MAX_CLIENTS 3
#define BUF_SIZE 4096
typedef struct { int sockfd; int id; } ThreadArg;
void* worker(void* arg) {
ThreadArg* ta = (ThreadArg*)arg;
int sock = ta->sockfd;
int id = ta->id;
free(ta);
char buf[BUF_SIZE];
ssize_t n;
const char* welcome = "Welcome! Type 'exit' to quit.\n";
write(sock, welcome, strlen(welcome));
printf("[SYS] Client #%d connected.\n", id);
while ((n = read(sock, buf, BUF_SIZE - 1)) > 0) {
buf[n] = '\0';
printf("[Client #%d] %s", id, buf);
if (strncmp(buf, "exit", 4) == 0) {
write(sock, "Goodbye!\n", 9);
break;
}
write(sock, buf, n); // echo
}
close(sock);
printf("[SYS] Client #%d disconnected.\n", id);
return NULL;
}
int main() {
int listenfd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in addr = {0};
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr.sin_port = htons(9000);
bind(listenfd, (struct sockaddr*)&addr, sizeof(addr));
listen(listenfd, SOMAXCONN);
printf("Server listening on 0.0.0.0:9000, max clients=%d\n", MAX_CLIENTS);
pthread_t threads[MAX_CLIENTS];
int count = 0;
while (1) {
struct sockaddr_in cli;
socklen_t len = sizeof(cli);
int connfd = accept(listenfd, (struct sockaddr*)&cli, &len);
if (connfd < 0) { perror("accept"); continue; }
if (count >= MAX_CLIENTS) {
write(connfd, "Server busy\n", 12);
close(connfd);
continue;
}
ThreadArg* arg = malloc(sizeof(ThreadArg));
arg->sockfd = connfd;
arg->id = ++count;
pthread_create(&threads[count-1], NULL, worker, arg);
pthread_detach(threads[count-1]); // 自動回收,簡化主執行緒
}
// 不會執行到這裡
close(listenfd);
return 0;
}
IOCP (Windows) / epoll + 固定執行緒池 (Linux) 解決 C10K 問題。setsockopt(SO_RCVTIMEO) 或應用層心跳檢測死連線。SIGINT/SIGTERM,優雅關閉監聽 Socket、通知工作者退出。「每連線一執行緒 + 連線數上限」是經典並發模型的入門範本。掌握 _beginthreadex/pthread_create 正確用法、參數傳遞所有權轉移、Socket 資源確定性釋放,即可寫出穩定的 TCP 伺服器雛形。高併發場景請進階至 **IOCP / epoll + 非阻塞 I/O + 執行緒池** 架構。