一、Socket通信基礎
1. Socket通信基本流程
服務器端流程:
-
創建Socket (
socket()
) -
綁定地址和端口 (
bind()
) -
監聽連接 (
listen()
) -
接受連接 (
accept()
) -
數據通信 (
read()
/write()
) -
關閉連接 (
close()
)
客戶端流程:
-
創建Socket (
socket()
) -
連接服務器 (
connect()
) -
數據通信 (
read()
/write()
) -
關閉連接 (
close()
)
2. 關鍵數據結構
struct sockaddr_in
用于存儲IPv4地址信息:
struct sockaddr_in {short sin_family; // 地址族,如AF_INETunsigned short sin_port; // 端口號struct in_addr sin_addr; // IP地址char sin_zero[8]; // 填充字段 };struct in_addr {unsigned long s_addr; // 32位IPv4地址 };
二、服務器代碼詳解
1. 頭文件引入
#include <stdio.h> // 標準輸入輸出 #include <stdlib.h> // 標準庫函數 #include <string.h> // 字符串處理 #include <unistd.h> // POSIX系統調用 #include <sys/types.h> // 系統數據類型 #include <sys/socket.h> // Socket相關函數 #include <netinet/in.h> // Internet地址族
這些頭文件提供了Socket編程所需的基本功能。
2. 錯誤處理函數
void error(const char *msg) {perror(msg); // 打印錯誤信息exit(1); // 異常退出 }
perror()
會根據全局變量errno
打印描述性錯誤信息。
3. 主函數結構
int main(int argc, char *argv[]) {if(argc < 2) {fprintf(stderr,"Port no not provided, Program terminated");exit(1);}// ... 其余代碼 }
檢查命令行參數,確保提供了端口號。
4. 變量聲明
int sockfd, newsockfd, portno; char buffer[255]; struct sockaddr_in serv_addr, cli_addr; socklen_t clilen;
sockfd
: 監聽Socket的文件描述符
newsockfd
: 與客戶端通信的Socket文件描述符
serv_addr
/cli_addr
: 服務器/客戶端地址信息
clilen
: 客戶端地址結構長度
5. 創建Socket
sockfd = socket(AF_INET, SOCK_STREAM, 0); if(sockfd < 0) {error("Error opening Socket."); }
socket()
函數參數:
AF_INET
: IPv4地址族
SOCK_STREAM
: 面向連接的TCP Socket
0
: 默認協議(TCP)
6. 初始化服務器地址
bzero((char *)&serv_addr, sizeof(serv_addr)); portno = atoi(argv[1]);serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = INADDR_ANY; serv_addr.sin_port = htons(portno);
-
bzero()
: 清零內存區域 -
INADDR_ANY
: 監聽所有網絡接口 -
htons()
: 將端口號轉換為網絡字節序(大端)
7. 綁定Socket
if(bind(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0)error("Binding Failed.");
bind()
將Socket與地址和端口綁定。
8. 監聽連接
listen(sockfd,5);
listen()
開始監聽連接,參數5
指定等待連接隊列的最大長度。
9. 接受連接
newsockfd = accept(sockfd, (struct sockaddr*)&cli_addr,&clilen); if(newsockfd < 0)error("Error on Accept");
accept()
接受客戶端連接,返回新的Socket文件描述符用于通信。
10. 計算器業務邏輯
int num1, num2, ans, choice,n; S: n = write(newsockfd,"Enter Number 1 : ",strlen("Enter Number 1")); if (n < 0) error("Error writing to socket"); read(newsockfd, &num1, sizeof(int)); printf("Client - Number 1 is : %d \n",num1);// ... 類似處理num2和choiceswitch (choice) { case 1:ans = num1 + num2;break; case 2:ans = num1 - num2;break; case 3:ans = num1 * num2;break; case 4:ans = num1 / num2;break; case 5:goto Q;break; }write(newsockfd,&ans,sizeof(int)); if(choice != 5)goto S;
這里實現了簡單的計算器功能,使用goto
實現循環邏輯。
11. 關閉連接
Q: close(newsockfd); close(sockfd); return 0;
三、關鍵函數深度解析
1. socket()
int socket(int domain, int type, int protocol);
-
功能:創建通信端點
-
參數:
-
domain
: 通信域(AF_INET, AF_INET6等) -
type
: 通信語義(SOCK_STREAM, SOCK_DGRAM等) -
protocol
: 通常為0,表示默認協議
-
-
返回值:成功返回文件描述符,失敗返回-1
2. bind()
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
-
功能:將Socket與地址綁定
-
參數:
-
sockfd
: Socket文件描述符 -
addr
: 指向地址結構的指針 -
addrlen
: 地址結構大小
-
-
返回值:成功返回0,失敗返回-1
3. listen()
int listen(int sockfd, int backlog);
-
功能:開始監聽連接請求
-
參數:
-
sockfd
: Socket文件描述符 -
backlog
: 等待連接隊列的最大長度
-
-
返回值:成功返回0,失敗返回-1
4. accept()
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
-
功能:接受連接請求
-
參數:
-
addr
: 用于存儲客戶端地址 -
addrlen
: 地址結構大小
-
-
返回值:成功返回新的Socket文件描述符,失敗返回-1
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>void error(const char *msg)
{perror(msg);exit(1);
}
int main(int argc, char *argv[])
{if(argc < 2){fprintf(stderr,"Port no not provided, Program terminated");exit(1);}int sockfd, newsockfd, portno;char buffer[255];struct sockaddr_in serv_addr, cli_addr;socklen_t clilen;sockfd = socket(AF_INET, SOCK_STREAM, 0);if(sockfd < 0){error ("Error opening Socket.");}bzero((char *)&serv_addr, sizeof(serv_addr));portno = atoi(argv[1]);serv_addr.sin_family = AF_INET;serv_addr.sin_addr.s_addr = INADDR_ANY;serv_addr.sin_port = htons(portno);if(bind(sockfd , (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0)error("Binding Failed.");listen(sockfd,5);clilen = sizeof(cli_addr);newsockfd = accept(sockfd, (struct sockaddr*)&cli_addr,&clilen);if(newsockfd < 0)error("Error on Accept");int num1, num2, ans, choice,n;S: n = write(newsockfd,"Enter Number 1 : ",strlen("Enter Number 1")); if (n < 0) error("Error writing to socket");read(newsockfd, &num1, sizeof(int));printf("Client - Number 1 is : %d \n",num1);n = write(newsockfd,"Enter Number 2 : ",strlen("Enter Number 2")); if (n < 0) error("Error writing to socket");read(newsockfd, &num2, sizeof(int));printf("Client - Number 2 is : %d \n",num2);n = write(newsockfd,"Enter your choice: \n1.Addition\n2.subtraction\n3.Multiplication\n4.Division\n5.Exit\n",strlen("Enter your choice: \n1.Addition\n2.subtraction\n3.Multiplication\n4.Division\n5.Exit\n"));if(n < 0) error("ERROR writing to socket");read(newsockfd, &choice, sizeof(int));printf("Client - choice is :%d\n",choice);switch (choice){case 1:ans = num1 + num2;break;case 2:ans = num1 - num2;break;case 3:ans = num1 * num2;break;case 4:ans = num1 / num2;break;case 5:goto Q;break;}write(newsockfd,&ans,sizeof(int));if(choice != 5)goto S;Q: close(newsockfd);close(sockfd);return 0;}
/*
filename server_ipaddress portnoargc[0] filename
argv[1] serve_ipaddress
argv[2] portno
*/#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>void error(const char *msg)
{perror(msg);exit(0);
}
int main(int argc, char *argv[])
{int sockfd, portno, n;char buffer[256];struct sockaddr_in serv_addr;struct hostent *server;if (argc < 3){fprintf(stderr, "usage %s hostname port\n", argv[0]);exit(1);}portno = atoi(argv[2]);sockfd = socket(AF_INET, SOCK_STREAM, 0);if(sockfd < 0){error ("Error opening Socket.");}server = gethostbyname(argv[1]);if(server == NULL){fprintf(stderr, "Error,no such host");}bzero((char *)&serv_addr, sizeof(serv_addr));serv_addr.sin_family = AF_INET;bcopy((char *)server->h_addr, (char *)&serv_addr.sin_addr.s_addr, server->h_length);serv_addr.sin_port = htons(portno);if(connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0){error("Connection Failed");}int num1, num2, choice, ans;S:bzero(buffer,256);n = read (sockfd, buffer, 255);if( n < 0 )error("ERROR reading from socket");printf("Server - %s\n",buffer);scanf("%d",&num1);write(sockfd, &num1,sizeof(int));bzero(buffer,256);n = read (sockfd, buffer, 255);if( n < 0 )error("ERROR reading from socket");printf("Server - %s\n",buffer);scanf("%d",&num2);write(sockfd, &num2,sizeof(int));bzero(buffer,256);n = read (sockfd, buffer, 255);if( n < 0 )error("ERROR reading from socket");printf("Server - %s\n",buffer);scanf("%d",&choice);write(sockfd, &choice,sizeof(int));if (choice == 5)goto Q;read(sockfd, &ans ,sizeof(int));printf("Server- The answer is :%d\n",ans);if(choice !=5)goto S;Q:printf("You have selected to exit.Exit Successful");close(sockfd);return 0;}
一、函數參數記憶框架(TCP Socket版)
1. 核心思維:"3W1H"模型
-
Where (地址相關):
sockaddr_in
,addrlen
-
What (數據相關):
buffer
,strlen
-
Which (標識符):
sockfd
,newsockfd
-
How (方式):
flags
(通常填0)
2. 函數參數速記表
函數 | 參數順序助記口訣 | 必須記住的參數 |
---|---|---|
socket() | "家-門-鑰匙" | AF_INET , SOCK_STREAM , 0 |
bind() | "門牌-地址本-地址長度" | sockfd , (struct sockaddr*)&serv_addr , sizeof(serv_addr) |
listen() | "門牌-候客室大小" | sockfd , 5 (backlog) |
accept() | "門牌-客戶登記表-表長度" | sockfd , cli_addr , &clilen |
connect() | "門牌-目的地地址-地址長度" | sockfd , serv_addr , sizeof(serv_addr) |
read() /write() | "信箱-紙條-紙條大小" | newsockfd , buffer , sizeof(buffer) |
二、參數分類記憶法
1. 地址家族三件套
serv_addr.sin_family = AF_INET; // IPv4 serv_addr.sin_addr.s_addr = INADDR_ANY; // 所有IP serv_addr.sin_port = htons(portno); // 端口轉換
-
記憶口訣:"家-門-方向牌"(family-address-port)
2. 類型轉換四重奏
// 指針轉換 (struct sockaddr*)&serv_addr// 字節序轉換 htons() // host to network short htonl() // host to network long ntohs() // network to host short ntohl() // network to host long
-
記憶技巧:"h→n是出門,n→h是回家"
三、實戰填參模板
1. 服務端標準流程
// 1. 創建socket(三固定參數) int sockfd = socket(AF_INET, SOCK_STREAM, 0);// 2. 綁定地址(結構體強制轉換) bind(sockfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));// 3. 接受連接(注意&clilen) accept(sockfd, (struct sockaddr*)&cli_addr, &clilen);
2. 客戶端連接模板
connect(sockfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
四、常見參數填錯場景
錯誤場景 | 正確寫法 | 記憶要點 |
---|---|---|
忘記地址結構體轉換 | (struct sockaddr*)&serv_addr | "套外套"法則 |
傳值 vs 傳地址混淆 | &clilen 而不是 clilen | accept()要修改長度值 |
字節序未轉換 | htons(portno) | 所有網絡傳輸的數據都要轉換 |
buffer大小計算錯誤 | read(fd, buf, sizeof(buf)) | 用sizeof而不是strlen |
五、調試技巧
-
參數檢查清單:
-
所有網絡字節序轉換了嗎?
-
地址結構體轉換了嗎?
-
長度參數傳地址了嗎?
-
錯誤處理都寫了嗎?
-
-
GDB調試命令:
bash
復制
(gdb) p serv_addr # 查看地址結構體 (gdb) p &serv_addr # 確認指針類型
六、類比記憶法
函數 | 現實類比 | 關鍵參數對應關系 |
---|---|---|
socket() | 買手機 | 手機類型=AF_INET |
bind() | 辦手機卡 | 電話號碼=portno |
listen() | 開機 | 最大未接來電=backlog |
accept() | 接聽來電 | 來電顯示=cli_addr |
connect() | 撥打電話 | 對方號碼=serv_addr |
七、進階記憶法(協議棧層次)
應用層:buffer數據 傳輸層:SOCK_STREAM/SOCK_DGRAM 網絡層:AF_INET/AF_INET6 鏈路層:系統自動處理
記住:從上到下越來越底層,從下到上越來越抽象