一、引言
在網絡編程的領域中,TCP(Transmission Control Protocol)協議因其可靠的數據傳輸特性而被廣泛應用。在 Linux 環境下,使用 C 或 C++ 進行 TCP 編程可以實現各種強大的網絡應用。本文將深入探討 Linux TCP 編程的各個方面,包括 API 接口的詳細說明、TCP Server 和 TCP Client 的實例代碼,以及完整的測試流程。
二、TCP 編程的 API 接口說明
(一)socket()
函數
int socket(int domain, int type, int protocol);
- 功能:創建一個套接字。
- 參數:
domain
:指定協議族,常見的有AF_INET
(IPv4 網絡協議)和AF_INET6
(IPv6 網絡協議)。type
:套接字類型,對于 TCP 通常使用SOCK_STREAM
。protocol
:指定使用的具體協議,通常設置為 0 以使用默認的 TCP 協議。
- 返回值:成功時返回一個非負的套接字描述符,失敗時返回 -1。
(二)bind()
函數
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
- 功能:將套接字與本地地址和端口綁定。
- 參數:
sockfd
:由socket()
函數返回的套接字描述符。addr
:指向包含地址和端口信息的結構體,如struct sockaddr_in
(IPv4)或struct sockaddr_in6
(IPv6)。addrlen
:addr
結構體的長度。
- 返回值:成功返回 0,失敗返回 -1。
(三)listen()
函數
int listen(int sockfd, int backlog);
- 功能:將套接字設置為監聽狀態,準備接受客戶端的連接請求。
- 參數:
sockfd
:已綁定的套接字描述符。backlog
:指定等待連接隊列的最大長度。
- 返回值:成功返回 0,失敗返回 -1。
(四)accept()
函數
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
- 功能:從已完成連接隊列中取出一個連接,并創建一個新的套接字與客戶端進行通信。
- 參數:
sockfd
:監聽套接字描述符。addr
:用于存儲客戶端的地址信息。addrlen
:用于指定addr
結構體的長度。
- 返回值:成功返回一個新的套接字描述符用于與客戶端通信,失敗返回 -1。
(五)connect()
函數(客戶端使用)
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
- 功能:客戶端向服務器發起連接請求。
- 參數:
sockfd
:套接字描述符。addr
:指向服務器的地址結構體。addrlen
:addr
結構體的長度。
- 返回值:成功返回 0,失敗返回 -1。
(六)send()
和 recv()
函數
send()
函數
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
- 功能:用于發送數據。
- 參數:
sockfd
:套接字描述符。buf
:指向要發送數據的緩沖區。len
:要發送的數據長度。flags
:控制選項,通常設置為 0。
- 返回值:成功返回實際發送的字節數,失敗返回 -1。
recv()
函數
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
- 功能:用于接收數據。
- 參數:
sockfd
:套接字描述符。buf
:用于存儲接收數據的緩沖區。len
:緩沖區的長度。flags
:控制選項,通常設置為 0。
- 返回值:成功返回實際接收的字節數,失敗返回 -1。
(七)close()
函數
int close(int fd);
- 功能:關閉套接字。
- 參數:要關閉的套接字描述符。
三、TCP Server 實例代碼(支持多線程和回顯)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <pthread.h>#define MAX_CONNECTIONS 10 // 最大連接數量pthread_mutex_t connectionCountMutex;
int connectionCount = 0; // 記錄當前連接數void *handle_client(void *arg) {int client_fd = *((int *)arg);char buffer[1024];int bytes_read;while ((bytes_read = recv(client_fd, buffer, sizeof(buffer), 0)) > 0) {// 回顯接收到的數據send(client_fd, buffer, bytes_read, 0);}// 處理客戶端斷開連接pthread_mutex_lock(&connectionCountMutex);connectionCount--;printf("Client disconnected. Current connections: %d\n", connectionCount);pthread_mutex_unlock(&connectionCountMutex);close(client_fd);pthread_exit(NULL);
}int main() {int server_fd, new_socket;struct sockaddr_in address;int addrlen = sizeof(address);int port = 8080; // 服務器監聽的端口// 創建套接字if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {perror("Socket creation failed");exit(EXIT_FAILURE);}// 初始化地址結構體address.sin_family = AF_INET;address.sin_addr.s_addr = INADDR_ANY;address.sin_port = htons(port);// 綁定套接字到本地地址和端口if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {perror("Bind failed");exit(EXIT_FAILURE);}// 開始監聽if (listen(server_fd, MAX_CONNECTIONS) < 0) {perror("Listen failed");exit(EXIT_FAILURE);}printf("Server is listening on port %d...\n", port);pthread_mutex_init(&connectionCountMutex, NULL);while (1) {// 接受客戶端連接if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t *)&addrlen)) < 0) {perror("Accept failed");exit(EXIT_FAILURE);}printf("New connection accepted. Current connections: %d\n", ++connectionCount);// 檢查連接數是否達到上限if (connectionCount > MAX_CONNECTIONS) {printf("Reached maximum connections. Closing new connection.\n");close(new_socket);connectionCount--;continue;}pthread_t thread;if (pthread_create(&thread, NULL, handle_client, &new_socket)!= 0) {perror("Thread creation failed");close(new_socket);connectionCount--;continue;}// 分離線程,使其資源在結束時自動回收pthread_detach(thread);}// 清理pthread_mutex_destroy(&connectionCountMutex);// 關閉服務器套接字close(server_fd);return 0;
}
四、TCP Client 實例代碼
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>int main(int argc, char *argv[]) {if (argc!= 3) {printf("Usage: %s <server_ip> <port>\n", argv[0]);return 1;}int sock = 0;struct sockaddr_in serv_addr;char buffer[1024] = {0};int port = atoi(argv[2]); // 將命令行參數轉換為端口號char *server_ip = argv[1]; // 服務器 IP 地址// 創建套接字if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {printf("\n Socket creation error \n");return -1;}serv_addr.sin_family = AF_INET;serv_addr.sin_port = htons(port);// 將服務器 IP 地址從字符串轉換為網絡地址格式if (inet_pton(AF_INET, server_ip, &serv_addr.sin_addr) <= 0) {printf("\nInvalid address/ Address not supported \n");return -1;}// 連接到服務器if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {printf("\nConnection Failed \n");return -1;}printf("Connected to server\n");while (1) {printf("Enter message: ");fgets(buffer, sizeof(buffer), stdin);// 發送數據send(sock, buffer, strlen(buffer), 0);// 接收服務器響應int valread = recv(sock, buffer, 1024, 0);if (valread <= 0) {printf("Server disconnected\n");break;}printf("Received: %s", buffer);}// 關閉套接字close(sock);return 0;
}
五、測試驗證
使用 GCC 編譯器編譯服務器程序:
gcc server.c -o server -lpthread
./server
同樣使用 GCC 編譯器編譯客戶端程序:
gcc client.c -o client
- 運行客戶端程序,并傳入服務器的 IP 地址和端口作為參數,例如:
./client 127.0.0.1 8080