深入了解linux网络—— TCP网络通信(下)
前言
学习了TCP
通信相关的接口使用,实现了基本的通信,现在来基于TCP
通信实现翻译功能、远程SHELL
翻译功能
对于翻译功能,实现起来还是非常容易的,直接复用之前实现好的Dict
类;
在TcpServer
中新增一个成员变量_func
,表示未来信息的处理方法。
这样在server
接收到信息时,只需回调_func
即可。
//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. bindInetAddr 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
对象,启动服务端即可。
//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
来存储。
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
);
将读取的内容拼接成字符串;读取完毕之后关闭文件即可。
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
也就不能被拷贝了。
class nocopy
{
public:nocopy() {}~nocopy() {}nocopy(const nocopy &) = delete;const nocopy &operator=(const nocopy &) = delete;
};
此外,这里当程序出现问题,socket
失败、bind
失败调用exit
退出,退出码都使用的是数字;这里就可以设计一个枚举类型,将退出码一个个列举出来,方便查错。
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