? ? ? ? 本章將會詳細的介紹如何使用 socket 實現 UDP 協議的傳送數據。有了前面基礎知識的鋪墊。對于本章的理解將會變得簡單。將會從基礎的 Serve 的初始化,進階到 Client 的初始化,以及 run。最后實現一個簡陋的小型的網絡聊天室。
????????
目錄
????????1.UdpSever.h
? ? ? ?1.1 構造函數
? ? ? ?1.2 initServe 函數
? ? ? ? ?2. UdpSever.cc
? ? ? ? 3. udpClient.hpp?
? ? ? ? ?3.1 構造函數
? ? ? ? 3.2 init(初始化函數)
? ? ? ? 3.3 run
? ? ? 4. udpClient.cc
5. 對于??UdpSever.h 中run函數的實現
?6. 結尾
?
????????1.UdpSever.h
? ? ? ? ? ? 在這個文件里面需用定義 Sever 的一個類,里面變量為:_ip, _port,_sockfd。要實現的函數為構造函數、初始化(init)、運行(run)、析構函數。進行初始化是實現服務器的網絡的核心,當然run 也非常重要。關于實現構造函數的時候為什么只需要 port 以及defaultip是什么我后面都會進行講解。
class udpServer{public:udpServer(uint16_t port): _ip(defaultIp), _port(port){}void initServe(){}void start(){}~udpServer() {}private:uint16_t _port;string _ip;int _sockfd;};
? ? ? ?1.1 構造函數
????????里面使用了 port,因為這個服務器的特點,我們使用的 linux 有2個,或是2個以上的 ip,因此我們在進行綁定的時候是不可以進行直接綁定的。要不然當使用不同的 ip 的時候,會導致數據無法從客戶端發送到服務端。因此需要使用默認的ip = “0.0.0.0”。(后面的cc文件中使用智能指針的方式進行定義)
? ? ? ??1.2 initServe 函數
????????進行初始化,本質就是使用 socket 打開文件,會返回文件描述符(可以理解為打開網卡這個文件,然后進行傳遞數據)。然后進行 bind ,使用 struct sockaddr_in loacl, 對于這個結構體里面的數據進行填寫,然后進行 bind 綁定。
? ? ? ? 對于soket 函數,官方的定義為:返回一個文件描述符fd, 第一個參數是選擇網絡通信還是本地通信,第二變量表示是使用udp 還是 tcp, 我們使用的是 sock_dgram(數據報的方式發送數據),第三個變量表示阻塞狀態默認為 0;最后如何是返回 -1 進行退出操作。
_sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (_sockfd == -1){cerr << "socket error" << errno << " : " << strerror(errno) << endl;exit(SOCKET_ERROR);}
? ? ? ? 使用 bind 函數進行綁定。?需要文件的描述符,一個 sockaddr 的結構體,sizeof(sockaddr 的結構體)。關鍵就是在于處理這個 sockaddr 我們使用的 sockaddr_in 然后進行類型的轉化。最后我們使用的是先進行清零,然后 第一個位置放上協議家族:AF_INET, 第二位置放上 port 端口號需要使用 htons(host主機到net網絡當中為 short 的 16 位的方式), ip 直接使用 inet_addr(將點分十進制轉化位 32 位的整形,然后變成網絡當中的大端方式發送)。還需要注意的是由于 inet_addr 傳遞的字符串的指針,所以是使用 c_str。整體的代碼如下。
?
void initServe(){// 使用 socket 函數,然后進行綁定_sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (_sockfd == -1){cerr << "socket error" << errno << " : " << strerror(errno) << endl;exit(SOCKET_ERROR);}// 進行band 綁定,注意是使用結構體進行綁定struct sockaddr_in local;bzero(&local, sizeof(local));local.sin_family = AF_INET;local.sin_port = htons(_port);local.sin_addr.s_addr = inet_addr(_ip.c_str());// 設置好 addr_in 就去進行綁定int n = bind(_sockfd, (struct sockaddr *)&local, sizeof(local));if (n == -1){cerr << "bind_error" << " : " << strerror(errno) << endl;exit(BIND_ERROR);}}
? ? ? ? 對于啟動函數,他本質就是一個無限的循環。?
for(;;)
? ? ? ? ?2. UdpSever.cc
? ? ? ? 就。是在運行的時候,我們需要用戶使用 ./ UdpSever port 如果不是就會提示如何使用。然后因為 argv[1] 使用的是字符串的方式,直接 atoi 就可以轉化位整型。然后就是智能指針的定義,進行初始化,以及啟動函數。
#include "udpServe.hpp"
#include <memory>
#include <unordered_map>using namespace djx;
static void Usage(std::string tmp)
{std::cout << "Usage:\n" << tmp << "local_port\n\n";
}
int main(int argc, char* argv[])
{if(argc != 2){//說明輸入錯誤,需要重新輸入Usage(argv[0]);exit(IN_ERROR);}uint16_t port = atoi(argv[1]);//然后需要啟動定義好的 udpstd::unique_ptr<udpServer> usvr(new udpServer(port));usvr->initServe();usvr->start();return 0;
}
? ? ? ? ?最終實現的樣式為這樣,已經成功運行了。
? ? ? ? 3. udpClient.hpp?
? ? ? ? ? ? ? ? 跟udpSever是一樣的都是4個函數,接下來我將會一一介紹。
? ? ? ? ?3.1 構造函數
? ? ? ? ? ? ? ? 這個構造函數就需要告訴你,你要發送的服務器的ip地址,以及 port 端口號。 所以定義如下:
udpClient(uint16_t serveport, string serveip):_serveport(serveport),_serveip(serveip){}
? ? ? ? 3.2 init(初始化函數)
? ? ? ? ? ? ? ? 與udpSever一樣都是使用socket,然后使用bind。但是這個bind 不要們進行顯示的綁定,但是是需要進行綁定的。 為了保證獨立性,ip 與 port 由操作系統為我們進行提供。
void initClient(){//click 是實現服務端的傳送數據_sockfd = socket(AF_INET, SOCK_DGRAM, 0);if(_sockfd == -1){cerr << "socket error" << errno << " : " << strerror(errno) << endl;exit(2);}//進行綁定不要顯示的進行綁定,是操作系統會幫助我們進行生成隨機獨立唯一的一個 port}
? ? ? ? 3.3 run
? ? ? ? run就是運行函數,使用函數接口 sendto ,這個是一個輸出型函數。是要將buf 緩沖里面的東西發送到 severip 和 severport 的 服務器當中,所以需要使用 sockaddr_in 對于里面的內容進行初始化。然后是對于 buffer 內容的輸入。是使用 string message 類型的來進行存放數據。
void run(){//需要使用函數 sendto, 但是在之前需要先獲取到 secve 的ip 以及 portstruct sockaddr_in serve;serve.sin_family = AF_INET;serve.sin_port = htons(_serveport);serve.sin_addr.s_addr = inet_addr(_serveip.c_str());while(1){string message;cin >> message;sendto(_sockfd, message.c_str(), sizeof(message), 0, (struct sockaddr*)&serve, sizeof(serve));}}
? ? ? 4. udpClient.cc
? ? ? ? 與 Serve 類型都是先進行判斷輸入的argc 是為 3,如果是就進行啟動。不是就告訴你如何使用。
#include "udpClient(fianll).hpp"
#include <memory>
using namespace std;
using namespace djx;
static void Usage(std::string tmp)
{cout << "Usage:\n\t" << tmp << " local_ip "<< "local_port\n\n";
}
int main(int argc, char* argv[])
{if(argc != 3){Usage(argv[0]);exit(1);}uint16_t port = atoi(argv[2]);string ip = argv[1];std::unique_ptr<udpClient> uclick(new udpClient(port, ip));uclick->initClient();uclick->run();return 0;
}
5. 對于??UdpSever.h 中run函數的實現
? ? ? ? start 函數需要接受從客服端發來的信息,通過之前啟動的 sokce 以及 bind,啟動了 udp 協議。內核就會幫助我們對于定義的 struct scokaddr_in 進行內容的填充(來自客戶端發送過來的數據)。里面需要的函數為 recvfrom 這是一個輸入輸出型參數。從? struct scokaddr_in 獲取輸入參數,將參數輸出到 buffer 這個緩存當中,然后進行打印。
? ? ? ? 整體的實現如下,其中 full 定義為 1024 個整形。然后 s 表示的是buffer 里面讀取到的個數,如果是小于0,就表示沒有數據,直接結束此次循環。為了照顧計算機網絡的數據嚴格傳送的特點,len 的長度需要定義為 socklen_t 。使用 char 的時候寫入的大小是從 0 開始的,所以是要sizeof -? 1.
void start(){char buffer[full]; for (;;){// 用來存放 哪個click發送來的數據struct sockaddr_in peer;socklen_t len = sizeof(peer); // 為了照顧vecvfrom函數,所以使用這個socken_t;ssize_t s = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &len);if (s > 0){buffer[s] = 0; string message = buffer; // s表示獲取到的長度string click_ip = inet_ntoa(peer.sin_addr); // 直接就是從結構體變過來int click_port = ntohs(peer.sin_port);cout << click_ip << "[" << click_port << "]#" << message << endl;}}}
?6. 結尾
? ? ? ? 到此 udp 簡單實現已經結束了,還不是很全面因為我們是要得到數據,然后實現相應的工作,這里還沒有實現功能,就是實現了一個簡陋的網絡聊天室。任意一臺主機通過輸入./udpClient ip + port, 我都可以在我的服務器上看見對應的信息。
????????以上是對于計算網絡中基礎知識的了解,這個文章用于我的學習記錄,如果是有其他的錯誤還請批評指正。如果對你有幫助還請給我點個贊👍👍👍。?????
????????
? ? ? ? ? ? ? ? ?