我們已經(jīng)實現(xiàn)了在FreeRTOS系統(tǒng)上的LwIP的移植工作,但只是簡單的在系統(tǒng)平臺上跑了起來。我們還希望能做更多的事情,這一節(jié)我們就在FreeRTOS系統(tǒng)上實現(xiàn)基于LwIP的UDP服務器。
1、UDP協(xié)議簡述
??UDP協(xié)議全稱是用戶數(shù)據(jù)報協(xié)議,在網(wǎng)絡中它與TCP協(xié)議一樣用于處理數(shù)據(jù)包,是一種無連接的協(xié)議。在OSI模型中,處于傳輸層,是IP協(xié)議的上層協(xié)議。UDP有不提供數(shù)據(jù)包分組、組裝和不能對數(shù)據(jù)包進行排序的缺點,也就是說,當報文發(fā)送之后,是無法得知其是否安全完整到達的。
??UDP協(xié)議的主要作用是將網(wǎng)絡數(shù)據(jù)流量壓縮成數(shù)據(jù)包的形式。一個典型的數(shù)據(jù)包就是一個二進制數(shù)據(jù)的傳輸單位。每一個數(shù)據(jù)包的前8個字節(jié)用來包含報頭信息,剩余字節(jié)則用來包含具體的傳輸數(shù)據(jù)。
??UDP報頭由4個域組成,其中每個域各占用2個字節(jié),具體如下:源端口號、目標端口號、數(shù)據(jù)報長度、校驗值。其數(shù)據(jù)結構如下:
????UDP協(xié)議使用端口號為不同的應用保留其各自的數(shù)據(jù)傳輸通道。UDP和TCP協(xié)議正是采用這一機制實現(xiàn)對同一時刻內(nèi)多項應用同時發(fā)送和接收數(shù)據(jù)的支持。數(shù)據(jù)發(fā)送一方(可以是客戶端或服務器端)將UDP數(shù)據(jù)包通過源端口發(fā)送出去,而數(shù)據(jù)接收一方則通過目標端口接收數(shù)據(jù)。有的網(wǎng)絡應用只能使用預先為其預留或注冊的靜態(tài)端口;而另外一些網(wǎng)絡應用則可以使用未被注冊的動態(tài)端口。因為UDP報頭使用兩個字節(jié)存放端口號,所以端口號的有效范圍是從0到65535。一般來說,大于49151的端口號都代表動態(tài)端口。
??數(shù)據(jù)報的長度是指包括報頭和數(shù)據(jù)部分在內(nèi)的總字節(jié)數(shù)。因為報頭的長度是固定的,所以該域主要被用來計算可變長度的數(shù)據(jù)部分。數(shù)據(jù)報的最大長度根據(jù)操作環(huán)境的不同而各異。從理論上說,包含報頭在內(nèi)的數(shù)據(jù)報的最大長度為65535字節(jié)。不過,一些實際應用往往會限制數(shù)據(jù)報的大小,有時會降低到8192字節(jié)。
??UDP協(xié)議使用報頭中的校驗值來保證數(shù)據(jù)的安全。校驗值首先在數(shù)據(jù)發(fā)送方通過特殊的算法計算得出,在傳遞到接收方之后,還需要再重新計算。如果某個數(shù)據(jù)報在傳輸過程中被第三方篡改或者由于線路噪音等原因受到損壞,發(fā)送和接收方的校驗計算值將不會相符,由此UDP協(xié)議可以檢測是否出錯。
2、帶系統(tǒng)UDP服務器的設計
??關于UDP服務器,我們以前在裸機狀態(tài)下,使用RAW/CallBack API函數(shù)實現(xiàn)過。在這里我們將基于操作系統(tǒng)來實現(xiàn)UDP服務器,在此我們需要使用netconn API函數(shù)實現(xiàn)。
2.1、netconn API
??在帶操作系統(tǒng)的LwIP應用中,應用程序需要使用netconn API函數(shù)來實現(xiàn)相關的應用,接下來我們了解一下netconn API函數(shù)。
(1)、公用部分函數(shù)
??其中即可用于TCP也可用于UDP的公共netconn API函數(shù)如下:
序號 | 函數(shù) | 描述 |
---|---|---|
1 | netconn_new() | 創(chuàng)建一個新連接 |
2 | netconn_peer() | 獲取遠程IP地址和端口 |
3 | netconn_addr() | 獲取本地IP地址和端口 |
4 | netconn_set_ipv6only() | 設置netconn調(diào)用的IPv6狀態(tài) |
5 | netconn_get_ipv6only() | 獲取netconn調(diào)用的IPv6狀態(tài) |
6 | netconn_delete() | 刪除現(xiàn)有連接 |
7 | netconn_bind() | 綁定到本地端口/ ip的連接 |
8 | netconn_connect() | 連接到遠程端口/ ip的連接 |
9 | netconn_recv() | 從netconn接收數(shù)據(jù) |
10 | netconn_gethostbyname_addrtype () | 執(zhí)行DNS查詢,只返回一個IP地址 |
(2)、用于TCP的函數(shù)
??對于TCP連接來說,還包括如下的netconn API函數(shù):
序號 | 函數(shù) | 描述 |
---|---|---|
1 | netconn_listen() | 將TCP連接設置為偵聽模式 |
2 | netconn_write() | 在連接的TCP netconn上發(fā)送數(shù)據(jù) |
3 | netconn_listen_with_backlog () | 將TCP netconn設置為偵聽模式 |
4 | netconn_accept() | 接受偵聽TCP連接上的傳入連接 |
5 | netconn_recv_tcp_pbuf () | 從TCP netconn接收數(shù)據(jù)(以pbuf的形式) |
6 | netconn_write_partly () | 通過TCP netconn發(fā)送數(shù)據(jù) |
7 | netconn_close() | 關閉TCP netconn而不刪除它 |
8 | netconn_shutdown () | 關閉TCP netconn的一端或兩端(不刪除它) |
(3)、用于UDP的函數(shù)
??對于UDP連接來說,還包括如下的netconn API函數(shù):
序號 | 函數(shù) | 描述 |
---|---|---|
1 | netconn_disconnect() | 斷開與遠程端口/ ip的連接 |
2 | netconn_sendto() | 將數(shù)據(jù)發(fā)送到指定的遠程端口/ ip(不適用于TCP) |
3 | netconn_send() | 將數(shù)據(jù)發(fā)送到當前連接的遠程端口/ ip(不適用于TCP) |
4 | netconn_join_leave_group() | 基本的IGMP多播支持 |
2.2、UDP服務器的流程
??在RAW API實現(xiàn)UDP服務器時,我們使用回調(diào)函數(shù),當接受到數(shù)據(jù)報文時,回調(diào)函數(shù)會被調(diào)用。在有操作系統(tǒng)的情況下,我們肯定是實現(xiàn)多線程,所以我們將UDP服務器設定為一個任務來執(zhí)行。在這個任務中我們將按如下流程來實現(xiàn)UDP服務器。
??從上圖中我們與無操作系統(tǒng)時的操作很類似。創(chuàng)建控制塊、綁定端口等是一樣的。但在內(nèi)部接收和發(fā)送報文的方式卻是有區(qū)別的。
??至于UDP服務器最終實現(xiàn)了哪些功能,需要我們根據(jù)實際需要在處理并返回信息階段實施。功能可以很復雜也可以很簡單,在這里我們就是實現(xiàn)一個簡單的回環(huán)服務器。
3、帶系統(tǒng)UDP服務器的實現(xiàn)
??我們已經(jīng)明白了UDP服務器在使用netconn API的實現(xiàn)方式及流程。接下來我們就來實現(xiàn)它。我們通過兩個函數(shù)來實現(xiàn):一是初始化任務,即創(chuàng)建相應的任務;二是實現(xiàn)這個任務函數(shù),也就是我們的UDP服務器。
??先實現(xiàn)任務的創(chuàng)建。這個函數(shù)很簡單,因為在移植LwIP協(xié)議棧時,要求在sys_arch.c文件中實現(xiàn)一個名為sys_thread_new的任務創(chuàng)建函數(shù),而我們已經(jīng)實現(xiàn)了這個任務創(chuàng)建函數(shù),所以我們直接調(diào)用它就好了。
/* UDP初始化配置 */
void UDP_Server_Initialization(void)
{
sys_thread_new("udpserver_thread", UDPServerThread, NULL, DEFAULT_THREAD_STACKSIZE,UDPECHO_THREAD_PRIO );
}
??接下來,我們看看UDP服務器任務函數(shù)的實現(xiàn),根據(jù)上一節(jié)我們給出的流程,實現(xiàn)如下:
/* 定義UDP服務器數(shù)據(jù)處理進程 */
static void UDPServerThread(void *arg)
{
err_t err, recv_err;
static struct netconn *conn;
static struct netbuf *buf;
static ip_addr_t *addr;
static unsigned short port;
LWIP_UNUSED_ARG(arg);
conn = netconn_new(NETCONN_UDP);
if (conn!= NULL)
{
err = netconn_bind(conn, IP_ADDR_ANY,UDP_ECHO_SERVER_PORT);
if (err == ERR_OK)
{
while (1)
{
recv_err = netconn_recv(conn, &buf);
if (recv_err == ERR_OK)
{
addr = netbuf_fromaddr(buf);
port = netbuf_fromport(buf);
netconn_connect(conn, addr, port);
buf->addr.addr = 0;
netconn_send(conn,buf);
netbuf_delete(buf);
}
}
}
else
{
netconn_delete(conn);
}
}
}
??對于UDP連接來說,netconn_connect函數(shù)的調(diào)用只是簡單的設置UDP控制塊中的remote_ip和remote_port字段。其實在這里不使用該函數(shù)也是沒問題的,因為buf中已經(jīng)包含了相關的信息。
4、帶系統(tǒng)UDP服務器總結
??我們實現(xiàn)了一個簡單的UDP服務器應用,其實帶有操作系統(tǒng)時只是在軟件編寫方面采用的形式不一樣。從外界看來,依然是一個UDP服務器,與有無操作系統(tǒng)無關。所以我們的測試方法也是一樣的,與我們預期的結果也是一樣的。
-
服務器
+關注
關注
13文章
9795瀏覽量
87991 -
UDP
+關注
關注
0文章
330瀏覽量
34655 -
FreeRTOS
+關注
關注
12文章
493瀏覽量
64353 -
LwIP
+關注
關注
2文章
89瀏覽量
28302
發(fā)布評論請先 登錄
使用lwip socket udp功能,開發(fā)板為客戶端時不能夠接收服務器端發(fā)送過來的數(shù)據(jù) ?
如何打開UDP服務器?
用GPRS的UDP協(xié)議跟服務器通信時服務器發(fā)送的數(shù)據(jù)收不到
基于udp協(xié)議用單片機做一個服務器
請問用lwip能在程序中同時實現(xiàn)tcp客戶端,tcp服務器及udp的功能嗎?
關于UDP服務器機制的問題怎么解決
用AT+CIPSERVER=1,80啟動服務器時,可以啟動UDP服務器嗎?
如何使用UDP協(xié)議和單片機做一個服務器進行的數(shù)據(jù)接收與發(fā)送的程序

【EsDA應用】串口轉UDP服務器
【EsDA應用】串口轉UDP服務器

【EsDA應用】串口服務器——UDP Server

評論