这里我们用UDP实现一个简单的英译汉小词典。我们还是仿照前一篇的UDP编程,将各自的组件封装起来,实现高内聚低耦合。
一. 字典翻译功能实现
首先我们将我们的字典知识库放在txt文本中。
apple: 苹果
banana: 香蕉
cat: 猫
dog: 狗
book: 书
pen: 笔
happy: 快乐的
sad: 悲伤的
run: 跑
jump: 跳
teacher: 老师
student: 学生
car: 汽车
bus: 公交车
love: 爱
hate: 恨
hello: 你好
goodbye: 再见
summer: 夏天
winter: 冬天
然后我们来实现翻译功能。为了体现高内聚低耦合
的思想,我们仍然封装成一个类。
const string defaultpath="./Dict.txt";
class Dict
{
public:
Dict(const string& path=defaultpath)
:_dict_conf_filepath(path)
{}
~Dict()
{}
private:
unordered_map<string,string> _dict;
string _dict_conf_filepath;
};
可以看到,类成员有两个,一个是Map类型的,对应我们先前txt文本中的一些汉英对照单词;另一个是string类型的,表明我们应该去哪里找汉英对照。
初始化时我们应该根据txt文本中的中英单词,填充_dict成员。
创建函数:
const string sep=": ";
bool Load()
{
ifstream in(_dict_conf_filepath);
if(!in.is_open())
{
LOG(FATAL,"open %s error\n",_dict_conf_filepath);
return false;
}
string line;
while(getline(in,line))
{
if(line.empty()) continue;
auto pos=line.find(sep);//[)
if(pos==string::npos) continue;
string word=line.substr(0,pos);
if(word.empty()) continue;
string han=line.substr(pos+sep.size());
if(han.empty()) continue;
LOG(DEBUG,"load info, %s: %s\n",word,han);
_dict.insert(make_pair(word,han));
}
in.close();
LOG(DEBUG,"load %s success\n",_dict_conf_filepath.c_str());
return true;
}
那么当我们上层调用函数寻找时,就可以根据_dict成员中找结果。
string Translate(const string& word,bool &ok)
{
ok=true;
auto iter=_dict.find(word);
if(iter==_dict.end())
{
ok=false;
return "未找到";
}
return iter->second;
}
最后我们加上命名空间,由此我们翻译功能实现代码整体如下:
#pragma once
#include<iostream>
#include<unordered_map>
#include<fstream>
#include<string>
#include"Log.hpp"
using namespace std;
namespace dict_ns
{
const string defaultpath="./Dict.txt";
const string sep=": ";
class Dict
{
private:
bool Load()
{
ifstream in(_dict_conf_filepath);
if(!in.is_open())
{
LOG(FATAL,"open %s error\n",_dict_conf_filepath);
return false;
}
string line;
while(getline(in,line))
{
if(line.empty()) continue;
auto pos=line.find(sep);//[)
if(pos==string::npos) continue;
string word=line.substr(0,pos);
if(word.empty()) continue;
string han=line.substr(pos+sep.size());
if(han.empty()) continue;
LOG(DEBUG,"load info, %s: %s\n",word,han);
_dict.insert(make_pair(word,han));
}
in.close();
LOG(DEBUG,"load %s success\n",_dict_conf_filepath.c_str());
return true;
}
public:
Dict(const string& path=defaultpath)
:_dict_conf_filepath(path)
{
Load();
}
string Translate(const string& word,bool &ok)
{
ok=true;
auto iter=_dict.find(word);
if(iter==_dict.end())
{
ok=false;
return "未找到";
}
return iter->second;
}
~Dict()
{}
private:
unordered_map<string,string> _dict;
string _dict_conf_filepath;
};
}
二. 服务端代码实现
我们将服务端封装成一个类,并封装对应步骤在类函数中。
const static int defaultfd = -1;
using func_t=function<string(const string&,bool& ok)>;
class UdpServer
{
public:
UdpServer(uint16_t port,func_t func)
: _sockfd(defaultfd), _port(port), _func(func)
,_isrunning(false)
{}
~UdpServer()
{}
private:
int _sockfd;
uint16_t _port; // 服务器所用的端口号
bool _isrunning;
//给服务器设定回调,用来让上层进行注册业务的处理方法
func_t _func;
};
此处有一个自定义类型
func_t
的变量,我们观察其参数结构,可以发现其实就是我们上面实现的翻译功能类中的Translate
函数。我们通过这样的方式,实现高内聚低耦合,让上层实现翻译功能。
此处服务端同样不需要IP地址,与前面原因相同(不知道的同鞋可以看links)。
服务端初始成员函数:
void InitServer()
{
// 1.创建udp socket 套接字...必须要做的
_sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (_sockfd < 0)
{
LOG(FATAL, "socket error,%s,%d\n", strerror(errno), errno);
exit(SOCKET_ERROR);
}
LOG(INFO, "socket create success,sockfd: %d\n", _sockfd);
// 2.1 填充sockaddr_in结构
struct sockaddr_in local; // struct sockaddr_in 系统提供的数据类型,local是变量,用户栈上开辟空间
bzero(&local, sizeof(local)); // 清空
local.sin_family = AF_INET;
local.sin_port = htons(_port); // port要经过网络传输给对面,即port先到网络,所以要将_port,从主机序列转化为网络序列
local.sin_addr.s_addr=INADDR_ANY;//htonl(INADDR_ANY)
// 2.2 bind sockfd和网络信息(IP(?)+Port)
int n = bind(_sockfd,(struct sockaddr*)&local,sizeof(local));
if(n<0)
{
LOG(FATAL, "bind error,%s,%d\n", strerror(errno), errno);
exit(BIND_ERROR);
}
LOG(INFO, "socket bind success\n");
}
此处还是跟前面UDP编程一样。
服务端启动成员函数:
void Start()//所有的服务器,本质解决的是输入输出的问题!不想让网络通信模块和业务模块进行强耦合
{
//一直运行,直到管理者不想运行了,服务器都是死循环
_isrunning=true;
while(true)
{
char request[1024];
struct sockaddr_in peer;
socklen_t len=sizeof(peer);
//1.我们要让server先收数据
ssize_t n=recvfrom(_sockfd,request,sizeof(request)-1,0,(struct sockaddr*)&peer,&len);
if(n>0)
{
request[n]=0;
InetAddr addr(peer);
LOG(DEBUG,"get message from [%s:%d]: %s\n",addr.Ip().c_str(),addr.Port(),request);
bool ok;
string response=_func(request,ok);//将请求回调出去,在外部进行处理
(void)ok;
//2.我们要将server收到的数据,发回给对方
sendto(_sockfd,response.c_str(),response.size(),0,(struct sockaddr*)&peer,len);
}
}
_isrunning=false;
}
此处我们大致思路还是先通过
recvfrom
函数收到来自客户端的数据,然后再将翻译的结果返回给客户端。在这中间,就是我们的翻译函数,在服务端类中即我们的_func
成员。
那么服务端代码合起来就是:
#pragma once
#include <iostream>
#include <string>
#include <cerrno>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <strings.h>
#include <stdlib.h>
#include<functional>
#include "Log.hpp"
#include"InetAddr.hpp"
#include"Dict.hpp"
using namespace std;
enum
{
SOCKET_ERROR = 1,
BIND_ERROR,
USAGE_ERROR
};
const static int defaultfd = -1;
using func_t=function<string(const string&,bool& ok)>;
class UdpServer
{
public:
UdpServer(uint16_t port,func_t func)
: _sockfd(defaultfd), _port(port), _func(func)
,_isrunning(false)
{}
void InitServer()
{
// 1.创建udp socket 套接字...必须要做的
_sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (_sockfd < 0)
{
LOG(FATAL, "socket error,%s,%d\n", strerror(errno), errno);
exit(SOCKET_ERROR);
}
LOG(INFO, "socket create success,sockfd: %d\n", _sockfd);
// 2.1 填充sockaddr_in结构
struct sockaddr_in local; // struct sockaddr_in 系统提供的数据类型,local是变量,用户栈上开辟空间
bzero(&local, sizeof(local)); // 清空
local.sin_family = AF_INET;
local.sin_port = htons(_port); // port要经过网络传输给对面,即port先到网络,所以要将_port,从主机序列转化为网络序列
local.sin_addr.s_addr=INADDR_ANY;//htonl(INADDR_ANY)
// 2.2 bind sockfd和网络信息(IP(?)+Port)
int n = bind(_sockfd,(struct sockaddr*)&local,sizeof(local));
if(n<0)
{
LOG(FATAL, "bind error,%s,%d\n", strerror(errno), errno);
exit(BIND_ERROR);
}
LOG(INFO, "socket bind success\n");
}
void Start()//所有的服务器,本质解决的是输入输出的问题!不想让网络通信模块和业务模块进行强耦合
{
//一直运行,直到管理者不想运行了,服务器都是死循环
_isrunning=true;
while(true)
{
char request[1024];
struct sockaddr_in peer;
socklen_t len=sizeof(peer);
//1.我们要让server先收数据
ssize_t n=recvfrom(_sockfd,request,sizeof(request)-1,0,(struct sockaddr*)&peer,&len);
if(n>0)
{
request[n]=0;
InetAddr addr(peer);
LOG(DEBUG,"get message from [%s:%d]: %s\n",addr.Ip().c_str(),addr.Port(),request);
bool ok;
string response=_func(request,ok);//将请求回调出去,在外部进行处理
(void)ok;
//2.我们要将server收到的数据,发回给对方
sendto(_sockfd,response.c_str(),response.size(),0,(struct sockaddr*)&peer,len);
}
}
_isrunning=false;
}
~UdpServer()
{
}
private:
int _sockfd;
uint16_t _port; // 服务器所用的端口号
bool _isrunning;
//给服务器设定回调,用来让上层进行注册业务的处理方法
func_t _func;
};
三. 服务端调用实现
此处还是跟UDP编程一样,因为我们实际只在服务端代码内部作了改动,在外层看起来调用都是没变的。
#include<iostream>
#include<memory>
#include"UdpServer.hpp"
#include"Log.hpp"
#include"Dict.hpp"
using namespace std;
using namespace dict_ns;
void Usage(string proc)
{
cout<<"Usage:\n\t"<<proc<<" local_port\n"<<endl;
}
// ./udpserver ip
int main(int argc,char *argv[])
{
if(argc!=2)
{
Usage(argv[0]);
exit(USAGE_ERROR);
}
EnableScreen();
//string ip=argv[1];
//定义翻译模块
Dict dict;
//网络模块
uint16_t port=stoi(argv[1]);
unique_ptr<UdpServer> usvr=make_unique<UdpServer>(port,\
bind(&Dict::Translate,&dict,placeholders::_1,placeholders::_2));//C++14
usvr->InitServer();
usvr->Start();
return 0;
}
四. 客户端代码实现
此处也是没有变化的,所以我们可以体会到我们这种将不同功能的代码分别封装起来思想的好处。可以看到此处跟UDP编程其实变化不大。
#include<iostream>
#include<string>
#include<cstdio>
#include<cstring>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
using namespace std;
void Usage(string proc)
{
cout<<"Usage:\n\t"<<proc<<" serverip serverport\n"<<endl;
}
// ./udpclient serverip serverport
int main(int argc,char *argv[])
{
if(argc!=3)
{
Usage(argv[0]);
exit(1);
}
string serverip=argv[1];
uint16_t serverport=stoi(argv[2]);
//1.创建socket
int sockfd=socket(AF_INET,SOCK_DGRAM,0);
if(sockfd<0)
{
cerr<<"socket error"<<endl;
}
//2.client一定要bind,client也有自己的ip和port,但是不建议显示(和server一样用bind函数)bind
//a.那如何bind呢?当udp client首次发送数据的时候,os会自动随机的给client进行bind--为什么?要bind,必然要和port关联!防止client port冲突
//b.什么时候bind?首次发送数据的时候
//构建目标主机的socket信息
struct sockaddr_in server;
memset(&server,0,sizeof(server));
server.sin_family=AF_INET;
server.sin_port=htons(serverport);
server.sin_addr.s_addr=inet_addr(serverip.c_str());
string message;
//3.直接通信即可
while(true)
{
cout<<"Please Enter# ";
getline(cin,message);
sendto(sockfd,message.c_str(),message.size(),0,(struct sockaddr*)&server,sizeof(server));
struct sockaddr_in peer;
socklen_t len=sizeof(peer);
char buffer[1024];
ssize_t n=recvfrom(sockfd,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&peer,&len);
if(n>0)
{
buffer[n]=0;
cout<<"server echo# "<<buffer<<endl;
}
}
return 0;
}
五. 效果展示
此处打印出来的英汉对照有点格式问题,所以没有显示出来,但是我们可以发现翻译出来是没有问题的。
总结:
好了,到这里今天的知识就讲完了,大家有错误一点要在评论指出,我怕我一人搁这瞎bb,没人告诉我错误就寄了。
祝大家越来越好,不用关注我(疯狂暗示)