目錄標題
- 前言
- 準備工作
- udpserver.hpp
- 成員變量
- 構造函數
- 初始化函數(socket,bind)
- start函數(recvfrom)
- udpServer.cc
- udpClient.hpp
- 構造函數
- 初始化函數
- run函數(sendto)
- udpClient.cc
- 測試
前言
在上一篇文章中我們初步的認識了端口號的作用,ip地址和MAC地址在網絡通信時起到的作用,以及網絡套接字的基本了解,那么這篇文章我們就對網絡編程常見的接口進行講解,并且使用這些接口實現一個簡單udp通信功能,本篇文章采用邊講邊實現的方式來帶著大家理解這些函數,在目錄里面我添加實現該功能時哪些函數會被講解,大家可以看一下。
準備工作
我們下面要實現一個簡單的通信功能,該功能分為客服端和服務端,客戶端向服務端發送數據,服務端將發送過來的數據打印到屏幕上即可:
因為客戶端和服務端要同時運行所以這里就創建兩個含有main函數的文件udpclient.cc udpserver.cc
,
這里采用面向對象的方式進行實現,所以這里就創建兩個類udpClient udpServer
來分別描述這里的發送消息和接收消息的行為,并分別將兩個類放到文件udpClient.hpp udpServer.hpp
里面,最后再創建一個makefile文件:
我們要給每個類都添加對應的構造函數,析構函數,初始化函數,運行函數(開始執行功能的函數),所以要實現的框架就是下面這樣:
//udpServer.hpp
class udpServer
{
public:udpServer()//構造函數{}void initServer()//初始化函數{}void start()//運行函數{}~udpServer()//析構函數{}
private:
};
//udpClient.hpp
class udpClient
{public:udpClient(){}void initClient(){}void run(){}~udpClient(){}private:
};
那么接下來的工作就是先實現udpserver類的具體內容。
udpserver.hpp
成員變量
udpserver用來描述服務端進程的類,所以他得有自己對應的ip地址和端口號,那么在類中就創建一個string類型的對象用來存儲ip地址和一個16位的無符號整數來存儲端口號:
class udpServer
{
public:udpServer(){}void initServer(){}void start(){}~udpServer(){}
private:string _ip;//存儲ip地址uint16_t _port;//存儲端口號
};
構造函數
構造函數就負責對這兩個類內成員變量進行初始化,因為有兩個變量所以構造函數就有兩個參數,因為在函數里面不會對參數進行修改所以參數的類型就得是const &類型,那么這里的代碼如下:
udpServer(const uint16_t& port, const string & ip)//構造函數//構造函數就負責獲取ip和端口號:_ip(ip),_port(port){}
大家在后面學習的時候會發現ip地址是可以通過某種方式被設置的,所以這里我們就先創建一個全局變量defaultip將其內容初始化為全0,然后將缺省參數設置為defaultip,那么這里代碼如下:
static const string defaultIp="0.0.0.0";
class udpServer
{
public:udpServer(const uint16_t& port, const string & ip=defaultIp)//構造函數//構造函數就負責獲取ip和端口號:_ip(ip),_port(port){}void initServer(){}void start(){}~udpServer(){}
private:string _ip;//存儲ip地址uint16_t _port;//存儲端口號
};
初始化函數(socket,bind)
當前是使用udp協議來進行通信,所以在通信之前就得使用套接字socket來創建一套網絡通信的文件機制,也就相當于在底層創建了一個網卡文件,然后將該文件與網卡設備關聯起來,我們來看看這個函數的參數:
第一個參數是標記位表示當前你想進行的是網絡通信還是本地通信,該參數的標記位有很多:
但是實際上經常使用的就只有前兩個:AF_UNIX(本地通信)和AF_INET(網絡通信)也就是上一篇文章中的這張圖片圖片:
第二個參數也是標記位表示當前套接字提供服務的類型也就是socket提供的能力類型,該標記位有下面這些:
比如說SOCK_STREAM就表示的是流式套接字,說人話就是該標記位在底層開了一套TCP策略來進行通信,SOCK_DGRAM表示的就是用戶數據報套接字說人話就是在底層開了一套UDP策略來進行通信,SOCK_RAW表示的就是原始套接字等等等,所以下面我們在使用socket的時候第一個傳輸就傳AF_INET,第二個參數就傳遞SOCK_DGRAM,第三個參數表示你想使用TCP_PROTOCOL還是UDP_PROTOCOL,但是前兩個參數的確定就已經確定socket套接字所提供的功能,所以第三個參數就顯得有點畫蛇添足,所以在傳遞第三個參數的時候直接傳遞0即可,那么這就是socket參數的意義,socket函數執行完之后就會返回一個int類型的數據,該數據就是一個文件描述符也就是之前說的在底層創建一個和網卡相關聯的文件的文件描述符,通過這個描述符就可以接受和發送消息, 如果創建套接字失敗該函數就會返回-1,所以對網絡的操作就和相當于之前對文件的操作,對網絡的讀寫相當于對文件的讀寫那么,所以在初始化函數里面我們就可以創建一個變量來接收socket函數的返回值,然后對該值進行判斷如果等于-1,我們就使用errno打印錯誤碼和錯誤碼對應的原因,并使用exit函數結束該進程,這里為了方便查看退出的原因就可以使用枚舉來進行替換,那么這里的代碼如下:
static const string defaultIp="0.0.0.0";
enum {USAGE_ERR = 1, SOCKET_ERR, BIND_ERR};
class udpServer
{
public:udpServer(const uint16_t& port, const string & ip=defaultIp)//構造函數//構造函數就負責獲取ip和端口號:_ip(ip),_port(port){}void initServer()//初始化函數//初始化函數里面就創建對應的端口號,然后對端口號進行bind{_sockfd=socket(AF_INET,SOCK_DGRAM,0);if(_sockfd==-1){//運行到這里說明創建端口失敗cout<<"socket error:"<< errno<<strerror(errno)<<endl;exit(SOCKET_ERR);}cout<<"socket success"<<" : "<<_sockfd<<endl;}void start(){}~udpServer(){}
private:string _ip;//存儲ip地址uint16_t _port;//存儲端口號
};
套接字創建完成之后這里就存在一個問題,我們上篇文章說過套接字是要有自己的ip地址和端口的,那這個套接字知道自己所要服務的ip地址和端口嗎?答案是不知道的,所以我們接下來就要將端口和ip地址綁定到這個套接字上面,那么這里就要用到函數bind
該函數的第一個參數表示要綁定哪個端口號也就是之前socket函數的返回值,第二個參數是一個sockaddr的結構體類型指針,在上一篇文章中我們說過因為套接字的類型有很多種并且當時創建接口的時候void*這種類型還沒有創建出來,所以這里就得創建了一個新的結構體類型以掩蓋底層套接字不同,這個結構體就是sockaddr,有了這個類型之后就可以用一個接口來服務多個套接字,該結構的構成如下:
雖然傳遞參數得是sockaddr的指針類型,但是實際在填寫的內容的時候依然得按照sockaddr_in的類型來進行填寫,該結構的構成如下:
最上面表示你想要通信的類型然后就是你要綁定的端口號和ip地址,所以我們得先創建一個結構體對象然后再以取地址加強制類型轉換的形式進行參數傳遞,該函數的第三個參數是一個socklen_t的類型實際上就是一個整形用來表示你傳遞的結構體長度,因為每個套接字結構體的長度都不一樣所以將長度傳遞給他之后他便知道了你之前填寫的是哪種類型,他在函數內部再將第二個參數的類型轉換回來,那么這就是bind函數的參數接收,該函數綁定成功之后就會返回0如果返回失敗就返回-1,所以我們就可以根據該函數的返回值來進行判斷是否成功,就接下來我們就先完成sockaddr_in結構體的填寫,首先該結構體的內容如下:
struct sockaddr_in
{_SOCKADDR_COMMON(sin_);in_port_t sin_port;struct in_addr sin_addr;//....
};
_SOCKADDR_COMMON(sin_);
是一個宏,該宏的定義如下:
#define _SOCKADDR_COMMON(sa_prefix) \
sa_family_t sa_prefix##family
這里的##就是用來形成新符號的,所以這里傳遞過來一個sin_最終這個參數就會形成sin_family,所以這個宏就會替換成為下面這樣:
_SOCKADDR_COMMON(sin_);
sa_family_t sin_family;
而這個sa_family_t類型就是之前說的協議家族也就是AF_INET,AF_UNIX等等等,結構體的第二個參數就是對應的端口號雖然是in_port_t類型但是本質上還是一個16位的無符號整數
typedef uint16_t in_Port_t;
第三個參數雖然是一個結構體但是in_addr結構體內部就只有一個in_addr_t類型的變量:
typedef uint32_t in_addr_t;
struct in_addr
{in_addr_t s_addr;
};
所以通過上面的代碼我們可以看到在操作系統中是用一個32位的整數來存儲ip地址的,但是我們在類中卻是使用string類型來存儲,那為什么要這樣呢?原因很簡單采用點分十進制的字符串可讀性特別的好符合我們人類的直覺,但是這種形式即需要轉換又需要特別大的空間并且網絡中的空間寸土寸金,所以在操作系統中就采用32位的無符號整數來進行存儲,在用戶層面中就是使用string類型來進行存儲,既然兩種類型完全不一樣所以在賦值之前必須得做一些轉換,那么這里的轉換就不需要我們自己來轉,操作系統中有對應的函數來實現,那么有了這些基礎我們就可以繼續完成初始化函數的實現,首先創建一個sockadd_in函數的對象,然后將其內容全部都初始化為0,那么這里我們可以使用bzero函數:
該函數就是將指定位置的值往后n個大小的空間全部都初始化為0,初始化完之后就將結構體內部的sin_family成員初始化為AF_INET,然后再將端口號填入到結構體的sin_port對象里面,這里大家要注意的一點就是在發送消息的時候也會將自己的端口號和ip地址發送過去,因為端口號的大小是兩個字節所以在綁定的時候得將其從主機序列轉換成為網絡序列,那么這里就可以使用htons函數來進行轉換:
然后再填寫ip地址,因為ip地址在用戶層和操作系統層存儲的形式不一樣所以這里就得做一些轉換,那么這里就可以使用函數inet_addr來將其轉換成為網絡序列并且該函數在轉換的時候還會對其大小端也進行轉換,將主機端轉化為網絡端:
將內容填完之后便可以使用bind函數將該結構體和套接字綁定起來,并創建一個變量用來記錄返回值以判斷綁定是否成功,那么該函數完整的代碼如下:
void initServer()//初始化函數
//初始化函數里面就創建對應的端口號,然后對端口號進行bind
{_sockfd=socket(AF_INET,SOCK_DGRAM,0);if(_sockfd==-1){//運行到這里說明創建端口失敗cout<<"socket error:"<< errno<<strerror(errno)<<endl;exit(SOCKET_ERR);}cout<<"socket success"<<" : "<<_sockfd<<endl;struct sockaddr_in local;bzero(&local,sizeof(sockaddr_in));local.sin_family=AF_INET;local.sin_port=htons(_port);local.sin_addr.s_addr=inet_addr(_ip.c_str());int res=bind(_sockfd,(struct sockaddr*)&local,sizeof(local));if(res==-1){cout<<"bind error: "<<errno<< strerror(errno)<<endl;exit(BIND_ERR);}
}
start函數(recvfrom)
服務器的本質就是死循環吧,所以start函數是一個死循環,在循環里面不停的接收別人發送過來的消息:
void start()//運行函數
{for(;;){}
}
那么用來接收消息的函數就是recvfrom,該函數的參數如下:
第一個參數表示從哪個套接字中讀取消息,第二個參數表示將讀取的數據放到哪個緩沖區中,第三個參數就是一次性讀取多少數據 ,第四個參數就表示以什么樣的方式來進行讀取這里我們就默認為0表示阻塞式的讀取,因為在接收消息的時候我們得知道是誰將消息發送了過來,所以第五個參數是一個輸出性參數該函數會將發送方的ip地址和端口號全部都填入第五個參數指向的結構體對象里面,第六個參數就表示傳過來指針指向的對象的大小,該函數調用結束之后就會返回讀取字符的個數,那么這就是該函數的使用形式用了這個函數我們就可以繼續完善start函數,首先在for循環的外面創建一個緩沖區用來接收數據,在for循環里面首先創建一個sockaddr_in對象,然后調用recvfrom函數進行接收消息,接收完了之后就可以根據返回值進行判斷如果返回值大于0我們就可以就讀取的數據進行打印:
void start()//運行函數
{char buffer[gnum]={0};for(;;){struct sockaddr_in peer; socklen_t len = sizeof(peer); //必填ssize_t s = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr*)&peer, &len);if(s>0){}}
}
打印數據的時候我們還要標注是那個ip地址和端口號發送過來的,所以在打印數據之前我們還得獲取ip地址和端口號,那么這里獲取就是從輸出型參數sockaddr_in中進行獲取,因為是從網絡發到主機來的所以在獲取端口號的時候得先進行轉換所以這里得用到ntohs函數將大端數據轉換成為主機端,因為網絡中的ip地址和存儲的形式不太一樣所以在獲取的時候得用到inet_ntoa函數來進行轉換將網絡形式轉換成為客戶形式并將大端數據轉換成為主機端數據,那么該函數完整的代碼如下:
void start()//運行函數
{char buffer[gnum]={0};for(;;){struct sockaddr_in peer; socklen_t len = sizeof(peer); //必填ssize_t s = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr*)&peer, &len);if(s>0){buffer[s]=0;string clientip=inet_ntoa(peer.sin_addr);uint16_t clientport = ntohs(peer.sin_port);cout << clientip <<"[" << clientport << "]# " << buffer << endl; }}
}
udpServer.cc
該文件裝的就是main函數,在main函數里面就創建一個udpServer對象然后調用初始化函數和start函數執行任務,但是這里存在一個問題udpServer對象的構造函數需要傳遞ip地址和端口號那從哪來獲取這兩個東西呢?答案是在運行可執行程序的時候傳遞這兩個參數就好比這樣:./udpServer.cc ip地址 端口號
,所以就得添加main函數的兩個參數:
int main(int argc,char* argv[])
{unique_ptr<udpServer> usr();usr->initServer();usr->start();return 0;
}
但是這里存在一種情況就是使用者傳遞多了或者少了參數,那么這個時候就會出現問題,所以在執行之前我們得判斷一下參數的個數,如果參數傳遞不對我們就執行一個函數用來告訴其正確的形式然后直接退出,然后將argv的第二個元素轉化成為端口號,第三個元素賦值給一個string對象,那么這里的代碼如下:
static void Usage(string proc)
{cout << "\nUsage:\n\t" << proc << "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[1]);string ip=argv[1];unique_ptr<udpServer> usr(new udpServer(port,ip));usr->initServer();usr->start();return 0;
}
那么這里我們可以單純的運行一下不傳遞任何的ip地址和端口號:
可以看到這里直接退出并顯示了正確的執行格式,然后我們可以使用ifconfig查看一下本地環回:
本地換回的地址就是127.0.0.1,我們網絡通信的地址是分層,發送消息的時候從上到下分裝的,發送到另外一臺機器的時候又是從下到上進行解包分用的比如說下面的圖片:
那么本地換回就是自己用來測試的,也就是在自己的主機上從上往下封裝,然后再在自己的自己主機上從下往上進行解包分用:
所以本地換回就是用來進行測試的,先來自己的地址進行測試如果測試成功了再考慮其他機器的測試,那么這里我們就可以進行一下測試:
可以看到當前有個進程確實是在運行,然后使用指令netstat -naup便可以查看所有的網絡進程:
可以到當前確實是有一個端口號為8081的進程并且該網絡進程的名字就是udpServer,所以這就證明當前是可以正常的執行本地換回的,那么平時我們是使用的公網ip鏈接的服務器,那么這里也是否能夠綁定公網ip呢?
答案是不可以的(有些還是可以具體情況以實踐為主)因為云服務器是虛擬化的服務器·,不能直接bind你的公網ip,但是如果你有一個虛擬機或者是一個真實的linux環境的話則可以bing公網ip,雖然公網ip我們不能直接bind但是還是可以直接綁定內網ip的:
但是雖然局域網ip或者私有ip可以直接綁定但是依然可能會出現華為云的機器不能和阿里云騰訊云的用戶進行通信,那我們該如何保證我們的機器能夠被其他的機器找到呢?實際上,一款網絡服務器是不建議指明一個IP的,一個服務器可能會有多個網卡所以也就可能存在多個IP的,如果只綁定了一個ip的話就可能會出現有很多的數據發到了不同的ip上這些數據都屬于這臺機器上的某個進程,但是卻只有這一個指定的ip能夠正常的收到,因為你只指明的綁定了那一個ip,所以為了讓所有的機器都能夠找到我們,我們在綁定ip地址的時候就是采用這樣的方法:
//local.sin_addr.s_addr=inet_addr(_ip.c_str());local.sin_addr.s_addr=INADDR_ANY;
而INADDR_ANY也就是一個宏他的真實定義就是一個宏:
#define INADDR_ANY ((in_addr_t)0x00000000)
也就是我們之前給的缺省參數,ip地址修改成這樣之后表示的意思就是未來發給這個機器的所有數據只要是發給8080端口的就會將數據發給8080對應的進程,所以這里就不會出現發給了一個ip而漏掉其他ip的現象,這是任意地址bind也是服務器的真實寫法。所以未來在執行服務端進程的時候就不需要傳遞ip地址直接傳遞端口就可以了:
#include"udpServer.hpp"
#include<memory>
static void Usage(string proc)
{cout << "\nUsage:\n\t" << proc << " local_port\n\n";
}int main(int argc,char* argv[])
{if(argc!=2){Usage(argv[0]);exit(1);}uint16_t port = atoi(argv[1]);unique_ptr<udpServer> usr(new udpServer(port));usr->initServer();usr->start();return 0;
}
udpClient.hpp
在這個文件夾里面裝的是客戶端所要執行的操作,這里要進行網絡通信所以也得有套接字,因為通信的時候得知道服務端的端口號和ip地址所以還得有兩個變量來存儲這些值,那么這里的代碼如下:
class udpClient
{public:udpClient(){}void initClient(){}void run(){}~udpClient(){}private: int _sockfd;string _serverip;uint16_t _serverport;
};
構造函數
構造函數需要兩個參數用來初始化_serverip和_serverport的值,這里的邏輯和客戶端差不多這里就不多說,直接上代碼:
udpClient(const string serverip,const uint16_t port)
:_serverip(serverip)
,_serverport(port)
,_sockfd(-1)
{}
初始化函數
初始化函數也是同樣的道理首先創建套接字然后對返回值進行判斷看是否創建成功?
void initClient()
{//創建套接字_sockfd=socket(AF_INET,SOCK_DGRAM,0);if(_sockfd==-1){cerr<<"socket error: "<<errno<<strerror(errno)<<endl;exit(SOCKET_ERR);}cout << "socket success: " << " : " << _sockfd << endl;
}
那接下來要對套接字進行綁定嗎?答案是必須要綁定的但是這里不需要我們人為的綁定,我們把端口值的選擇權交給了操作系統,那這是為什么呢?為什么服務端要人為的綁定而客戶端確實操作系統來綁定呢?原因是未來客戶端服務器是要明確的端口號不能隨意的改變,他得讓所有人都能夠知道,而服務端的端口號卻不需要明確他可以隨意的改變他只需要保證唯一性就可以了,并且寫服務器的一定是一家公司,但是客戶端卻可能是無數家公司,比如說抖音,今日頭條,西瓜視頻都屬于字節跳動的所以在這個公司內部肯定有明確的規定,但是客戶端在用戶的手機上面,一個手機上可能會有各種各樣的軟件所以而且這些軟件來自各種各樣的公司所以一旦客戶端也指定了端口號很可能就會出現兩個軟件爭搶一個端口號的現象,所以為了避免這樣的顯現就把客戶端的端口號選著的權利交給操作系統讓操作系統來自行分配,那么這里就存在一個問題:操作系統又是什么時候來進行分配呢?這個問題我們后面再進行解答,那么這就是初始化函數的全部內容。
run函數(sendto)
同樣的道理run函數也得是一個死循環,在run函數里面我們就需要向客服端發送消息,那么這里就得使用sendto函數,該函數的參數如下:
第一個參數就是將數據放到哪個套接字上,第二個參數就是消息現在存放在哪里,第三個參數表示消息有多少,第四個參數表示當前的屬性直接默認為0也就是阻塞式發送有數據就發沒數據就不發,因為發送數據的時候我們得順便告訴服務器是誰向你發送的數據,所以第五第六個參數就是用來填寫目標主機的端口號和ip地址,這個跟前面的類似就不多說了,所以run函數的實現就是先創建一個sockaddr_in對象然后填寫相應的信息,再創建一個string的對象和一個循環,在循環里面就往string對象里面填入信息然后將string對象的內容通過sendto函數發送到對應的主機里面,那么run函數的完整代碼如下:
void run()
{struct sockaddr_in server;//用來記錄服務端口的信息server.sin_family = AF_INET;server.sin_addr.s_addr = inet_addr(_serverip.c_str());server.sin_port = htons(_serverport);string tmp;while(1){cout << "Please Enter# ";cin>>tmp;sendto(_sockfd,tmp.c_str(),tmp.size(),0,(struct sockaddr*)&server,sizeof(server));}
}
udpClient.cc
該文件的實現和udpServer.cc文件的實現相差不大,唯一的區別就是這里必須得指明你往哪個機器發送,那么這里就不多說了:
#include"udpClient.hpp"
#include<memory>
static void Usage(string proc)
{cout << "\nUsage:\n\t" << proc << "local_ip local_port\n\n";
}
int main(int argc,char *argv[])
{if(argc!=3){Usage(argv[0]);exit(1);}string serverip=argv[1];uint16_t serverport=atoi(argv[2]);unique_ptr<udpClient> ucil(new udpClient(serverip,serverport));ucil->initClient();ucil->run();return 0;
}
測試
下面就可以開始進行測試,首先運行客戶端:
再運行服務端:
然后在服務端中輸入信息就可以看到客戶端中立馬將信息顯示了出來:
那么這就說明我們的代碼實現的沒有問題,那么完整的代碼就如下,首先是文件udpServer.hpp
#include <iostream>
#include <string>
#include <strings.h>
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
using namespace std;
static const string defaultIp="0.0.0.0";
static const int gnum =1024;
enum {USAGE_ERR = 1, SOCKET_ERR, BIND_ERR};
class udpServer
{
public:udpServer(const uint16_t& port, const string & ip=defaultIp)//構造函數//構造函數就負責獲取ip和端口號:_ip(ip),_port(port){}void initServer()//初始化函數//初始化函數里面就創建對應的端口號,然后對端口號進行bind{_sockfd=socket(AF_INET,SOCK_DGRAM,0);if(_sockfd==-1){//運行到這里說明創建端口失敗cout<<"socket error: "<< errno<<strerror(errno)<<endl;exit(SOCKET_ERR);}cout<<"socket success"<<" : "<<_sockfd<<endl;struct sockaddr_in local;bzero(&local,sizeof(sockaddr_in));local.sin_family=AF_INET;local.sin_port=htons(_port);//local.sin_addr.s_addr=inet_addr(_ip.c_str());local.sin_addr.s_addr=INADDR_ANY;int res=bind(_sockfd,(struct sockaddr*)&local,sizeof(local));if(res==-1){cout<<"bind error: "<<errno<< strerror(errno)<<endl;exit(BIND_ERR);}}void start()//運行函數{char buffer[gnum]={0};for(;;){struct sockaddr_in peer; socklen_t len = sizeof(peer); //必填ssize_t s = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr*)&peer, &len);if(s>0){buffer[s]=0;string clientip=inet_ntoa(peer.sin_addr);uint16_t clientport = ntohs(peer.sin_port);cout << clientip <<"[" << clientport << "]# " << buffer << endl; }}}~udpServer()//析構函數{}private:int _sockfd;string _ip;uint16_t _port;
};
udpServer.cc文件的內容如下:
#include"udpServer.hpp"
#include<memory>
static void Usage(string proc)
{cout << "\nUsage:\n\t" << proc << " local_port\n\n";
}int main(int argc,char* argv[])
{if(argc!=2){Usage(argv[0]);exit(1);}uint16_t port = atoi(argv[1]);unique_ptr<udpServer> usr(new udpServer(port));usr->initServer();usr->start();return 0;
}
udpClient.hpp的內容如下:
#include <iostream>
#include <string>
#include <strings.h>
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
using namespace std;
enum {USAGE_ERR = 1, SOCKET_ERR, BIND_ERR};
class udpClient
{public:udpClient(const string serverip,const uint16_t port):_serverip(serverip),_serverport(port),_sockfd(-1){}void initClient(){//創建套接字_sockfd=socket(AF_INET,SOCK_DGRAM,0);if(_sockfd==-1){cerr<<"socket error: "<<errno<<strerror(errno)<<endl;exit(SOCKET_ERR);}//無需綁定cout << "socket success: " << " : " << _sockfd << endl;}void run(){struct sockaddr_in server;//用來記錄服務端口的信息server.sin_family = AF_INET;server.sin_addr.s_addr = inet_addr(_serverip.c_str());server.sin_port = htons(_serverport);string tmp;while(1){cout << "Please Enter# ";cin>>tmp;sendto(_sockfd,tmp.c_str(),tmp.size(),0,(struct sockaddr*)&server,sizeof(server));}}~udpClient(){}private: int _sockfd;string _serverip;uint16_t _serverport;
};
udpClient.cc文件的內容如下:
#include"udpClient.hpp"
#include<memory>
static void Usage(string proc)
{cout << "\nUsage:\n\t" << proc << "local_ip local_port\n\n";
}
int main(int argc,char *argv[])
{if(argc!=3){Usage(argv[0]);exit(1);}string serverip=argv[1];uint16_t serverport=atoi(argv[2]);unique_ptr<udpClient> ucil(new udpClient(serverip,serverport));ucil->initClient();ucil->run();return 0;
}