前言
学习了TCP
通信相关的接口使用,实现了基本的通信,现在来基于TCP
通信实现翻译功能、远程SHELL
翻译功能
对于翻译功能,实现起来还是非常容易的,直接复用之前实现好的Dict
类;
在TcpServer
中新增一个成员变量_func
,表示未来信息的处理方法。
这样在server
接收到信息时,只需回调_func
即可。
cpp
//tcpserver.hpp
using func_t = std::function<std::string(std::string)>;
class TcpServer
{
void Server(int rwfd, InetAddr &addr)
{
while (true)
{
char english[256];
int rn = read(rwfd, english, sizeof(english) - 1);
if (rn < 0)
{
// read出错
LOG(Level::ERROR) << "read error";
break;
}
else if (rn == 0)
{
// write端退出
LOG(Level::INFO) << "writer is exit";
break;
}
// 读取成功
english[rn] = '\0';
std::string chinese = _func(english);
std::cout << addr.ToString() << " : " << english << " -> " << chinese << std::endl;
int wn = write(rwfd, chinese.c_str(), chinese.size());
if (wn < 0)
{
LOG(Level::ERROR) << "write error";
break;
}
}
}
public:
TcpServer(uint16_t port, func_t func) : _sockfd(-1), _port(port), _func(func)
{
}
~TcpServer() {}
void Init()
{
// 1. socket
_sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (_sockfd < 0)
{
LOG(Level::FATAL) << "socket error";
exit(1);
}
LOG(Level::DEBUG) << "socket success";
// 2. bind
InetAddr addr(_port);
int b = bind(_sockfd, addr.GetInetAddr(), addr.GetLen());
if (b < 0)
{
LOG(Level::FATAL) << "bind error";
exit(2);
}
LOG(Level::DEBUG) << "bind success";
int l = listen(_sockfd, 5);
if (l < 0)
{
LOG(Level::FATAL) << "listen error";
exit(3);
}
LOG(Level::DEBUG) << "listen success";
}
class ThreadData
{
public:
ThreadData(int fd, TcpServer *tsvr, InetAddr addr)
: _fd(fd), _tsvr(tsvr), _addr(addr)
{
}
int _fd;
TcpServer *_tsvr;
InetAddr _addr;
};
static void *Routinue(void *argv)
{
pthread_detach(pthread_self());
ThreadData *td = static_cast<ThreadData *>(argv);
td->_tsvr->Server(td->_fd, td->_addr);
return nullptr;
}
void Start()
{
while (true)
{
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
bzero(&peer, len);
int rwfd = accept(_sockfd, (struct sockaddr *)&peer, &len);
if (rwfd < 0)
{
LOG(Level::FATAL) << "accept error";
exit(4);
}
LOG(Level::DEBUG) << "accept success";
// 多线程
pthread_t tid;
ThreadData *td = new ThreadData(rwfd, this, peer);
pthread_create(&tid, nullptr, Routinue, td);
}
private:
int _sockfd;
uint16_t _port;
func_t _func;
};
这样在tcpserver
启动时,先加载字典,然后创建TcpServer
对象,启动服务端即可。
cpp
//tcpserver.cc
int main(int argc, char *argv[])
{
if (argc != 2)
{
std::cout << "usage : " << argv[0] << " port" << std::endl;
exit(1);
}
uint16_t port = std::stoi(argv[1]);
Dict d;
d.Load();
TcpServer tsvr(port, [&d](std::string english) -> std::string
{ return d.Translate(english); });
tsvr.Init();
tsvr.Start();
return 0;
}
远程shell
这里简单实现远程shell
,实现原理很简单:
简单设计一个InetShell
类,其中存储着可以远程执行的命令(防止恶意操作)
再由InetShell
通过一个方式去执行远端命令;
然后通过回调,在server
接受到信息后,回调InetShell
中的执行命令的方法;
最后,将执行的结果,通过返回值传给server
、server
再将返回值发送给远端。
1. 设计InetShell
首先,在InetShell
中要保存可以执行的命令(防止恶意操作),这里就直接使用unordered_set
来存储。
cpp
class InetShell
{
InetShell()
{
_whitelist.insert("ls");
_whitelist.insert("ll");
_whitelist.insert("pwd");
_whitelist.insert("who");
_whitelist.insert("whoami");
_whitelist.insert("touch test.txt");
_whitelist.insert("mkdir test");
}
private:
std::unordered_set<std::string> _whitelist;
};
2. 执行命令
在InetShell
中,要实现一个执行命令的方法Execute
;
在执行该命令之前,就要先判断当前命令是否安全(是否在_whitelist
中);
在确认安全后,就要执行命令:
这里执行命令,要命令行解析、进行程序替换等等;
这里就不做这些操作了,可以直接使用
popen
(popen
:将传递进来的字符当中命令行信息,进行命令行解析,执行命令;最后执行结果以文件的形式返回一个FILE*
类型的指针)
所以,这里在确认命令安全之后,就可以直接调用popen
,将命令行信息传递进去,以读方式打开(r
)。
然后就可以使用C
语言文件读取的方法将执行结果读取出来(fgets
);
将读取的内容拼接成字符串;读取完毕之后关闭文件即可。
cpp
std::string Execute(const std::string &com)
{
if (IsWhite(com))
return "unsafe";
FILE *fp = popen(com.c_str(), "r");
if (fp == NULL)
return std::string();
char buff[1024];
std::string result;
while (fgets(buff, sizeof(buff), fp))
{
result += buff;
}
pclose(fp);
return result;
}
这样,在
server
中通过回调,执行该方法,获取执行结果,然后再将结果发送给远端。

这里就通过简单的服务(翻译、远程shell
),来熟悉TCP
通信。
补充
这里Tcp
通信,所实现的TcpServer
和UDP
通信所实现的UdpServer
都是不希望被拷贝的;我们可以通过删除拷贝构造和拷贝赋值来保证不被拷贝;
但是这里就实现一个nocopy
类,该类删除了拷贝构造和拷贝赋值,TcpServer
继承nocopy
类,从而TcpServer
也就不能被拷贝了。
cpp
class nocopy
{
public:
nocopy() {}
~nocopy() {}
nocopy(const nocopy &) = delete;
const nocopy &operator=(const nocopy &) = delete;
};
此外,这里当程序出现问题,socket
失败、bind
失败调用exit
退出,退出码都使用的是数字;这里就可以设计一个枚举类型,将退出码一个个列举出来,方便查错。
cpp
enum ERR
{
OK = 0,
SOCKET_ERR,
BIND_ERR,
LISTEN_ERR,
ACCEPT_ERR,
CONNECT_ERR,
FORK_ERR
};
到这里,本篇文章内容就结束了,感谢支持
我的博客即将同步至腾讯云开发者社区,邀请大家一同入驻:https://cloud.tencent.com/developer/support-plan?invite_code=2oul0hvapjsws