文章目錄
- Linux C語言網絡編程詳細入門教程:如何一步步實現TCP服務端與客戶端通信
- 前言
- 一、網絡通信基礎概念
- 二、服務端與客戶端的完整流程圖解
- 三、每一步的詳細講解和代碼示例
- 1. 創建Socket(服務端和客戶端都要)
- 2. 綁定本地地址和端口(服務端用bind)
- 3. 設置監聽(服務端listen)
- 4. 等待并接受連接(服務端accept)
- 5. 客戶端發起連接(connect)
- 6. 數據收發(read/write)
- 7. 關閉socket
- 四、流程圖&示意
- 五、錯誤處理機制(errno、perror、strerror)
- 六、完整最簡服務端和客戶端代碼范例
- 1. 服務端示例
- 2. 客戶端示例
- 七、通用經驗和常見問題排查
- 八、圖解數據流和socket關系
- 九、總結
Linux C語言網絡編程詳細入門教程:如何一步步實現TCP服務端與客戶端通信
前言
本文面向初學者,目標是讓你“一看就懂、能馬上動手實踐”。從零講起,手把手梳理服務端與客戶端的構建全過程,每個函數、參數、典型用法、數據流向、底層機制、注意事項全部細細拆解,讓你徹底明白如何讓兩個程序通過網絡可靠通信。
一、網絡通信基礎概念
-
什么是Socket?
- Socket(套接字)是操作系統為進程之間通過網絡發送和接收數據而提供的一套“接口”。
- 類比現實世界,就是一臺機器(主機)上的“電話插孔”,只有插上電話線(建立連接)你們才能說話。
- Socket抽象了所有底層的網絡細節,為開發者提供了“像讀寫文件一樣”進行網絡通信的方式。
-
IP和端口
- IP:主機的唯一網絡地址,相當于“電話號碼”。
- 端口:主機內部區分不同網絡服務的編號,相當于“分機號”。
-
TCP通信流程(面向連接)
- 服務端先開啟,監聽一個IP+端口。
- 客戶端主動連接服務端的IP+端口。
- 建立連接(三次握手)。
- 雙方可以互相發送和接收數據。
- 通信完成后關閉連接。
二、服務端與客戶端的完整流程圖解
服務端主要流程:
- 創建Socket
- 綁定本地IP和端口(bind)
- 設置為監聽狀態(listen)
- 死循環等待客戶端連接(accept)
- 收發數據(read/write)
- 關閉通信(close)
客戶端主要流程:
- 創建Socket
- 配置服務器IP和端口
- 發起連接請求(connect)
- 收發數據(read/write)
- 關閉通信(close)
三、每一步的詳細講解和代碼示例
1. 創建Socket(服務端和客戶端都要)
作用:告訴內核“我要用網絡通信”,創建一個通信端點。
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
- domain:協議族,常用
AF_INET
(IPv4)。 - type:套接字類型,常用
SOCK_STREAM
(TCP,可靠流)。 - protocol:協議,通常填0,表示由系統自動選擇。
舉例:
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == -1) {perror("socket error");exit(1);
}
- 返回值:成功時是一個“文件描述符”,失敗返回-1。
2. 綁定本地地址和端口(服務端用bind)
作用:明確告訴操作系統“我用哪個IP+端口”來等待客戶端連接。只有bind了,別人才能找到你。
#include <netinet/in.h>
#include <string.h>
struct sockaddr_in servaddr;
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET; // 協議族
servaddr.sin_port = htons(8888); // 端口(本地字節序轉網絡字節序)
servaddr.sin_addr.s_addr = htonl(INADDR_ANY); // 本機所有IPint ret = bind(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr));
if (ret == -1) {perror("bind error");exit(1);
}
htons
/htonl
是將主機字節序轉為網絡字節序(大端)。INADDR_ANY
讓你的服務監聽本機所有網卡(IP)。
3. 設置監聽(服務端listen)
作用:讓socket進入“監聽”狀態,準備接收連接請求。
int listen(int sockfd, int backlog);
- sockfd:剛才創建并bind過的socket。
- backlog:內核排隊等待連接的最大數量。
舉例:
if (listen(sockfd, 128) == -1) {perror("listen error");exit(1);
}
- 這時操作系統會幫你排隊管理那些“想要連你”的客戶端。
4. 等待并接受連接(服務端accept)
作用:阻塞等待客戶端“來電”,接聽一個新連接,為每個連接分配一個新的socket。
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
- sockfd:監聽socket。
- addr:傳出參數,獲取客戶端的IP+端口等信息。
- addrlen:addr結構體大小(調用前要賦初值)。
舉例:
struct sockaddr_in cliaddr;
socklen_t cliaddr_len = sizeof(cliaddr);
int connfd = accept(sockfd, (struct sockaddr*)&cliaddr, &cliaddr_len);
if (connfd == -1) {perror("accept error");continue; // 或exit(1)
}
- connfd:每個客戶端連接都會分配一個新socket,獨立通信。
- 注意:監聽socket(sockfd)只負責“等電話”,不能直接收發數據,后面通信都用connfd。
5. 客戶端發起連接(connect)
作用:主動“打電話”給服務端,發起三次握手,連接指定IP+端口。
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
客戶端配置服務器地址:
struct sockaddr_in servaddr;
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(8888);
inet_pton(AF_INET, "192.168.1.100", &servaddr.sin_addr);if (connect(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) == -1) {perror("connect error");exit(1);
}
- inet_pton 用于將點分十進制IP字符串轉為網絡字節序整數。
6. 數據收發(read/write)
作用:收發字節流數據,就像讀寫文件一樣。
write()
發送數據到對方read()
從對方接收數據
例子(雙方都類似):
char buf[1024];
// 發送
write(sockfd, "hello", 5);
// 接收
int n = read(sockfd, buf, sizeof(buf)-1);
if (n > 0) {buf[n] = '\0';printf("收到: %s\n", buf);
}
- 注意:read和write返回值要判斷,<=0說明對方關閉了連接或出錯。
- 服務器處理建議:每個連接完成后記得close(connfd)。
7. 關閉socket
作用:釋放系統資源,斷開連接。
close(sockfd); // 對服務端監聽socket、通信socket、客戶端socket都適用
四、流程圖&示意
服務器端流程 客戶端流程
--------------------------------------------------------
socket() socket()| |
bind() connect()| |
listen() || ---------三次握手
accept() <--------+ || | |
read()/write() <---+---> read()/write()| | |
close() close() close()
五、錯誤處理機制(errno、perror、strerror)
- 每一步系統調用都可能出錯,一定要檢查返回值(-1 代表失敗)。
- 出錯后操作系統會設置errno變量(int類型,存放錯誤碼)。
- perror(“描述”) 會直接把你的描述和錯誤原因一起打印到標準錯誤輸出。
- strerror(errno) 把錯誤碼轉為字符串,可以寫日志或文件。
例子:
if (bind(sockfd, ...) == -1) {perror("bind error");// fprintf(logfile, "bind error: %s\n", strerror(errno));exit(1);
}
- 建議每個步驟都這樣處理,排查故障時一清二楚。
六、完整最簡服務端和客戶端代碼范例
1. 服務端示例
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <arpa/inet.h>#define SERVER_PORT 8888int main() {int listenfd = socket(AF_INET, SOCK_STREAM, 0);struct sockaddr_in servaddr;memset(&servaddr, 0, sizeof(servaddr));servaddr.sin_family = AF_INET;servaddr.sin_port = htons(SERVER_PORT);servaddr.sin_addr.s_addr = htonl(INADDR_ANY);if (bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) == -1) {perror("bind error");exit(1);}if (listen(listenfd, 128) == -1) {perror("listen error");exit(1);}printf("Server is listening...\n");while (1) {struct sockaddr_in cliaddr;socklen_t cliaddr_len = sizeof(cliaddr);int connfd = accept(listenfd, (struct sockaddr*)&cliaddr, &cliaddr_len);if (connfd == -1) {perror("accept error");continue;}char buf[1024];int n = read(connfd, buf, sizeof(buf)-1);if (n > 0) {buf[n] = '\0';printf("client says: %s\n", buf);write(connfd, buf, n); // 回顯}close(connfd);}close(listenfd);return 0;
}
2. 客戶端示例
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <arpa/inet.h>#define SERVER_PORT 8888
#define SERVER_IP "127.0.0.1"int main() {int sockfd = socket(AF_INET, SOCK_STREAM, 0);struct sockaddr_in servaddr;memset(&servaddr, 0, sizeof(servaddr));servaddr.sin_family = AF_INET;servaddr.sin_port = htons(SERVER_PORT);inet_pton(AF_INET, SERVER_IP, &servaddr.sin_addr);if (connect(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) == -1) {perror("connect error");exit(1);}char sendbuf[1024] = "hello server";write(sockfd, sendbuf, strlen(sendbuf));char recvbuf[1024];int n = read(sockfd, recvbuf, sizeof(recvbuf)-1);if (n > 0) {recvbuf[n] = '\0';printf("server says: %s\n", recvbuf);}close(sockfd);return 0;
}
七、通用經驗和常見問題排查
- 端口被占用,bind出錯:先用
netstat -ntlp
查端口占用,或改端口再試。 - connect失敗:IP、端口寫錯?服務器沒開?防火墻攔截?
- read/write出錯或為0:對方關閉了連接,需及時close。
- 每個連接用獨立socket,主循環不要關listenfd。
- 大項目建議引入多線程或select/epoll提升并發能力。
八、圖解數據流和socket關系
| 客戶端 | 網絡 | 服務端 |
+--------+---------------+-----------------------+
| | ---connect--> | [listenfd] |
| | <---三次握手--| |
| | | accept()產生[connfd] |
| |<->read/write<>| [connfd]<->[listenfd] |
| | ---close----->| [connfd closed] |
- listenfd負責排隊監聽,不用于通信。connfd負責與客戶端通信。
九、總結
- 先socket(),再bind()(服務端),然后listen(),accept()連接,read/write通信,close()結束。客戶端用connect()主動連接。
- 每一步都檢查錯誤,配合perror/strerror打印詳細信息,便于調試和維護。
- IP、端口、字節序、緩沖區管理要細心。
- 建議將代碼拆分成模塊,錯誤處理、日志、收發通信各自封裝。
只要理解并掌握上面每一步、每個關鍵函數的使用,你就能獨立搭建出穩定可靠的C語言網絡通信程序!每次遇到問題都可以翻回來看,一步步排查流程,問題迎刃而解。