轉(zhuǎn)自
前面的文章中我們給出了幾個(gè)TCP的例子建车,對(duì)于UDP而言傀顾,只要能理解前面的內(nèi)容丹喻,實(shí)現(xiàn)并非難事薄货。
UDP中的服務(wù)器端和客戶端沒有連接
UDP不像TCP,無需在連接狀態(tài)下交換數(shù)據(jù)碍论,因此基于UDP的服務(wù)器端和客戶端也無需經(jīng)過連接過程谅猾。也就是說,不必調(diào)用 listen() 和 accept() 函數(shù)鳍悠。UDP中只有創(chuàng)建套接字的過程和數(shù)據(jù)交換的過程赊瞬。
UDP服務(wù)器端和客戶端均只需1個(gè)套接字
TCP中先煎,套接字是一對(duì)一的關(guān)系。如要向10個(gè)客戶端提供服務(wù)巧涧,那么除了負(fù)責(zé)監(jiān)聽的套接字外薯蝎,還需要?jiǎng)?chuàng)建10套接字。但在UDP中谤绳,不管是服務(wù)器端還是客戶端都只需要1個(gè)套接字占锯。之前解釋UDP原理的時(shí)候舉了郵寄包裹的例子,負(fù)責(zé)郵寄包裹的快遞公司可以比喻為UDP套接字缩筛,只要有1個(gè)快遞公司消略,就可以通過它向任意地址郵寄包裹。同樣瞎抛,只需1個(gè)UDP套接字就可以向任意主機(jī)傳送數(shù)據(jù)艺演。
基于UDP的接收和發(fā)送函數(shù)
創(chuàng)建好TCP套接字后,傳輸數(shù)據(jù)時(shí)無需再添加地址信息桐臊,因?yàn)門CP套接字將保持與對(duì)方套接字的連接胎撤。換言之,TCP套接字知道目標(biāo)地址信息断凶。但UDP套接字不會(huì)保持連接狀態(tài)伤提,每次傳輸數(shù)據(jù)都要添加目標(biāo)地址信息,這相當(dāng)于在郵寄包裹前填寫收件人地址认烁。
發(fā)送數(shù)據(jù)使用 sendto() 函數(shù):
//Linux
ssize_t sendto(int sock, void *buf, size_t nbytes, int flags, struct sockaddr *to, socklen_t addrlen);
//Windows
int sendto(SOCKET sock, const char *buf, int nbytes, int flags, const struct sockadr *to, int addrlen);
Linux和Windows下的 sendto() 函數(shù)類似肿男,下面是詳細(xì)參數(shù)說明:
- sock:用于傳輸U(kuò)DP數(shù)據(jù)的套接字;
- buf:保存待傳輸數(shù)據(jù)的緩沖區(qū)地址却嗡;
- nbytes:帶傳輸數(shù)據(jù)的長(zhǎng)度(以字節(jié)計(jì))舶沛;
- flags:可選項(xiàng)參數(shù),若沒有可傳遞0窗价;
- to:存有目標(biāo)地址信息的 sockaddr 結(jié)構(gòu)體變量的地址如庭;
- addrlen:傳遞給參數(shù) to 的地址值結(jié)構(gòu)體變量的長(zhǎng)度。
UDP 發(fā)送函數(shù) sendto() 與TCP發(fā)送函數(shù) write()/send() 的最大區(qū)別在于舌镶,sendto() 函數(shù)需要向他傳遞目標(biāo)地址信息。
接收數(shù)據(jù)使用 recvfrom() 函數(shù):
//Linux
ssize_t recvfrom(int sock, void *buf, size_t nbytes, int flags, struct sockadr *from, socklen_t *addrlen);
//Windows
int recvfrom(SOCKET sock, char *buf, int nbytes, int flags, const struct sockaddr *from, int *addrlen);
由于UDP數(shù)據(jù)的發(fā)送端不不定豪娜,所以 recvfrom() 函數(shù)定義為可接收發(fā)送端信息的形式餐胀,具體參數(shù)如下:
- sock:用于接收UDP數(shù)據(jù)的套接字;
- buf:保存接收數(shù)據(jù)的緩沖區(qū)地址瘤载;
- nbytes:可接收的最大字節(jié)數(shù)(不能超過buf緩沖區(qū)的大蟹裨帧);
- flags:可選項(xiàng)參數(shù)鸣奔,若沒有可傳遞0墨技;
- from:存有發(fā)送端地址信息的sockaddr結(jié)構(gòu)體變量的地址惩阶;
- addrlen:保存參數(shù) from 的結(jié)構(gòu)體變量長(zhǎng)度的變量地址值。
基于UDP的回聲服務(wù)器端/客戶端
下面結(jié)合之前的內(nèi)容實(shí)現(xiàn)回聲客戶端扣汪。需要注意的是断楷,UDP不同于TCP,不存在請(qǐng)求連接和受理過程崭别,因此在某種意義上無法明確區(qū)分服務(wù)器端和客戶端冬筒,只是因?yàn)槠涮峁┓?wù)而稱為服務(wù)器端,希望各位讀者不要誤解茅主。
下面給出Windows下的代碼舞痰,Linux與此類似,不再贅述诀姚。
服務(wù)器端 server.cpp:
#include <stdio.h>
#include <winsock2.h>
#pragma comment (lib, "ws2_32.lib") //加載 ws2_32.dll
#define BUF_SIZE 100
int main(){
WSADATA wsaData;
WSAStartup( MAKEWORD(2, 2), &wsaData);
//創(chuàng)建套接字
SOCKET sock = socket(AF_INET, SOCK_DGRAM, 0);
//綁定套接字
sockaddr_in servAddr;
memset(&servAddr, 0, sizeof(servAddr)); //每個(gè)字節(jié)都用0填充
servAddr.sin_family = PF_INET; //使用IPv4地址
servAddr.sin_addr.s_addr = htonl(INADDR_ANY); //自動(dòng)獲取IP地址
servAddr.sin_port = htons(1234); //端口
bind(sock, (SOCKADDR*)&servAddr, sizeof(SOCKADDR));
//接收客戶端請(qǐng)求
SOCKADDR clntAddr; //客戶端地址信息
int nSize = sizeof(SOCKADDR);
char buffer[BUF_SIZE]; //緩沖區(qū)
while(1){
int strLen = recvfrom(sock, buffer, BUF_SIZE, 0, &clntAddr, &nSize);
sendto(sock, buffer, strLen, 0, &clntAddr, nSize);
}
closesocket(sock);
WSACleanup();
return 0;
}
代碼說明:
第12行代碼在創(chuàng)建套接字時(shí)响牛,向 socket() 第二個(gè)參數(shù)傳遞 SOCK_DGRAM,以指明使用UDP協(xié)議赫段。
第18行代碼中使用htonl(INADDR_ANY)來自動(dòng)獲取IP地址呀打。
利用常數(shù) INADDR_ANY 自動(dòng)獲取IP地址有一個(gè)明顯的好處,就是當(dāng)軟件安裝到其他服務(wù)器或者服務(wù)器IP地址改變時(shí)瑞佩,不用再更改源碼重新編譯聚磺,也不用在啟動(dòng)軟件時(shí)手動(dòng)輸入。而且炬丸,如果一臺(tái)計(jì)算機(jī)中已分配多個(gè)IP地址(例如路由器)瘫寝,那么只要端口號(hào)一致,就可以從不同的IP地址接收數(shù)據(jù)稠炬。所以焕阿,服務(wù)器中優(yōu)先考慮使用INADDR_ANY;而客戶端中除非帶有一部分服務(wù)器功能首启,否則不會(huì)采用暮屡。
客戶端 client.cpp:
#include <stdio.h>
#include <WinSock2.h>
#pragma comment(lib, "ws2_32.lib") //加載 ws2_32.dll
#define BUF_SIZE 100
int main(){
//初始化DLL
WSADATA wsaData;
WSAStartup(MAKEWORD(2, 2), &wsaData);
//創(chuàng)建套接字
SOCKET sock = socket(PF_INET, SOCK_DGRAM, 0);
//服務(wù)器地址信息
sockaddr_in servAddr;
memset(&servAddr, 0, sizeof(servAddr)); //每個(gè)字節(jié)都用0填充
servAddr.sin_family = PF_INET;
servAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
servAddr.sin_port = htons(1234);
//不斷獲取用戶輸入并發(fā)送給服務(wù)器,然后接受服務(wù)器數(shù)據(jù)
sockaddr fromAddr;
int addrLen = sizeof(fromAddr);
while(1){
char buffer[BUF_SIZE] = {0};
printf("Input a string: ");
gets(buffer);
sendto(sock, buffer, strlen(buffer), 0, (struct sockaddr*)&servAddr, sizeof(servAddr));
int strLen = recvfrom(sock, buffer, BUF_SIZE, 0, &fromAddr, &addrLen);
buffer[strLen] = 0;
printf("Message form server: %s\n", buffer);
}
closesocket(sock);
WSACleanup();
return 0;
}
先運(yùn)行 server毅桃,再運(yùn)行 client褒纲,client 輸出結(jié)果為:
Input a string: C語言中文網(wǎng)
Message form server: C語言中文網(wǎng)
Input a string: c.biancheng.net Founded in 2012
Message form server: c.biancheng.net Founded in 2012
Input a string:
從代碼中可以看出,server.cpp 中沒有使用 listen() 函數(shù)钥飞,client.cpp 中也沒有使用 connect() 函數(shù)莺掠,因?yàn)?UDP 不需要連接。