在上期的socket套接字的使用詳解中(socket套接字的使用詳解)最后實現的TCP服務器只能處理一個客戶端的請求發送,當有其他客戶端請求連接時會被阻塞。為了能同時處理多個客戶端的連接請求,本期使用多線程的方式來解決。
程序流程
- 創建監聽套接字:使用
socket
函數創建套接字lfd
。 - 綁定套接字:使用
bind
函數將套接字綁定到指定的 IP 地址和端口。 - 監聽連接請求:使用
listen
函數開始監聽連接請求。 - 等待并接受客戶端連接:使用
accept
函數等待并接受客戶端連接請求。 - 獲取客戶端地址信息:使用
getpeername
函數獲取已連接客戶端的 IP 地址和端口號。 - 創建線程處理客戶端請求:使用
pthread_create
函數創建新線程處理該連接,并設置線程為分離屬性。 - 線程處理客戶端通信:在
handle_client
函數中處理與客戶端的通信,包括讀取請求、處理數據、發送響應。 - 主線程繼續監聽新的連接請求:主線程關閉新的客戶端套接字,由新線程處理。繼續循環等待新的客戶端連接請求。
示例代碼:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <pthread.h>
#include <ctype.h>// 處理客戶端通信的函數
void *handle_client(void *arg) {int cfd = *(int *)arg;free(arg);struct sockaddr_in client_addr;socklen_t client_addr_len = sizeof(client_addr);//getpeername 函數獲取與套接字 cfd 關聯的遠程(客戶端)地址信息,并將其存儲在 client_addr 結構體中。getpeername(cfd, (struct sockaddr *)&client_addr, &client_addr_len); char client_ip[INET_ADDRSTRLEN];inet_ntop(AF_INET, &client_addr.sin_addr, client_ip, sizeof(client_ip));int client_port = ntohs(client_addr.sin_port);printf("Client connected: IP [%s], PORT [%d], FD [%d]\n", client_ip, client_port, cfd);char buf[1024];int n;while ((n = read(cfd, buf, sizeof(buf))) > 0) {for (int i = 0; i < n; i++) {buf[i] = toupper(buf[i]);}write(cfd, buf, n);}printf("Client disconnected: IP [%s], PORT [%d], FD [%d]\n", client_ip, client_port, cfd);close(cfd);return NULL;
}int main() {// 創建監聽套接字int lfd = socket(AF_INET, SOCK_STREAM, 0);if (lfd < 0) {perror("socket error");return -1;}// 綁定套接字struct sockaddr_in serv_addr;bzero(&serv_addr, sizeof(serv_addr));serv_addr.sin_family = AF_INET;serv_addr.sin_port = htons(8888);serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);if (bind(lfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {perror("bind error");close(lfd);return -1;}// 監聽連接請求if (listen(lfd, 5) < 0) {perror("listen error");close(lfd);return -1;}while (1) {struct sockaddr_in client_addr;socklen_t client_addr_len = sizeof(client_addr);int *cfd = malloc(sizeof(int));*cfd = accept(lfd, (struct sockaddr *)&client_addr, &client_addr_len);if (*cfd < 0) {perror("accept error");free(cfd);continue;}// 創建線程處理客戶端請求pthread_t tid;pthread_attr_t attr;pthread_attr_init(&attr);pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); // 設置線程分離屬性if (pthread_create(&tid, &attr, handle_client, cfd) != 0) {perror("pthread_create error");close(*cfd);free(cfd);}pthread_attr_destroy(&attr);}close(lfd);return 0;
}
客戶端:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>#define PORT 8888
#define BUFFER_SIZE 1024
#define SERVER_IP "127.0.0.1"int main() {int sock = 0, valread;struct sockaddr_in serv_addr;char buffer[BUFFER_SIZE] = {0};char input_buffer[BUFFER_SIZE] = {0};char *hello = "Hello from client";int opt = 1;// 創建 TCP 套接字if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {perror("socket creation failed");return -1;}// 設置服務器地址結構serv_addr.sin_family = AF_INET;serv_addr.sin_port = htons(PORT);// 將 IPv4 地址從文本轉換為二進制形式if (inet_pton(AF_INET, SERVER_IP, &serv_addr.sin_addr) <= 0) {perror("Invalid address/ Address not supported");return -1;}// 連接服務器if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {perror("Connection Failed");return -1;}printf("Connected to server\n");// 循環發送消息并接收響應while (1) {printf("Enter message to send (or 'exit' to quit): ");fgets(input_buffer, BUFFER_SIZE, stdin);// 去掉輸入的換行符input_buffer[strcspn(input_buffer, "\n")] = 0;// 如果輸入是 'exit',則退出循環if (strcmp(input_buffer, "exit") == 0) {break;}// 發送消息給服務器send(sock, input_buffer, strlen(input_buffer), 0);printf("Message sent to server: %s\n", input_buffer);// 接收服務器的響應valread = read(sock, buffer, BUFFER_SIZE);printf("Server response: %s\n", buffer);memset(buffer, 0, sizeof(buffer));}close(sock);return 0;
}