【网络】UDP网络服务器简单模拟实现
文章目录
- makefile
- 服务端udpServer
- udpServer.cc
- udpServer.hpp
- 初始化
- 启动
- 测试
- 客户端udpClient
- udpClient.cc
- udpClient.hpp
- 初始化
- 启动
- 整体代码
UDP的封装:
UDP网络服务器模拟实现:主要分为makefile文件进行编译
UDP客户端
:udpClient.cc(客户端的调用),udpClient.hpp(客户端的实现)
UDP服务端
:udpServer.cc(服务端的调用),udpServer.hpp(服务端的实现)
makefile
创建makefile文件:
makefile里可以定义变量,如cc=g++
服务端udpServer
udpServer.cc
客户端进行调用的逻辑代码:构建udpServer的对象,然后进行初始化,在进行启动起来;调用逻辑如下:
#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;
}
udpServer.hpp
客户端的实现代码逻辑:对外提供了一个初始化接口以及启动接口。
作为一款服务器:要有自己的服务端口号uint16_t _port,同时网络服务器需要有对应的string _ip地址,文件描述符_sockfd:进行各种各样的数据通信,在类内进行读写操作
对于ip地址的类型:字符串型只在我们用户层作为参数传递,这个不用去管,调用接口转换即可
初始化
对于UDP服务器如何初始化:完成两步即可:1.创建套接字socket2.绑定端口号port和ip
1.创建套接字socket,如果要进行网络通信用套接字来进行创建
NAME
socket - create an endpoint for communication
SYNOPSIS
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
参数据数domain:域,未来套接字是进行网络通信还是本地通信,主要使用下面这两种 :
AF_UNIX, AF_LOCAL Local communication unix(7)
AF_INET IPv4 Internet protocols ip(7)
参数type:套接字提供服务的类型,如SOCK_STREAM:流式服务TCP策略,SOCK_DGRAM:数据报服务,UDP策略参数protocol:缺省为0,可由前两个类型确定
返回值:失败返回-1,成功返回文件描述符
2.绑定端口号和ip:bind
NAME
bind - bind a name to a socket
SYNOPSIS
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
参数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)];
};
__SOCKADDR_COMMON的定义:
typedef unsigned short int sa_family_t;
#define __SOCKADDR_COMMON(sa_prefix) \
sa_family_t sa_prefix##family
##
是将字符串合并,也就是将接收到的sin_与family合并起来,形成了sin_family
创建结构体后要先清空数据(初始化),可以用memset,系统也提供了接口:
#include <strings.h>
void bzero(void *s, size_t n);
inet_addr:1.把字符串转化成整数;2.再把整数转化成对应的网络序列
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
in_addr_t inet_addr(const char *cp);
//const char*cp:点分十进制风格的IP地址
代码实现:
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();
int n = bind(_sockfd,(struct sockaddr*)&local,sizeof(local));
if(n==-1)
{
cerr<<"bind error: "<<errno<<" : "<<strerror(errno)<<endl;
exit(BIND_ERR);
}
//UDP Server 的预备工作完成
}
启动
服务器的本质就是一个死循环,死循环不退出的就是常驻内存的进程。OS本质就是一个死循环
客户端发来的消息,服务端可能对这个消息做一点处理,然后发会给客户端;所以得先读取数据recvfrom
#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这个结构体得知
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);
//1.数据是什么?2.谁发的
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;
}
}
}
测试
本地环回:服务器代码的测试
./udpServer 127.0.0.1 8080
查看网络情况就可以用指令netstat
:
-a:显示所有连线中的Socket;
-e:显示网络其他相关信息;
-i:显示网络界面信息表单;
-l:显示监控中的服务器的Socket;
-n:直接使用ip地址(数字),而不通过域名服务器;
-p:显示正在使用Socket的程序识别码和程序名称;
-t:显示TCP传输协议的连线状况;
-u:显示UDP传输协议的连线状况;
现在如果绑定云服务器IP地址:
./udpServer 43.143.177.75 8080
云服务器是虚拟化的服务器,不能直接bind你的公网IP,可以绑定内网IP(ifconfig);如果是虚拟机或者独立真实的Linux环境,你可以bind你的IP;如何保证云服务器能够被别人访问:实际上,一款网络服务器不建议指明一个IP,也就是不要显示地绑定IP,服务器IP可能不止一个,如果只绑定一个明确的IP,最终的数据可能用别的IP来访问端口号,访问不出来,所以真实的服务器IP一般采用INADDR_ANY(全0,任意地址)代表任意地址bind
客户端udpClient
udpClient.cc
客户端如何去调用:./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;
}
udpClient.hpp
作为一个客户端:对外提供一个初始化接口,以及一个启动run接口
初始化
对于初始化接口:服务端有套接字,客户端也必须得有
- 客户端不需要显示的bind
在服务端bind的时候,最重要的不是绑定IP,而是绑定端口,客户端需要显示地绑定端口是为了服务器未来要明确的port,不能随意改变
而客户端需要端口,但是不重要,自己有端口号就可以,不需要显示地绑定:写服务器的是一家公司,写client的是无数家公司;由OS自动形成端口进行bind
代码实现
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;
//2.client必须要bind,client不要显示地bind,不需要程序员自己bind,由OS自动形成端口号进行bind
}
启动
对于run接口:需要发送数据,发送数据需要用到接口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:哪个套接字,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));
}
}
最后运行起来测试一下(这里是同一台主机之间测试,如果是不同的机器,我们传递参数的时候就要传递公网IP)
整体代码
//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;
}