🔥个人主页: Milestone-里程碑
❄️个人专栏: <<力扣hot100>> <<C++>><<Linux>>
🌟心向往之行必能至
目录
[一 : Command.hpp :封装命令符的使用](#一 : Command.hpp :封装命令符的使用)
[1.1 相关接口](#1.1 相关接口)
[四.TcpServer.cc 两种方式进行传参](#四.TcpServer.cc 两种方式进行传参)
上一篇文章中,我们实现了TCP增加英文查找中文的功能,本篇文章,让我们实现多人聊天时,可以实现Linux命令符的使用
一 : Command.hpp :封装命令符的使用
实现命令符的使用,我们前面写过的自定义shell里也实现了,可以直接CV过来,但此处我们引入新的接口进行实现
先将允许的命令符加入白名单,再进行判断执行还是退出
bash
#pragma once
#include<set>
#include"InetAddr.hpp"
#include "Log.hpp"
#include"Common.hpp"
using namespace LogModule;
class Command{
public:
Command(){
_WhiteList.insert("ls");
_WhiteList.insert("who");
_WhiteList.insert("pwd");
_WhiteList.insert("ll");
_WhiteList.insert("whoami");
_WhiteList.insert("ls -l");
}
bool IsWLC(const std::string &com)
{
auto iter =_WhiteList.find(com);
return iter==_WhiteList.end();
}
std::string Excute(const std::string&cmd,InetAddr&addr)
{
if(IsWLC(cmd))
{
return std::string("坏人");
// exit(com_err);
}
FILE*fp=popen(cmd.c_str(),"r");
if(fp==nullptr)
{
return std::string("你要执行的命令没有: ")+cmd;
}
std::string who=addr.StringAddr();
std::string res;
char line[1024];
while(fgets(line,sizeof(line),fp))
{
res+=line;
}
pclose(fp);
std::string result = who + "execute done, result is: \n" + res;
LOG(LogLevel::DEBUG) << result;
return result;
}
~Command(){}
private:
std::set<std::string> _WhiteList;
};
1.1 相关接口
popen:"创建管道并执行 shell 命令
bash
POPEN(3) Linux Programmer's Manual POPEN(3)
NAME
popen, pclose - pipe stream to or from a process
SYNOPSIS
#include <stdio.h>
FILE *popen(const char *command, const char *type);
int pclose(FILE *stream);
Feature Test Macro Requirements for glibc (see feature_test_macros(7)):
popen(), pclose():
_POSIX_C_SOURCE >= 2
|| /* Glibc versions <= 2.19: */ _BSD_SOURCE || _SVID_SOURCE
bash
RETURN VALUE
popen(): on success, returns a pointer to an open stream that can be used to read or write to the pipe; if the fork(2) or pipe(2) calls fail, or if the function cannot
allocate memory, NULL is returned.
pclose(): on success, returns the exit status of the command; if wait4(2) returns an error, or some other error is detected, -1 is returned.
Both functions set errno to an appropriate value in the case of an error.
pclose :关闭由popen创建的管道流
bash
POPEN(3) Linux Programmer's Manual POPEN(3)
NAME
popen, pclose - pipe stream to or from a process
SYNOPSIS
#include <stdio.h>
FILE *popen(const char *command, const char *type);
int pclose(FILE *stream);
Feature Test Macro Requirements for glibc (see feature_test_macros(7)):
popen(), pclose():
_POSIX_C_SOURCE >= 2
|| /* Glibc versions <= 2.19: */ _BSD_SOURCE || _SVID_SOURCE
bash
RETURN VALUE
popen(): on success, returns a pointer to an open stream that can be used to read or write to the pipe; if the fork(2) or pipe(2) calls fail, or if the function cannot
allocate memory, NULL is returned.
pclose(): on success, returns the exit status of the command; if wait4(2) returns an error, or some other error is detected, -1 is returned.
Both functions set errno to an appropriate value in the case of an error.
下面文件的代码基本与前面写的一样,直接CV,小幅改动即可
二.TcpServer.hpp
bash
#pragma once
#include <sys/types.h>
#include <unistd.h>
#include <wait.h>
#include <signal.h>
#include <pthread.h>
#include<functional>
#include "InetAddr.hpp"
#include "Common.hpp"
#include "Log.hpp"
#include"ThreadPool.hpp"
using namespace LogModule;
using namespace ThreadPoolModule;
const int defaultsocket = -1;
using task_t=std::function<void()>;
using func_t=std::function<std::string(const std::string &word,InetAddr &addr)>;
class TcpServer : NoCopy // 服务器不可拷贝
{
public:
TcpServer(uint16_t &port,func_t func)
: _listensocket(defaultsocket), _port(port), _isrunning(false),_func(func)
{
}
void Init()
{
// signal(SIGCHLD, SIG_IGN); // 忽略SIG_IGN信号
// 1.创建套接字
_listensocket = socket(AF_INET, SOCK_STREAM, 0);
if (_listensocket < 0)
{
LOG(LogLevel::ERROR) << "socket fail";
exit(socket_err);
}
LOG(LogLevel::INFO) << "socket success" << _listensocket;
// 2. bind
InetAddr local(_port);
int n = bind(_listensocket, local.NetAddrPtr(), local.NetAddrLen());
if (n < 0)
{
LOG(LogLevel::ERROR) << "bind fail" << _listensocket;
exit(bind_err);
}
LOG(LogLevel::INFO) << "bind success" << _listensocket;
// 进行监听
int backlog = 5;
n = listen(_listensocket, backlog);
if (n < 0)
{
LOG(LogLevel::ERROR) << "listen fail" << _listensocket;
exit(listen_err);
}
LOG(LogLevel::INFO) << "listen success" << _listensocket;
}
class ThreadData
{
public:
ThreadData(int fd, InetAddr &ar, TcpServer *s) : sockfd(fd), addr(ar), tsvr(s) {}
public:
int sockfd;
InetAddr addr;
TcpServer *tsvr;
};
static void *Rotinue(void *argv)
{
ThreadData *td = static_cast<ThreadData *>(argv);
// 脱离主线程
pthread_detach(pthread_self());
td->tsvr->Service(td->sockfd, td->addr);
delete td;
return nullptr;
}
// 短服务
// 长服务: 多进程多线程比较合适
void Service(int sockfd, InetAddr &peer)
{
// 1. 先读取数据
// a. n>0: 读取成功
// b. n<0: 读取失败
// c. n==0: 对端把链接关闭了,读到了文件的结尾 --- pipe
char buf[1024];
while (true)
{
ssize_t n = read(sockfd, buf, sizeof(buf) - 1);
if (n < 0)
{
LOG(LogLevel::ERROR) << "read fail";
close(sockfd);
exit(read_err);
}
else if (n == 0)
{
LOG(LogLevel::DEBUG) << peer.StringAddr() << "exit ";
close(sockfd);
break;
}
else
{
//1.读到单词
//2. 读到命令
buf[n] = 0;
std::string echo_string = _func(std::string(buf),peer);
LOG(LogLevel::INFO) << peer.StringAddr() << " #" << buf<<"->"<<echo_string;
write(sockfd, echo_string.c_str(), echo_string.size());
}
}
}
void Start()
{
_isrunning = true;
while (_isrunning)
{
// a. 获取链接
struct sockaddr_in peer;
socklen_t len = sizeof(sockaddr_in);
// 如果没有连接,accept就会阻塞
int sockfd = accept(_listensocket, CONV(peer), &len);
if (sockfd < 0)
{
LOG(LogLevel::WARNING) << "accept error";
continue;
}
InetAddr addr(peer);
LOG(LogLevel::INFO) << "accept success, peer addr : " << addr.StringAddr();
// version0 -- text version 单线程版本
// Service(sockfd, addr);
// version1 -- 多线程版本
// pid_t id = fork();
// if (id < 0)
// {
// LOG(LogLevel::ERROR) << "fork fail";
// exit(fork_err);
// }
// // 父与子都关闭不需要的端口
// else if (id == 0)
// {
// close(_listensocket);
// if(fork()>0)
// exit(OK);
// //孙子进程
// Service(sockfd, addr);
// exit(OK);
// }
// else
// {
// close(sockfd);
// pid_t rid = waitpid(id, nullptr, 0);
// (void)rid;
// //父进程要等待子进程,要不然僵尸,但等待与之前的一样,等于没处理
// //解决方法:
// //1. 设置为非阻塞状态,不推荐
// //2. 自定义信号(不够优雅)
// //3. 利用子进程创建一个孙进程,让子进程退出,孙进程就成了孤儿进程,由系统领养
// }
// version 2 ---多线程版本
ThreadData *td = new ThreadData(sockfd, addr, this);
pthread_t tid;
pthread_create(&tid, nullptr, Rotinue, td);
//version 3 --线程池版本,一般适合处理短服务
// // 将新链接和客户端构建一个新任务,push线程池中
// ThreadPool<task_t>::GetInstance()->Enqueue([this,sockfd,&addr]
// (){
// this->Service(sockfd,addr);
// });
}
_isrunning = false;
}
~TcpServer() {}
private:
int _listensocket; // 监听socket
uint16_t _port;
bool _isrunning;
func_t _func;
};
三.TcpClient.cc
bash
#include <iostream>
#include "Common.hpp"
#include "Log.hpp"
#include "InetAddr.hpp"
using namespace LogModule;
int main(int args, char *argv[])
{
if (args != 3)
{
LOG(LogLevel::ERROR) << "USAG" << argv[0] << " ip port";
exit(usgv_err);
}
std::string ip = argv[1];
uint16_t port = std::stoi(argv[2]);
// 1.创建套接字
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0)
{
LOG(LogLevel::ERROR) << "sockfd error";
exit(socket_err);
}
LOG(LogLevel::DEBUG) << "sockfd success";
// 无需显示绑定,系统随随机绑定端口号
// 2. 我应该做什么呢?listen?accept?都不需要!!
// 2. 直接向目标服务器发起建立连接的请求
InetAddr serveraddr(ip, port);
int n = connect(sockfd, serveraddr.NetAddrPtr(), serveraddr.NetAddrLen());
if (n < 0)
{
LOG(LogLevel::ERROR) << "connect fail";
exit(connect_err);
}
LOG(LogLevel::DEBUG) << "connect success";
while (true)
{
// 1.写P
LOG(LogLevel::DEBUG) << "Please input#";
std::string line;
std::getline(std::cin, line);
ssize_t n = write(sockfd, line.c_str(), line.size());
// 2.读
char buf[1024];
n = read(sockfd, buf, sizeof(buf) - 1);
if (n > 0)
{
buf[n] = 0;
LOG(LogLevel::INFO) << "server echo#" << buf;
}
}
return 0;
}
四.TcpServer.cc 两种方式进行传参
bash
#include"TcpServer.hpp"
#include"Commad.hpp"
#include<iostream>
int main(int args,char*argv[])
{
if(args!=2)
{
std::cout<<"USGR: "<<argv[0]<<"port";
exit(usgv_err);
}
uint16_t port=std::stoi(argv[1]);
Command cmd;
//两种绑定 1. lambda 2.bind
// std::unique_ptr<TcpServer> tcp=std::make_unique<TcpServer>(port,[&cmd]( const std::string&word,InetAddr &addr){return cmd.Excute(word,addr);});
// 2.
func_t f=std::bind(&Command::Excute,&cmd,std::placeholders::_1,std::placeholders::_2);
std::unique_ptr<TcpServer> tcp=std::make_unique<TcpServer>(port,f);
tcp->Init();
tcp->Start();
return 0;
}
五.总结
- 有了前面代码的基础,此处最大的变动就是封装一个Commad的类
- 在学习中,如果有新的知识,应该去学习,而非CV之前的
- 需要熟练使用bind和lambda