在進(jìn)行本節(jié)之前,首先解決大家的一個(gè)疑惑點(diǎn):Client和Client_Socket有什么區(qū)別或分別代表的含義?
Socket標(biāo)準(zhǔn)定義為套接字,應(yīng)用于主流的網(wǎng)絡(luò)設(shè)計(jì)程序,具有使用簡單,多平臺(tái)移植方便的特點(diǎn)。在Socket應(yīng)用中,使用一個(gè)套接字來記錄網(wǎng)絡(luò)的一個(gè)連接,套接字是一個(gè)整數(shù),就像操作文件一樣,利用一個(gè)文件描述符,進(jìn)行打開、讀、寫、關(guān)閉等操作。在網(wǎng)絡(luò)中,可以對(duì)Socket 套接字進(jìn)行類似的操作,比如開啟一個(gè)網(wǎng)絡(luò)的連接、讀取連接主機(jī)發(fā)送來的數(shù)據(jù)、向連接的主機(jī)發(fā)送數(shù)據(jù)、終止連接等操作。LwIP設(shè)計(jì)目的主要應(yīng)用于嵌入式平臺(tái),對(duì)于Socket的支持并不完全,只是通過對(duì)netconn進(jìn)行封裝實(shí)現(xiàn)部分功能,使得LwIP也具有多平臺(tái)應(yīng)用的特性,通過Socket方式的了解能夠極大簡化通信過程的理解,快速實(shí)現(xiàn)應(yīng)用開發(fā)。
Demo應(yīng)用中,使用的開發(fā)板為MB-039,在工程中使用LwIP+FreeRTOS,實(shí)驗(yàn)展示如何制作一個(gè)客戶端并發(fā)送數(shù)據(jù),板載Ethernet相關(guān)的硬件部分電路如下:
MB-039 完整原理圖可以通過MM32官網(wǎng)下載。
各個(gè)信號(hào)引腳對(duì)應(yīng)如下:
通過配置復(fù)用相關(guān)引腳為RMII相關(guān)的功能,初始化以太網(wǎng)功能,執(zhí)行FreeRTOS的啟動(dòng)。具體過程可參考樣例初始化程序中代碼。
在進(jìn)行Client_Socket實(shí)驗(yàn)前,我們先了解需要使用到的應(yīng)用功能函數(shù):
(1)socket () (2)connect () (3)write ()
(1) socket ()
Socket()指向lwip_socket(),功能為申請(qǐng)一個(gè)套接字,lwip_socket()源碼如下:
int lwip_socket(int domain, int type, int protocol) { struct netconn *conn; int i; LWIP_UNUSED_ARG(domain); /* @todo: check this */ /* create a netconn */ switch (type) { case SOCK_RAW: conn = netconn_new_with_proto_and_callback(DOMAIN_TO_NETCONN_TYPE(domain, NETCONN_RAW), (u8_t)protocol, DEFAULT_SOCKET_EVENTCB); LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_socket(%s, SOCK_RAW, %d) = ", domain == PF_INET ? "PF_INET" : "UNKNOWN", protocol)); break; case SOCK_DGRAM: conn = netconn_new_with_callback(DOMAIN_TO_NETCONN_TYPE(domain, ((protocol == IPPROTO_UDPLITE) ? NETCONN_UDPLITE : NETCONN_UDP)), DEFAULT_SOCKET_EVENTCB); LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_socket(%s, SOCK_DGRAM, %d) = ", domain == PF_INET ? "PF_INET" : "UNKNOWN", protocol)); #if LWIP_NETBUF_RECVINFO if (conn) { /* netconn layer enables pktinfo by default, sockets default to off */ conn->flags = ~NETCONN_FLAG_PKTINFO; } #endif /* LWIP_NETBUF_RECVINFO */ break; case SOCK_STREAM: conn = netconn_new_with_callback(DOMAIN_TO_NETCONN_TYPE(domain, NETCONN_TCP), DEFAULT_SOCKET_EVENTCB); LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_socket(%s, SOCK_STREAM, %d) = ", domain == PF_INET ? "PF_INET" : "UNKNOWN", protocol)); break; default: LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_socket(%d, %d/UNKNOWN, %d) = -1n", domain, type, protocol)); set_errno(EINVAL); return -1; } if (!conn) { LWIP_DEBUGF(SOCKETS_DEBUG, ("-1 / ENOBUFS (could not create netconn)n")); set_errno(ENOBUFS); return -1; } i = alloc_socket(conn, 0); if (i == -1) { netconn_delete(conn); set_errno(ENFILE); return -1; } conn->socket = i; done_socket( sockets[i - LWIP_SOCKET_OFFSET]); LWIP_DEBUGF(SOCKETS_DEBUG, ("%dn", i)); set_errno(0); return i; }
從源碼中我們可以看出,本質(zhì)上是對(duì)netconn_new()進(jìn)行封裝。我們關(guān)注一下其參數(shù),domain表示協(xié)議簇,對(duì)于IP/TCP來說該值始終為AF_INET。重點(diǎn)需要關(guān)注一下type,我們查看API手冊(cè)對(duì)于幾種類型的解釋如下:
1. SOCK_STREAM:提供可靠的(即能保證數(shù)據(jù)正確傳送到對(duì)方)面向連接Socket服務(wù),多用于資料(如文件)傳輸,如TCP協(xié)議。
2. SOCK_DGRAM:是提供無保障的面向消息的Socket服務(wù),主要用于在網(wǎng)絡(luò)上發(fā)廣播信息,如UDP協(xié)議,提供無連接不可靠的數(shù)據(jù)報(bào)交付服務(wù)。
3. SOCK_RAW:表示原始套接字,它允許應(yīng)用程序訪問網(wǎng)絡(luò)層的原始數(shù)據(jù)包,這個(gè)套接字用得比較少,暫時(shí)不用理會(huì)它。
Protocol指定套接字使用的協(xié)議,對(duì)于IPv4,TCP協(xié)議提供SOCK_STREAM服務(wù),只有UDP協(xié)議提供SOCK_DGRAM服務(wù)。
(2) connect ()
connect()指向lwip_connect()(源碼較長,就不進(jìn)行粘貼了),函數(shù)的作用與前文介紹netconn_connect功能一致,通過源碼可以知道其是通過對(duì)netconn_connect的封裝實(shí)現(xiàn)。在TCP客戶端連接中,調(diào)用這個(gè)函數(shù)將發(fā)生握手過程,并最終建立新的TCP連接。對(duì)于UDP來說調(diào)用這個(gè)函數(shù)只是在UDP控制塊中記錄遠(yuǎn)端IP地址與端口號(hào)。
(3) write ()
Write()指向lwip_write,源碼如下,其通過調(diào)用lwip_send實(shí)現(xiàn),flags為0。
ssize_t lwip_write(int s, const void *data, size_t size) { return lwip_send(s, data, size, 0); }
了解了以上3個(gè)API,接下來開始創(chuàng)建Client_Socket工程:
static void client(void *thread_param) { int sock = -1; struct sockaddr_in client_addr; ip4_addr_t ipaddr; uint8_t send_buf[]= " This is MM32F3270 TCP Client_Socket Demo n"; IP4_ADDR( ipaddr,DEST_IP_ADDR0,DEST_IP_ADDR1,DEST_IP_ADDR2,DEST_IP_ADDR3); while(1) { sock = socket(AF_INET, SOCK_STREAM, 0); //(1) if (sock < 0) { vTaskDelay(10); continue; } client_addr.sin_family = AF_INET; //(2) client_addr.sin_port = htons(DEST_PORT); //(3) client_addr.sin_addr.s_addr = ipaddr.addr; //(4) memset( (client_addr.sin_zero), 0, sizeof(client_addr.sin_zero)); if (connect(sock, (struct sockaddr *) client_addr, sizeof(struct sockaddr)) == -1) //(5) { printf("Connect failed!n"); closesocket(sock); vTaskDelay(10); continue; } while (1) { if(write(sock,send_buf,sizeof(send_buf)) < 0) //(6) break; vTaskDelay(1000); } closesocket(sock); } }
(1)申請(qǐng)一個(gè)套接字:socket
(2)協(xié)議簇類型(AF_INET用于TCP/IP協(xié)議)
(3)將端口賦值給client_addr的sin_port成員
(4)將地址賦值給client_addr的sin_addr.s_addr成員
(5)創(chuàng)建連接,將sock與地址端口進(jìn)行綁定,建立連接
(6)發(fā)送數(shù)據(jù)
到這里已經(jīng)完成了Client Socket工程的創(chuàng)建,還有一步比較重要的是配置Client與Server端的IP,將數(shù)據(jù)發(fā)送給服務(wù)器端。
在Windows下,通過打開命令行窗口輸入:ipconfig可以獲取本機(jī)地址與服務(wù)器的地址。
可以觀察到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可選用空閑端口,設(shè)備IP需要設(shè)置在同一個(gè)網(wǎng)段內(nèi)通信才能進(jìn)行IP_ADDR0、IP_ADDR1 、IP_ADDR2,需要與PC地址保持一致,IP_ADDR3可以隨意設(shè)置(和PC地址不一致即可)。
#define DEST_IP_ADDR0 192 #define DEST_IP_ADDR1 168 #define DEST_IP_ADDR2 105 #define DEST_IP_ADDR3 34 #define DEST_PORT 5001 #define IP_ADDR0 192 #define IP_ADDR1 168 #define IP_ADDR2 105 #define IP_ADDR3 130
將程序下載入開發(fā)板中,使用SSCOM工具進(jìn)行如下設(shè)置:
點(diǎn)擊偵聽:
可以觀察到正常偵聽并接收到數(shù)據(jù),表明實(shí)驗(yàn)成功。Demo程序可登錄MindMotion的官網(wǎng)下載MM32F3270 lib_Samples。
來源:靈動(dòng)MM32MCU
審核編輯:湯梓紅
-
嵌入式
+關(guān)注
關(guān)注
5045文章
18816瀏覽量
298442 -
以太網(wǎng)
+關(guān)注
關(guān)注
40文章
5287瀏覽量
169633 -
開發(fā)板
+關(guān)注
關(guān)注
25文章
4771瀏覽量
96154 -
FreeRTOS
+關(guān)注
關(guān)注
12文章
483瀏覽量
61721
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論