上一節(jié)我們對(duì)TCP的報(bào)文和連接過程做了介紹,本節(jié)通過Socket的方式對(duì)整個(gè)通信過程再次進(jìn)行一次整理(使用Socket方式易于加深對(duì)以太網(wǎng)通信過程的理解,在此講解使用的是完整版Socket)。
服務(wù)器端初始化
1)調(diào)用socket,向內(nèi)核申請(qǐng)一個(gè)套接字sock
2)調(diào)用bind將sock與服務(wù)器端的IP與PORT綁定
3)調(diào)用listen將套接字設(shè)為監(jiān)聽模式,準(zhǔn)備接收客戶端連接請(qǐng)求
4)調(diào)用accept等待并接收客戶端的連接請(qǐng)求,建立好TCP連接后,該函數(shù)會(huì)返回一個(gè)新的已連接套接字connected
創(chuàng)建連接
1)客戶端調(diào)用socket創(chuàng)建套接字
2)調(diào)用connect,向服務(wù)器發(fā)送連接請(qǐng)求
3)connect會(huì)發(fā)送一個(gè)請(qǐng)求SYN段并阻塞等待服務(wù)器應(yīng)答(第一次握手)
4)服務(wù)器收到SYN,會(huì)給客戶端發(fā)送一個(gè)確認(rèn)應(yīng)答ACK,同時(shí)發(fā)送一個(gè)請(qǐng)求(SYN)建立連接(第二次握手)
5)客戶端收到服務(wù)器發(fā)的SYN+ACK段,表明客戶端連接已建立成功,進(jìn)入已連接狀態(tài)??蛻舳嗽傧蚍?wù)器發(fā)送一個(gè)ACK段,服務(wù)器收到后則服務(wù)器連接成功
數(shù)據(jù)傳輸
1)服務(wù)器端使用accept連接建立成功后(通信雙方可同時(shí)寫數(shù)據(jù),支持全雙工),調(diào)用read開始讀數(shù)據(jù),若沒有數(shù)據(jù)則阻塞等待
2)客戶端調(diào)用write向服務(wù)器發(fā)送數(shù)據(jù)請(qǐng)求,客戶端收到之后調(diào)用read處理請(qǐng)求,此過程服務(wù)器調(diào)用read阻塞等待
3)服務(wù)器調(diào)用write將處理好的請(qǐng)求發(fā)送給客戶端,再次調(diào)用read等待下一個(gè)請(qǐng)求
4)服務(wù)器收到SYN,會(huì)給客戶端發(fā)送一個(gè)確認(rèn)應(yīng)答ACK,同時(shí)發(fā)送一個(gè)請(qǐng)求(SYN)建立連接(第二次握手)
4)客戶端收到后從read返回,發(fā)送下一條請(qǐng)求,如此循環(huán)下去
斷開連接
1)沒有數(shù)據(jù),則客戶端調(diào)用close關(guān)閉連接,給服務(wù)器發(fā)送一個(gè)斷開連接請(qǐng)求FIN段(第一次握手)
2)服務(wù)器收到客戶端的FIN段,給客戶端發(fā)送一個(gè)確認(rèn)應(yīng)答ACK段,表明同意斷開連接??蛻舳耸盏紸CK段并調(diào)用read返回0,表明客戶端連接已經(jīng)斷開(第二次握手)
3)read返回0后,服務(wù)器知道客戶端已經(jīng)斷開連接,它也調(diào)用close關(guān)閉連接,給客戶端發(fā)送一個(gè)斷開連接請(qǐng)求FIN段(第三次握手)
4)客戶端收到服務(wù)器發(fā)送的FIN段,就給服務(wù)器一個(gè)確認(rèn)應(yīng)答ACK段,表明同意斷開連接??蛻舳诉M(jìn)入TIME_WAIT狀態(tài),服務(wù)器收到客戶端的ACK段后也斷開連接
實(shí)驗(yàn)使用MB-039開發(fā)板,在應(yīng)用工程中使用LwIP+FreeRTOS,實(shí)驗(yàn)展示如何制作一個(gè)TCP Server_socket,并收發(fā)數(shù)據(jù),實(shí)驗(yàn)使用到的硬件如下:
如圖是MB-039(完整原理圖可以通過MM32官網(wǎng)下載)的Ethermac部分。
各個(gè)信號(hào)引腳對(duì)應(yīng)如下:
Server_socket實(shí)驗(yàn)用到的API大部分在前面已經(jīng)進(jìn)行講解(只是對(duì)NETCONN接口編輯方式進(jìn)行二次封裝),本節(jié)只介紹一個(gè)比較關(guān)鍵的API:setsockopt(s,level,optname,opval,optlen)。
從名稱中就可以看出函數(shù)功能用于設(shè)置套接字的一些選項(xiàng),我們關(guān)注一下參數(shù):
(1)level有多個(gè)常用的選項(xiàng)
SOL_SOCKET:表示在Socket層
IPPROTO_TCP:表示在TCP層
IPPROTO_IP:表示在IP層
(2)optname 表示該層的具體選項(xiàng)名稱
level為SOL_SOCKET時(shí),有以下選項(xiàng):SO_REUSEADDR(允許重用本地地址和端口)、
SO_SNDTIMEO(設(shè)置發(fā)送數(shù)據(jù)超時(shí)時(shí)間)、SO_SNDTIMEO(設(shè)置接收數(shù)據(jù)超時(shí)時(shí)間)、SO_RCVBUF(設(shè)置發(fā)送數(shù)據(jù)緩沖區(qū)大小)等。
level為IPPROTO_TCP時(shí),有以下選項(xiàng):TCP_NODELAY(不使用Nagle算法)、TCP_KEEPALIVE(設(shè)置TCP保活時(shí)間)等。
level為IPPROTO_IP選項(xiàng),有以下選項(xiàng):IP_TTL(設(shè)置生存時(shí)間)、IP_TOS(設(shè)置服務(wù)類型)等。
實(shí)現(xiàn)Server_socket函數(shù):
static void server_socket(void* thread_param) { int sock = -1, connected; char* recv_data; struct sockaddr_in server_addr, client_addr; socklen_t sin_size; int recv_data_len; printf("The local port number is%dnn", LOCAL_PORT); recv_data = (char*)pvPortMalloc(RECV_DATA); if (recv_data == NULL) { printf("No memoryn"); goto __exit; } sock = socket(AF_INET, SOCK_STREAM, 0); if (sock < 0) { printf("Socket errorn"); goto __exit; } server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = INADDR_ANY; server_addr.sin_port = htons(LOCAL_PORT); memset( (server_addr.sin_zero), 0, sizeof(server_addr.sin_zero)); if (bind(sock, (struct sockaddr*) server_addr, sizeof(struct sockaddr)) == -1) { printf("Unable to bindn"); goto __exit; } if (listen(sock, 5) == -1) { // (1) printf("Listen errorn"); goto __exit; } while(1) { sin_size = sizeof(struct sockaddr_in); connected = accept(sock, (struct sockaddr*) client_addr, sin_size); // (2) printf("new client connected from (%s, %d)n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port)); { int flag = 1; setsockopt(connected, IPPROTO_TCP, /* set option at TCP level */ TCP_NODELAY, /* name of option */ (void*) flag, /* the cast is historical cruft */ sizeof(int)); /* length of option value */ // (3) } while(1) { recv_data_len = recv(connected, recv_data, RECV_DATA, 0); // (4) if (recv_data_len <= 0) break; printf("recv %d len datan", recv_data_len); write(connected, recv_data, recv_data_len); // (5) } if (connected >= 0) closesocket(connected); // (6) connected = -1; } __exit: if (sock >= 0) closesocket(sock); if (recv_data) free(recv_data); }
1)進(jìn)入監(jiān)聽狀態(tài)
2)阻塞應(yīng)用線程直至與遠(yuǎn)端主機(jī)建立TCP連接,建立成功后遠(yuǎn)程主機(jī)的信息將保持在連接句柄中(connected)
3)對(duì)套接字connected進(jìn)行設(shè)置:在TCP層,不使用Nagle算法
4)處理客戶端的連接請(qǐng)求,接收遠(yuǎn)程主機(jī)信息
5)將接收的數(shù)據(jù)進(jìn)行轉(zhuǎn)發(fā)
6)主動(dòng)關(guān)閉客戶端的連接
到這里已經(jīng)完成了Server_socket函數(shù)的創(chuàng)建,看一下PC的IP地址,設(shè)備需要處于同一網(wǎng)段方便測試。打開命令行窗口輸入:ipconfig
PC的地址為:192.168.105.34,在sys_arch.h文件中對(duì)DEST_IP_ADDR0 、DEST_IP_ADDR1、DEST_IP_ADDR2、DEST_IP_ADDR3進(jìn)行修改,DEST_PORT 隨意修改。
#define LOCAL_PORT 2021 #define IP_ADDR0 192 #define IP_ADDR1 168 #define IP_ADDR2 105 #define IP_ADDR3 26
將程序下載入開發(fā)板中,使用NetAssist進(jìn)行如下設(shè)置:
1)協(xié)議設(shè)置,此時(shí)設(shè)備為Server,則PC為Client
2)設(shè)置遠(yuǎn)程主機(jī)地址(即設(shè)備地址)
3)端口號(hào)
點(diǎn)擊連接,若提示連接失敗,則Ping一下開發(fā)板地址,可以正常Ping通則檢查端口號(hào);如果無法Ping通則需要對(duì)工程進(jìn)行檢查。
任意輸入字符進(jìn)行發(fā)送。
通過上圖可以觀察到發(fā)送成功,并且設(shè)備返回?cái)?shù)據(jù)與發(fā)送數(shù)據(jù)一致,表明實(shí)驗(yàn)成功。
實(shí)驗(yàn)程序請(qǐng)登錄我們的官網(wǎng)下載MM32F3270 SDK,工程路徑如下:
~MM32F3270_Lib_Samples_V0.90Demo_appEthernet_DemoETH_RTOSFreertos_Server_socket
來源:靈動(dòng)MM32MCU
審核編輯:湯梓紅
-
以太網(wǎng)
+關(guān)注
關(guān)注
40文章
5460瀏覽量
172379 -
通信
+關(guān)注
關(guān)注
18文章
6065瀏覽量
136282 -
Server
+關(guān)注
關(guān)注
0文章
93瀏覽量
24074 -
TCP
+關(guān)注
關(guān)注
8文章
1377瀏覽量
79186
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論