我在之前的文章中曾說過,我現在開始負責車輛以太網應用層協議棧的開發和維護。
以太網通信說白了也屬於計算機通信的範疇,因此網絡通信的5層模型仍然適用於車輛通信領域。
既然設計到ECU間的網絡通信,那麼socket網絡編程的知識一定是必不可少的。
今天這篇文章就總結我這周學習的socket網絡編程的知識。
Socket編程常用的函數
首先,下圖是client與server之間的通信流程圖。
根據上面的socket通信流程圖,socket編程常用的函數如下:
-
客戶端使用socket(), connect(), send(), recv(), close();
-
服務端使用socket(), bind(), listen(), accept(), recv(), send(), close();
Socket通信的三次握手和四次揮手
Socket建立連接三次握手的流程
-
第一次握手:client端先向server端發送連接請求
-
第二次握手:server收到後,回覆ACK給client
-
第三次握手:client端收到ACK後,回覆ACK給server
至此,client與server之間的網絡連接建立,可以順利進行數據傳輸。
Socket斷開連接四次揮手的流程
-
第一次揮手:client向server發送斷開連接請求
-
第二次揮手:server收到client的請求,回覆ACK給client (這次揮手錶示server收到了client的請求,但是並不會立即斷開連接,可能還有未發送的數據)
-
第三次揮手:server向client發送斷開連接請求(這次揮手錶示server端的數據已經發送完畢,client端可以斷開)
-
第四次揮手:client收到了server的請求,發送ACK給server。之後client端處於等待狀態,過了一段時間後將socket關閉
至此,client與server之間的網絡連接斷開。
簡單實現進程間的Socket的通信
實現步驟如下:
(1)使用socket套接字,並選擇TCP/IP協議,端口號爲6000
(2)客戶端發送“this is a test”到服務端
(3)服務端收到後打印字符串,並回復“test ok”到客戶端
(4)通信結束,斷開連接
客戶端socket通信的實現步驟
-
socket()創建socket
-
設置socket的屬性
-
connect()向服務端發起連接
-
send()向服務端發送消息
-
recv()等待服務端的消息
-
close()關閉socket,回收資源
//client.c
#include<stdio.h>
#include<sys/socket.h>
#include<stdlib.h>
#include<netinet/in.h>
#define PORT 6000
int main()
{
int socketfd, error;
struct sockaddr_in serv_addr;
//創建socket
socketfd = socket(AF_INET, SOCK_STREAM, 0);
//設置socket的屬性
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(PORT);
serv_addr.sin_addr.s_addr = INADDR_ANY;
//向服務端發起連接
error = connect(socketfd, &serv_addr, sizeof(struct sockaddr));
if(0 != error)
{
printf("connect server error.");
return 0;
}
//向服務端發送消息
char *buff = "this is a test";
error = send(socketfd, buff, strlen(buff), 0);
if(-1 == error)
{
printf("send message to server error.");
return 0;
}
//等待接收服務端的消息
char recv_buff[1024] = {0};
error = recv(socketfd, recv_buff, 1024,0);
if(-1 == error)
{
printf("receive error");
return 0;
}
printf("Receive buff:%s\n", recv_buff);
//關閉socket,回收資源
close(socketfd);
return 0;
}
服務端socket通信的實現步驟
- socket()創建socket
- bind()對socket進行綁定
- listen()監聽綁定的socket,用於監聽來自客戶端的消息
- accept()用於接收客戶端的連接請求和消息
- recv()函數接收客戶端的消息,並將其保存到設置好的buffer中
- send()函數用於服務端向客戶端發送消息
- close()關閉socket,回收資源
//server.c
#include<stdio.h>
#include<stdlib.h>
#include<sys/socket.h>
#include<netinet/in.h>
#define PORT 6000
int main()
{
int socketfd, clientfd, error;
struct sockaddr_in server_addr, client_addr;
int sin_size;
char buff[1024] = {0};
//創建socket
socketfd = socket(AF_INET, SOCK_STREAM, 0);
if(-1 == socketfd)
{
printf("create socket fail.");
return 0;
}
//socket屬性設置
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(PORT);
server_addr.sin_addr.s_addr = INADDR_ANY;
//綁定socket
error = bind(socketfd, &server_addr, sizeof(struct sockaddr_in));
if(0 != error)
{
printf("bind fail.");
close(socketfd);
return 0;
}
//監聽socket
error = listen(socketfd, 5);
if(0 != error)
{
printf("listen fail.");
close(socketfd);
return 0;
}
//接收客戶端的連接請求
clientfd = accept(socketfd, (struct sockaddr*)&client_addr, &sin_size);
//連接建立後,接收客戶端的消息
error = recv(clientfd, buff, 1024,0);
if(-1 == error)
{
printf("receive error");
close(socketfd);
return 0;
}
printf("Receive buff:%s\n", buff);
//收到消息後,向客戶端發送響應
char *send_buff = "test ok.";
error = send(clientfd, send_buff, strlen(send_buff), 0);
if(-1 == error)
{
printf("send error");
close(socketfd);
return 0;
}
//關閉socket,回收資源
close(socketfd);
return 0;
}
代碼完成後,使用gcc編譯代碼,生成server和client的可執行文件。
然後,在linux界面打開兩個命令行,先運行server進程,再運行client進程,即可觀察到兩個進程之間的通信。
總結
這篇文章承接的是之前《白話計算機網絡》,但當時工作較忙一直沒有更新。
此外,如果想繼續學習socket通信和網絡協議,推薦一個開源項目tinyhttp。該項目通過500行代碼,利用多線程和socket通信簡單實現了http協議,是很適合初學者學習的資源,強烈推薦!!!
我這兩天正在閱讀該項目的源碼,等把項目源碼吃透,會寫一篇總結出來。
參考資料:
https://www.cnblogs.com/niwotaxuexiba/p/9700764.html
https://blog.csdn.net/chenlycly/article/details/51657179
ps: 歡迎關注我的公衆號[酷酷的coder],分享轉行菜鳥程序員成長過程彙總的煩惱和反思.