[socket編程-基于UDP傳輸?shù)睦覿

轉(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;
}

代碼說明:

  1. 第12行代碼在創(chuàng)建套接字時,向 socket() 第二個參數(shù)傳遞 SOCK_DGRAM,以指明使用UDP協(xié)議。

  2. 第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 不需要連接。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容