代码的整体逻辑:
UDP服务端
:udpServer.cc(服务端的调用),udpServer.hpp(服务端的实现)
UDP客户端
:udpClient.cc(客户端的调用),udpClient.hpp(客户端的实现)
1.udp服务端
服务端:1.初始化服务器 2.启动服务器
作为一款服务器:要有自己的服务端口号uint16_t _port,同时网络服务器需要有对应的string _ip地址,文件描述符_sockfd:进行各种各样的数据通信,在类内进行读写操作。对于ip地址的类型:字符串型只在我们用户层作为参数传递,传递的IP格式为" 127.0.0.1 "这种格式的IP称之为“点分十进制"IP,这样的IP是字符串,可读性高,但是仅仅在我们用户层使用,但是在网络通信的过程中IP是需要转换为 uint32_t 类型,这时候需要调用相关的接口转换为网络通信使用的的IP风格。
一.初始化服务器
初始化服务器服务器包括:创建套接字和绑定端口号和IP
参数据数domain:域,未来套接字是进行网络通信还是本地通信,主要使用下面这两种 :
AF_UNIX, AF_LOCAL
AF_INET
参数type:套接字提供服务的类型,如SOCK_STREAM:流式服务TCP策略,SOCK_DGRAM:数据报服务,UDP策略参数protocol:缺省为0,可由前两个类型由系统决定并填充
返回值:失败返回-1,成功返回文件描述符
绑定端口号和IP的接口:
参数sockfd:文件描述符进行通信,也就是调用socket的返回值
参数addr:利用struct sockaddr_in强转
参数addrlen:结构体的长度
返回值:成功返回0,失败返回-1
这里需要定义一个sockaddr_in
结构体并填充数据,然后传递进去
sockaddr_in结构体的内部成员:
struct sockaddr_in
{
__SOCKADDR_COMMON (sin_);
in_port_t sin_port; /* Port number. */
struct in_addr sin_addr; /* Internet address. */
/* Pad to size of `struct sockaddr'. */
unsigned char sin_zero[sizeof (struct sockaddr) -
__SOCKADDR_COMMON_SIZE -
sizeof (in_port_t) -
sizeof (struct in_addr)];
};
在结构体内部:首先填充协议家族,给别人发消息未来还要从对方接收消息,所以需要将自己的port和IP填充进去,port需要由主机序列转为网络序列,因为初始传来的ip是string类型的所以IP需要由点分十进制转为整数然后再转为网络序列。
一款网络服务器不建议指明一个IP,也就是不要显示地绑定IP,服务器IP可能不止一个,如果只绑定一个明确的IP,最终的数据可能用别的IP来访问端口号,访问不出来,所以真实的服务器IP一般采用INADDR_ANY(全0,任意地址)代表任意地址bind
初始化服务器的代码:
void initServer()
{
//1.创建套接字
_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;
//2.绑定端口号port,ip
struct sockaddr_in local;//定义了一个变量
//结构体清空
bzero(&local,sizeof(local));
local.sin_family = AF_INET;//协议家族
local.sin_port=htons(_port);//给别人发消息,port和ip也要发给对方,需要大小端转换
//local.sin_addr.s_addr = inet_addr(_ip.c_str());//1.ip转成整数 2.整数要转成网络序列:htonl();
local.sin_addr.s_addr=inet_addr(INADDR_ANY);
int n = bind(_sockfd,(struct sockaddr*)&local,sizeof(local));
if(n==-1)
{
cerr<<"bind error: "<<errno<<" : "<<strerror(errno)<<endl;
exit(BIND_ERR);
}
}
二.启动服务器
服务器只要启动了就是不断地在接收并处理数据最后有必要时还要给客户端返回一些数据,所以要一直循环的运行,服务器是一个常驻内存的进程。
接收数据:
#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:读取到特定缓冲区,len:多长
flags:读取的方式,默认为0,阻塞读取
src_addr:收到消息除了本身,还得知道是谁发的,输入输出型参数,返回对应的消息内容是从哪一个client发来的,len:大小是多少
返回-1表示失败,成功返回字节数
创建一个类型为sockaddr_in 的结构体,未来根据这个结构体可以得知发送数据主机的IP和端口号
void start()
{
char buffer[gnum];
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);//1.网络序列转化成本地序列2.4字节整数IP转化成点分十进制
uint16_t clientport = ntohs(peer.sin_port);
string message = buffer;
cout<<clientip<<"["<<clientport<<"]# "<<message<<endl;
}
}
}
三.上层的调用
#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";
}
// ./udpServer port
int main(int argc,char*argv[])
{
if(argc != 2)
{
Usage(argv[0]);
exit(1);
}
uint16_t port = atoi(argv[1]);
std::unique_ptr<udpServer> usvr(new udpServer(port));
usvr->initServer();
usvr->start();
return 0;
}
2.udp客户端
1.客户端上层的调用
启动客户端方法为 : ./udpClient server_ip server_port,客户端想连接服务器,必须得知道对方的IP(公网IP)和端口号调用逻辑如下:
#include "udpClient.hpp"
#include <memory>
using namespace Client;
static void Usage(string proc)
{
cout<<"\nUsage:\n\t"<<proc<<" server_ip server_port\n\n";
}
//./udpCleint server_ip server_port
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> ucli(new udpClient(serverip,serverport));
ucli->initClient();
ucli->run();
return 0;
}
2.初始化客户端
初始化客户端需要创建套接字,客户端需要端口,但是不重要,自己有端口号就可以,不需要显示的绑定会由操作系统帮助我们找到一个没有被绑定的端口号并绑定。
3.启动客户端
#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:哪个套接字,buf:缓冲区,len:长度,flags:0,有数据就发,没数据就阻塞
dest_addr:这个结构体也是由struct sockaddr_in强转,向谁发,填充对方的IP和端口
addrlen:没有指针,输入型参数
代码实现:
void run()
{
struct sockaddr_in server;
memset(&server,0,sizeof(server));
server.sin_family = AF_INET;
server.sin_addr.s_addr = inet_addr(_serverip.c_str());
server.sin_port = htons(_serverport);
string message;
while(!_quit)
{
cout<<"Please Enter# ";
cin>>message;
//发送给远端服务器
sendto(_sockfd,message.c_str(),message.size(),0,(struct sockaddr*)&server,sizeof(server));
}
}
3.测试服务器
IP 为 127.0.0.1是本地回环地址,通俗的讲,就是我们在主机上发送给127
开头的IP地址的数据包会被发送的主机自己接收,根本传不出去,外部设备也无法通过回环地址访问到本机,数据只是在本机内协议贯穿,根本不会到达物理层,用来服务器代码的测试。
4.整体代码
//udpServer.hpp
#pragma once
#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <arpa/inet.h>
#include <strings.h>
#include <netinet/in.h>
#include <functional>
namespace Server
{
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)
:_port(port),_ip(ip),_sockfd(-1)
{}
void initServer()
{
//1.创建套接字
_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;
//2.绑定端口号port,ip
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());
local.sin_addr.s_addr = htons(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(;;)
{
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);//1.网络序列转化成本地序列2.4字节整数IP转化成点分十进制
uint16_t clientport = ntohs(peer.sin_port);
string message = buffer;
cout<<clientip<<"["<<clientport<<"]# "<<message<<endl;
}
}
}
private:
uint16_t _port;//端口号
string _ip;//ip地址
int _sockfd;//文件描述符
};
}
//udpServer.cc
#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";
}
// ./udpServer port
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(port));
usvr->initServer();
usvr->start();
return 0;
}
//udpClient.hpp
#pragma once
#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <arpa/inet.h>
#include <strings.h>
#include <netinet/in.h>
namespace Client
{
using namespace std;
class udpClient
{
public:
udpClient(const string&serverip,const uint16_t &serverport)
:_serverip(serverip),_serverport(serverport),_sockfd(-1),_quit(false)
{}
void initClient()
{
//客户端创建socket
_sockfd = socket(AF_INET,SOCK_DGRAM,0);
if(_sockfd == -1)
{
cerr<<"socket error: "<<errno<<" : "<<strerror(errno)<<endl;
exit(2);
}
cout<<"socket success "<<" : "<<_sockfd<<endl;
}
void run()
{
struct sockaddr_in server;
memset(&server,0,sizeof(server));
server.sin_family = AF_INET;
server.sin_addr.s_addr = inet_addr(_serverip.c_str());
server.sin_port = htons(_serverport);
string message;
while(!_quit)
{
cout<<"Please Enter# ";
cin>>message;
sendto(_sockfd,message.c_str(),message.size(),0,(struct sockaddr*)&server,sizeof(server));
}
}
private:
int _sockfd;
string _serverip;
uint16_t _serverport;
bool _quit;
};
}
//udpClient.cc
#include "udpClient.hpp"
#include <memory>
using namespace Client;
static void Usage(string proc)
{
cout<<"\nUsage:\n\t"<<proc<<" server_ip server_port\n\n";
}
//./udpCleint server_ip server_port
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> ucli(new udpClient(serverip,serverport));
ucli->initClient();
ucli->run();
return 0;
}
makefile文件: