轉(zhuǎn)自
前面的文章中我們給出了幾個TCP的例子,對于UDP而言,只要能理解前面的內(nèi)容,實現(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個套接字
TCP中,套接字是一對一的關(guān)系。如要向10個客戶端提供服務(wù),那么除了負(fù)責(zé)監(jiān)聽的套接字外,還需要創(chuàng)建10套接字。但在UDP中,不管是服務(wù)器端還是客戶端都只需要1個套接字。之前解釋UDP原理的時候舉了郵寄包裹的例子,負(fù)責(zé)郵寄包裹的快遞公司可以比喻為UDP套接字,只要有1個快遞公司,就可以通過它向任意地址郵寄包裹。同樣,只需1個UDP套接字就可以向任意主機傳送數(shù)據(jù)。
基于UDP的接收和發(fā)送函數(shù)
創(chuàng)建好TCP套接字后,傳輸數(shù)據(jù)時無需再添加地址信息,因為TCP套接字將保持與對方套接字的連接。換言之,TCP套接字知道目標(biāo)地址信息。但UDP套接字不會保持連接狀態(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:用于傳輸UDP數(shù)據(jù)的套接字;
- buf:保存待傳輸數(shù)據(jù)的緩沖區(qū)地址;
- nbytes:帶傳輸數(shù)據(jù)的長度(以字節(jié)計);
- flags:可選項參數(shù),若沒有可傳遞0;
- to:存有目標(biāo)地址信息的 sockaddr 結(jié)構(gòu)體變量的地址;
- addrlen:傳遞給參數(shù) to 的地址值結(jié)構(gòu)體變量的長度。
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:可選項參數(shù),若沒有可傳遞0;
- from:存有發(fā)送端地址信息的sockaddr結(jié)構(gòu)體變量的地址;
- addrlen:保存參數(shù) from 的結(jié)構(gòu)體變量長度的變量地址值。
基于UDP的回聲服務(wù)器端/客戶端
下面結(jié)合之前的內(nèi)容實現(xiàn)回聲客戶端。需要注意的是,UDP不同于TCP,不存在請求連接和受理過程,因此在某種意義上無法明確區(qū)分服務(wù)器端和客戶端,只是因為其提供服務(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)); //每個字節(jié)都用0填充
servAddr.sin_family = PF_INET; //使用IPv4地址
servAddr.sin_addr.s_addr = htonl(INADDR_ANY); //自動獲取IP地址
servAddr.sin_port = htons(1234); //端口
bind(sock, (SOCKADDR*)&servAddr, sizeof(SOCKADDR));
//接收客戶端請求
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)建套接字時,向 socket() 第二個參數(shù)傳遞 SOCK_DGRAM,以指明使用UDP協(xié)議。
第18行代碼中使用htonl(INADDR_ANY)來自動獲取IP地址。
利用常數(shù) INADDR_ANY 自動獲取IP地址有一個明顯的好處,就是當(dāng)軟件安裝到其他服務(wù)器或者服務(wù)器IP地址改變時,不用再更改源碼重新編譯,也不用在啟動軟件時手動輸入。而且,如果一臺計算機中已分配多個IP地址(例如路由器),那么只要端口號一致,就可以從不同的IP地址接收數(shù)據(jù)。所以,服務(wù)器中優(yōu)先考慮使用INADDR_ANY;而客戶端中除非帶有一部分服務(wù)器功能,否則不會采用。
客戶端 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)); //每個字節(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;
}
先運行 server,再運行 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ù),因為 UDP 不需要連接。