一、開發(fā)環(huán)境
本次實驗是在騰訊云服務器上進行
二、服務端實現(xiàn)
做完這次實驗,感受最深的就是函數(shù)接口方面的問題,我們先來介紹一下需要用到的接口。
2.1 接口認識
2.1.1 socket創(chuàng)建網(wǎng)絡通信套接字
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
參數(shù)domain:域,未來套接字是進行網(wǎng)絡通信(AF_INET)還是本地通信(AF_UNIX, AF_LOCAL)
參數(shù)type:套接字提供服務的類型,如SOCK_STREAM:流式服務TCP策略,SOCK_DGRAM:數(shù)據(jù)報服務,UDP策略
參數(shù)protocol:缺省為0,可由前兩個類型確定
返回值:失敗返回-1,成功返回文件描述符
2.1.2 bind:綁定Ip和端口號
綁定端口號和ip
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
參數(shù)sockfd:文件描述符,也就是調(diào)用socket的返回值
參數(shù)addr:利用struct sockaddr_in強轉(zhuǎn)
參數(shù)addrlen:結(jié)構(gòu)體的大小
返回值:成功返回0,失敗返回-1
2.1.3 sockaddr_in結(jié)構(gòu)體
struct sockaddr_in
{
__SOCKADDR_COMMON (sin_);//協(xié)議家族,對應AF_INET
in_port_t sin_port; //端口號,in_port_t是對port的重命名
struct in_addr sin_addr;//IP地址,in_addr結(jié)構(gòu)體里封裝了一個32位整數(shù)
/* Pad to size of `struct sockaddr'. */
unsigned char sin_zero[sizeof (struct sockaddr) -
__SOCKADDR_COMMON_SIZE -
sizeof (in_port_t) -
sizeof (struct in_addr)];
};
#define __SOCKADDR_COMMON(sa_prefix) \
sa_family_t sa_prefix##family
//sa_family_t //16位整數(shù)
typedef uint16_t in_port_t;
typedef uint32_t in_addr_t;
struct in_addr
{
in_addr_t s_addr;
};
宏中的**##**是將兩個字符串合并成一個新字符串,也就是將接收到的sa_prefix與family合并起來,形成了sa_prefix_family
創(chuàng)建結(jié)構(gòu)體后要先清空數(shù)據(jù)
#include <strings.h>
void bzero(void *s, size_t n);
2.1.4 IP地址轉(zhuǎn)換函數(shù):inet_addr、inet_ntoa
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
inet_addr內(nèi)部完成兩件事情:1.把字符串轉(zhuǎn)化成整數(shù);2.再把整數(shù)轉(zhuǎn)化成對應的網(wǎng)絡序列**
in_addr_t inet_addr(const char *cp);
//const char*cp:點分十進制風格的IP地址
//1.網(wǎng)絡->主機 2.uint32_t -> 點分十進制
char *inet_ntoa(struct in_addr in);
2.1.5 recvfrom:讀取數(shù)據(jù)
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);
sockfd:特定的套接字,buf:讀取到特定緩沖區(qū),len:結(jié)構(gòu)體大小
flags:讀取的方式,默認為0,阻塞讀取
src_addr:輸入輸出型參數(shù),收到消息除了本身,還得知道是那個IP+port發(fā)過來的數(shù)據(jù)
len:大小是多少
返回-1表示失敗,成功返回字節(jié)數(shù)
2.2 頭文件udpServer.hpp
頭文件中:構(gòu)造負責初始化參數(shù)
initServer函數(shù)負責初始化服務器:1.創(chuàng)建套接字;2.將套接字與IP地址和端口號綁定。
start()函數(shù)服務器本質(zhì)是一個死循環(huán),在start啟動的時候,通過recvfrom
讀取通過網(wǎng)絡發(fā)來的數(shù)據(jù)、源ip+源端口號分別保存到緩沖區(qū)和struct sockaddr這個結(jié)構(gòu)體中
#pragma once
#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <functional>
namespace Server
{
using namespace std;
//默認的點分十進制IP
static const string defaultIP="0.0.0.0";
static const int gnum=1024;//緩沖區(qū)大小
enum{USAGE_ERR=1,SOCKET_ERR,BIND_ERR};//退出碼
typedef function<void(string,uint16_t,string)> func_t;
class udpServer
{
public:
udpServer(const func_t& callback,const uint16_t &port,const string& ip=defaultIP)
:callback_(callback),port_(port),ip_(ip),sockfd_(-1)
{}
void initServer()
{
//****創(chuàng)建UDP網(wǎng)絡通信端口****
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;
//******綁定port,ip****
//服務器要明確綁定port,不能隨意改變,需要程序員顯示指明
struct sockaddr_in local;
bzero(&local,sizeof(local));//對結(jié)構(gòu)體數(shù)據(jù)清0
//填充結(jié)構(gòu)體
local.sin_family=AF_INET;
//大小端轉(zhuǎn)換,給別人發(fā)消息,端口號和IP地址也要發(fā)給對方
local.sin_port=htons(port_);//端口號轉(zhuǎn)網(wǎng)絡
//1.string->uint32_t 2.htonl()---->兩個工作統(tǒng)一交給inet_addr
local.sin_addr.s_addr=inet_addr(ip_.c_str());//ip轉(zhuǎn)網(wǎng)絡
// local.sin_addr.s_addr=htonl(INADDR_ANY);//任意地址綁定,服務器真正寫法,上面與這個選一個就行
int n=bind(sockfd_,(struct sockaddr*)&local,sizeof(local));
if(n==-1)
{
cerr << "bind error: " << errno << " : " << strerror(errno) << endl;
exit(BIND_ERR);
}
}
void start()
{
char buffer[gnum];
for(;;)
{
//****讀取數(shù)據(jù)****
//服務器的本質(zhì)就是一個死循環(huán)
struct sockaddr_in peer;//做輸入輸出型參數(shù)
socklen_t len=sizeof(peer);//必填
//數(shù)據(jù)獲?。篒P地址+端口號+數(shù)據(jù)
ssize_t s= recvfrom(sockfd_,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&peer,&len);
if(s>0)
{
buffer[s]=0;
//1.網(wǎng)絡->主機 2.uint32_t -> 點分十進制
//string clientip=peer.sin_addr.s_addr;需要轉(zhuǎn)兩步,不方便
string clientip=inet_ntoa(peer.sin_addr);
uint16_t clientport=ntohs(peer.sin_port);
string message=buffer;
cout<<clientip<<"["<<clientport<<"]#"<<message<<endl;
//利用回調(diào)方法處理數(shù)據(jù),實現(xiàn)解耦
callback_(clientip,clientport,message);
}
}
}
~udpServer(){}
private:
uint16_t port_;//端口號
string ip_;//IP地址
int sockfd_;//文件描述符
func_t callback_;//回調(diào)
};
}
2.3 綁定IP和port問題
- 一般情況下,服務器不會綁定某一個確定的IP,避免因綁定一個確定的
IP而漏掉另一個ip發(fā)過來的數(shù)據(jù),實際情況是,將ip設(shè)置為全0,任何提交到服務器的開放端口的數(shù)據(jù)都會被該服務器處理- 服務器不需要綁定一個固定IP(目的ip有多個,可能是本地環(huán)回、內(nèi)網(wǎng)IP、公網(wǎng)IP),只要是訪問服務器上的某個開放的端口,都會把數(shù)據(jù)拿過來處理,但這并不意味著客戶端不需要綁定IP
- 這個IP是目的IP,是已經(jīng)收到了數(shù)據(jù)向上交付的時候,不需要綁定IP,只需要看端口號就行了
本地環(huán)回:客戶端與服務端在一臺主機上,進行通信的時候數(shù)據(jù)貫穿協(xié)議棧流動,但不會到達物理層(出不去),僅測試
內(nèi)網(wǎng)IP:真正屬于這個服務器的IP,同一個品牌的服務器可以用內(nèi)網(wǎng)ip通信
公網(wǎng)IP:云服務器是虛擬的,不能直接bind公網(wǎng)IP
虛擬機或者真正的linux可以綁定
內(nèi)網(wǎng)IP可以被綁定
2.4 源文件udpServer.cc
服務器端進行測試的時候不需要提供IP地址
#include "udpServer.hpp"
#include <memory>
using namespace std;
using namespace Server;
static void Usage(string proc)
{
cout << "\nUsage:\n\t" << proc << " local_port\n\n";
}
void handlerMessage(string,uint16_t,string)
{
//對message進行處理,完成server通信與業(yè)務邏輯解耦
}
int main(int argc,char* argv[])
{
if(argc != 2)
{
Usage(argv[0]);
exit(USAGE_ERR);
}
uint16_t port=atoi(argv[1]);
std::unique_ptr<udpServer> usvr(new udpServer(handlerMessage,port));
usvr->initServer();
usvr->start();
return 0;
}
三、客戶端實現(xiàn)
3.1 接口認識
3.1.1 數(shù)據(jù)發(fā)送:sendto
#include <sys/types.h>
#include <sys/socket.h>
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);
sockfd:發(fā)送給哪個套接字,
buf:要發(fā)送的消息;len:消息的字節(jié)數(shù);flags:0,有數(shù)據(jù)就發(fā),沒數(shù)據(jù)就阻塞
dest_addr:這個結(jié)構(gòu)體也是由struct sockaddr_in強轉(zhuǎn),指明向誰發(fā),填充服務器的IP和端口
addrlen:輸入型參數(shù)
3.2 頭文件udpClient.hpp
在這個文件中:構(gòu)造函數(shù)負責初始化變量
initClient函數(shù)負責創(chuàng)建套接字但是不需要程序員手動綁定端口號,OS會幫我們綁
服務端明確綁定是因為需要客戶端知道,并且不能隨便改變;未來是多個客戶端訪問一個服務端,客戶端端口號是多少不重要,保證唯一性就行
run()函數(shù)負責發(fā)送數(shù)據(jù)到服務端,需要用到sendto函數(shù),而sendto函數(shù)在首次發(fā)送數(shù)據(jù)的時候,OS發(fā)現(xiàn)客戶端還未綁定端口號,會令sendto函數(shù)自動綁定一個端口號
#pragma once
#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
namespace Client
{
using namespace std;
// //默認的點分十進制IP
// static const string defaultIP="0.0.0.0";
// static const int gnum=1024;
enum{USAGE_ERR=1,SOCKET_ERR,BIND_ERR};//退出碼
class udpClient
{
public:
udpClient(const string& serverip,const uint16_t&serverport)
:serverip_(serverip),serverport_(serverport),sockfd_(-1),quit_(false)
{}
void initClient()
{
//****1.創(chuàng)建UDP網(wǎng)絡通信端口****
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;
//****Client必須要bind,但是Client不需要顯示bind(不需要自己寫)******/
//客戶端端口是多少不重要,只要能保證唯一性就行,讓OS自己去綁
}
void run()
{
/*********發(fā)送數(shù)據(jù)***********/
struct sockaddr_in server;
memset(&server,0,sizeof(server));
server.sin_family=AF_INET;
//主機序列轉(zhuǎn)網(wǎng)絡序列
server.sin_addr.s_addr=inet_addr(serverip_.c_str());
server.sin_port=htons(serverport_);
string message;
while (!quit_)
{
cout<<"Please Enter# "<<endl;
cin>>message;
//首次向服務器發(fā)送數(shù)據(jù)的時候,OS識別到還未綁定,sendto自動綁定ip+port
sendto(sockfd_,message.c_str(),message.size(),0,(struct sockaddr*)&server,sizeof(server));
}
}
~udpClient()
{}
private:
int sockfd_;
string serverip_;
uint16_t serverport_;
bool quit_;
};
}
3.3 源文件udpClient.cc
客戶端進行測試的時候必須要提供IP地址和端口號
#include "udpClient.hpp"
#include <memory>
using namespace std;
using namespace Client;
static void Usage(string proc)
{
cout << "\nUsage:\n\t" <<proc << " server_ip server_port\n\n";
}
//udpClient server_ip server_port
int main(int argc,char* argv[])
{
if(argc!=3)
{
Usage(argv[0]);
exit(USAGE_ERR);
}
//客戶端必須要知道發(fā)給誰
string serverip=argv[1];
uint16_t serverport=atoi(argv[2]);
unique_ptr<udpClient> ucli(new udpClient(serverip,serverport));
ucli->initClient();
ucli->run();
return 0;
}
四、結(jié)果展示
無論是客戶端還是服務端的測試文件,所要傳遞的參數(shù)(ip、port
)與頭文件中struct sockaddr_in
需要填充的ip、port沒有關(guān)系文章來源:http://www.zghlxwxcb.cn/news/detail-438614.html
這篇博客只是講解一下UDP下網(wǎng)絡通信的邏輯,關(guān)于它的應用方面會在近期推出,敬請關(guān)注!!!文章來源地址http://www.zghlxwxcb.cn/news/detail-438614.html
到了這里,關(guān)于linux【網(wǎng)絡編程】之UDP網(wǎng)絡程序模擬實現(xiàn)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!