本文主要是介绍Linux第三十九章,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
🐶博主主页:@ᰔᩚ. 一怀明月ꦿ
❤️🔥专栏系列:线性代数,C初学者入门训练,题解C,C的使用文章,「初学」C++,linux
🔥座右铭:“不要等到什么都没有了,才下定决心去做”
🚀🚀🚀大家觉不错的话,就恳求大家点点关注,点点小爱心,指点指点🚀🚀🚀
目录
popen
sz和rz
简单的聊天室
Comm.hpp
InetAddr.hpp
Lockguard.hpp
Log.hpp
nocopy.hpp
thread.hpp
ThreadPool.hpp
Udpserver.hpp
main.cc
UdpClient.cc
Makefile
运行结果
popen
在Linux中,popen是一个用于执行shell命令并建立一个管道连接的函数。它允许你在程序中执行一个shell命令,并通过标准输入或标准输出与命令进行交互。
popen函数的原型如下:FILE *popen(const char *command, const char *mode); 其中,command参数是一个字符串,表示要执行的shell命令。mode参数是一个字符串, 指定管道连接的模式,可以是"r"(读模式)或"w"(写模式)。 popen函数会返回一个FILE类型的指针,可以像操作普通文件一样使用它来读取或写入数据。
以下是一个示例,展示如何使用popen函数执行一个shell命令并读取其输出:
#include <stdio.h> int main() {FILE *fp;char output[1024];// 执行shell命令并读取输出fp = popen("ls -l", "r");if (fp == NULL) {printf("Failed to run command\n");return 1;}// 从管道中读取输出while (fgets(output, sizeof(output), fp) != NULL) {printf("%s", output);}// 关闭管道连接pclose(fp);return 0; } 在上述示例中,我们使用popen函数执行了一个ls -l命令,并将其输出读取到缓冲区中,然后逐行打印出来。
需要注意的是,在使用popen时,要小心处理命令参数,以避免潜在的安全风险,例如通过用户输入直接构造命令参数可能导致命令注入漏洞。
sz和rz
sz 和 rz 是两个用于在 Linux 系统上进行文件传输的命令行工具。
* sz:用于在从远程主机传输文件到本地时使用。它的作用是将文件从远程主机发送到本地主机。通常情况下,它与 rz 配合使用,以实现从本地上传文件到远程主机的功能。
* rz:用于在从本地主机传输文件到远程主机时使用。它的作用是在本地选择文件,然后将其发送到远程主机。通常情况下,它与 sz 配合使用,以实现从本地上传文件到远程主机的功能。
这两个命令通常用于通过 SSH 连接到远程主机,并在命令行界面上传或下载文件。要使用这些命令,你需要在本地和远程主机上都安装了 lrzsz 软件包。在大多数 Linux 发行版中,这个软件包是默认安装的,但如果没有安装,你可以使用包管理器来安装它。
下载lrzsz 软件包
在 Ubuntu 和 Debian 等基于 Debian 的发行版中 sudo apt-get update sudo apt-get install lrzsz
简单的聊天室
Comm.hpp
用于定义一些错误码
#pragma once enum {Usage_Err=1,Socket_Err,Bind_Err };
InetAddr.hpp
用于将网络字节序的ip地址转为主机字节序、用于将网络字节序的端口号转为主机字节序
#pragma once#include <iostream> #include <string> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include <netinet/in.h>using namespace std;//用于将网络字节序的ip地址转为主机字节序 //用于将网络字节序的端口号转为主机字节序 class InetAddr { public:InetAddr(struct sockaddr_in &addr):_addr(addr){_port = ntohs(_addr.sin_port); // 想看看客户端的端口号,ntohs(peer.sin_port),因为我们是从网络拿的数据,我需要将网络字节序转为主机序列_ip = inet_ntoa(_addr.sin_addr); // 想看看客户端的ip,将网络字节序的ip地址转为主机字节序}string Ip(){return _ip;}uint16_t Port(){return _port;}string PrintDebug(){string clientinfo = _ip + ":" + to_string(_port);return clientinfo;}const struct sockaddr_in& GetAddr(){return _addr;}bool operator==(InetAddr& addr){return this->_ip==addr.Ip()&&this->_port==addr.Port();}~InetAddr(){}private:string _ip;uint16_t _port;struct sockaddr_in _addr; };
Lockguard.hpp
用于创建锁
#pragma once #include <pthread.h>class Mutex { public:Mutex(pthread_mutex_t* lock):_lock(lock){}void Lock(){pthread_mutex_lock(_lock);}void Unlock(){pthread_mutex_unlock(_lock);} private:pthread_mutex_t *_lock; };class Lockguard {public:Lockguard(pthread_mutex_t* lock):_mutex(lock){_mutex.Lock();}~Lockguard(){_mutex.Unlock();}private:Mutex _mutex; };
Log.hpp
用于记录日志,可以选择将日志输出到显示器、一个文件、根据日志等级进行分类文件
#include <iostream> #include <stdarg.h> #include <string> #include <ctime> #include <unistd.h> #include <fstream> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> using namespace std;// 定义一个枚举,表示日志级别 enum {Debug = 0,Info,Warning,Error,Fatal };// 定义一个枚举,表示输出方式 enum {Screen = 10,OneFile,ClassFile };// 定义一个函数,将日志级别转换为字符串 string Leveltostring(int level) {switch (level){case Debug:return "Debug";case Info:return "Info";case Warning:return "Warning";case Error:return "Error";case Fatal:return "Fatal";default:return "Unknown";} }const int default_style = Screen; // 默认想显示器打印 const string default_filename = "log."; // 默认的文件名是log. const string logdir="log"; // 定义一个日志类 class Log { public:Log() : style(default_style), filename(default_filename){mkdir(logdir.c_str(), 0777);// 创建一个目录}// 定义一个函数,将时间戳转换为字符串string Timelocaltime(){time_t curtime = time(nullptr);struct tm *curr = localtime(&curtime);char time_buffer[128];snprintf(time_buffer, sizeof(time_buffer), "%d年-%d月-%d日 %d时:%d分:%d秒",curr->tm_year + 1900, curr->tm_mon + 1, curr->tm_mday, curr->tm_hour, curr->tm_min, curr->tm_sec);return time_buffer;}// 定义一个函数,设置一个style,向哪里输出,默认是向屏幕输出void SetStyle(int style) // 设置一个style,向哪里输出,默认是向屏幕输出{this->style = style;}// 定义一个函数,设置一个文件名void SetFilename(const string &filename){this->filename = filename;}// 定义一个函数,将日志写入文件void WriteLogToOneFile(const string &logname, const string &message){int fd = open(logname.c_str(), O_CREAT | O_WRONLY | O_APPEND, 0666);if (fd < 0){exit(-1);}write(fd, message.c_str(), message.size());close(fd);}// 定义一个函数,将日志写入文件void WriteLogToClassFile(const string &levelstr, const string &message) // 将日志写入文件{string logname = logdir;logname += "/";logname += filename;logname += levelstr;WriteLogToOneFile(logname, message);}//void WriteLog(const string &levelstr, const string &message){switch (style){case Screen:cout << message;break;case OneFile:WriteLogToClassFile("all", message);break;case ClassFile:WriteLogToClassFile(levelstr, message);break;default:break;}}void LogMessage(int level, const char *format, ...){char rightbuffer[1024]; // 日志的内容va_list args; // va_list其实就是char* 类型的va_start(args, format); // 获取可变参数的位置,由args去指向可变参数部分vsnprintf(rightbuffer, sizeof(rightbuffer), format, args);va_end(args); // 相等于args=nullptrchar leftbuffer[1024];string levelstr = Leveltostring(level);string curtime = Timelocaltime();string idstr = to_string(getpid());snprintf(leftbuffer, sizeof(leftbuffer), "[%s][%s][%s]", levelstr.c_str(), curtime.c_str(), idstr.c_str());string loginfo = leftbuffer;loginfo.append(rightbuffer);WriteLog(levelstr, loginfo);}~Log(){}private:int style;string filename; };
nocopy.hpp
主要是用来设计一个不能继承的类
#pragma once#include<iostream>class nocopy {public:nocopy(){}nocopy(const nocopy&)=delete;const nocopy& operator=(const nocopy&)=delete;~nocopy(){} };
thread.hpp
用于创建线程
#pragma once #include<iostream> #include<string> #include<functional> #include<pthread.h>using namespace std;//typedef function<void()> func_t template<class T> using func_t=function<void(T&)>;template<class T> class Thread { public:Thread(const string& threadname,func_t<T> func,T& data):_tid(0),_threadname(threadname),_isrunning(false),_func(func),_data(data){}static void* Threadroutine(void* args)//类内成员方法,其第一个参数是this指针,所以会导致编译错误//这里使用static,让Thraedroutine成为类的方法,{(void)args;//仅仅是为了消除警告,变量未使用Thread* ts=static_cast<Thread*>(args);ts->_func(ts->_data);return nullptr;}bool Start(){int n=pthread_create(&_tid,nullptr,Threadroutine,this);//把当前对象传递给线程执行的方法if(n==0){_isrunning=true;return true;}else return false;}bool Join(){if(!_isrunning)return true;int n=pthread_join(_tid,nullptr);if(n==0){_isrunning=false;return true;}return false;}bool Isrunning(){return _isrunning;}string Threadname(){return _threadname;} private:pthread_t _tid;string _threadname;bool _isrunning;func_t<T> _func;T _data; };
ThreadPool.hpp
用于创建线程池
#pragma once#include <iostream> #include <queue> #include "Log.hpp" #include "thread.hpp" #include "Lockguard.hpp" #include <functional> #include<unistd.h> using namespace std; using namespace std::placeholders; static int defaultnum=5;Log lg;//全局的日志对象,用于记录线程池的日志信息//给线程执行的方法传递的参数 class ThreadData {public:ThreadData(const string& threadname):_threadname(threadname){}string _threadname; };template <class T> class ThreadPool { public:static ThreadPool<T>* Getinstance(){{Lockguard lockguard(&_mutex_q);if(instance==nullptr){lg.LogMessage(Info,"单例创建成功...\n");instance=new ThreadPool<T>();}}return instance;} private:ThreadPool(int thread_num=defaultnum): _thread_num(thread_num){pthread_mutex_init(&_mutex,nullptr);pthread_cond_init(&_cond,nullptr);//构建线程for(int i=0;i<_thread_num;++i){string threadname="thread -";threadname+=to_string(i+1);ThreadData td(threadname);//这里使用了bind绑定成员函数Thread<ThreadData> t(threadname, bind(&ThreadPool<T>::ThreadRun,this,_1),td);lg.LogMessage(Info,"%s is created ...\n",threadname.c_str());_threads.push_back(t);}} public://启动线程池,让线程执行自己的方法thread_routinebool Start(){//启动for(auto& thread:_threads){thread.Start();lg.LogMessage(Info,"%s is running...\n",thread.Threadname().c_str());}return true;}//封装了pthread_cond_waitvoid ThreadWait(const ThreadData &td){lg.LogMessage(Debug,"no task,%s is sleeping...\n",td._threadname.c_str());pthread_cond_wait(&_cond,&_mutex);}//封装了pthread_cond_signalvoid ThreadWakeup(){lg.LogMessage(Debug,"have task\n");pthread_cond_signal(&_cond);}//线程执行的任务void ThreadRun(ThreadData& td){while(true){T t;{//这个花括号,为了设置lockguard的生命周期的,这样才可以调用析构函数进行解锁Lockguard lockguard(&_mutex);//方法1)pthread_mutex_lock(&_mutex);//加锁while(_q.empty())//如果任务队列是空的,就不用去拿任务了{ThreadWait(td);//pthread_cond_wait(&_cond,&mutex);//让线程阻塞,因为没有任务,然后解锁,让其他线程申请到锁,如果队列还是为空的话,仍然会阻塞....}t=_q.front();//取任务_q.pop();//1)pthread_mutex_unlock(&_mutex);}//执行任务t();// lg.LogMessage(Debug,"%s handler %s done , result is :%s\n",\// td._threadname.c_str(),t.Printtask().c_str(),t.Printresult().c_str());//cout<<"handler done"<<t.Printresult()<<endl;}}//线程池中插入任务void Push( T& in){Lockguard lockguard(&_mutex);_q.push(in);ThreadWakeup();//每次插入任务后,唤醒一个线程}~ThreadPool(){pthread_mutex_destroy(&_mutex);pthread_cond_destroy(&_cond);}//for debug//主线程等待线程void Wait(){for(auto& thread: _threads){thread.Join();}} private:queue<T> _q;//队列,用于存储线程池中线程要执行的任务vector<Thread<ThreadData>> _threads;//线程池其实是一个顺序表类型,里面存储的多线程int _thread_num;//创建线程的数量pthread_mutex_t _mutex; // 锁pthread_cond_t _cond; // 条件变量//懒汉单例static pthread_mutex_t _mutex_q;//单例锁static ThreadPool* instance;};template<class T> ThreadPool<T>* ThreadPool<T>::instance=nullptr; template<class T> pthread_mutex_t ThreadPool<T>::_mutex_q=PTHREAD_MUTEX_INITIALIZER;
Udpserver.hpp
服务端
#pragma once #include "nocopy.hpp" // #include "Log.hpp" #include <iostream> #include <string> #include <sys/types.h> #include <sys/socket.h> #include <cerrno> #include <cstring> #include <unistd.h> #include "Comm.hpp" #include <netinet/in.h> #include <strings.h> #include <arpa/inet.h> #include <functional> #include "ThreadPool.hpp" #include <vector> #include <pthread.h>#include "InetAddr.hpp" using namespace std;static const string defaultip = "0.0.0.0"; static const uint16_t defaultport = 8888; static const int defaultfd = -1; static const int defaultsize = 1024;using task_t = function<void()>;class UdpServer : public nocopy { public:UdpServer(uint16_t port = defaultport, string ip = defaultip): _ip(ip), _port(port), _sockfd(defaultfd){pthread_mutex_init(&_user_mutex, nullptr);}void Init(){// 创建套接字_sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (_sockfd < 0){lg.LogMessage(Fatal, "socket error,%d :%s\n", errno, strerror(errno));exit(Socket_Err);}lg.LogMessage(Info, "socket success ,sockfd:%d \n", _sockfd);// 2.绑定,指定网络信息struct sockaddr_in local; // 创建套接字地址结构体对象bzero(&local, sizeof(local)); // 初始化locallocal.sin_family = AF_INET; // 指定协议族local.sin_port = htons(_port); // 指定端口号(htons的功能就是将我们创建的端口号转成网络子节序)// local.sin_addr.s_addr = inet_addr(_ip.c_str()); // 指定ip,需要传递整形的ip(inet_addr就是将字符串ip地址转为整形,同时也转为网络子节序)local.sin_addr.s_addr = INADDR_ANY; // 指定ip,INADDR_ANY表示本机的任意一个ip地址int n = ::bind(_sockfd, (struct sockaddr *)&local, sizeof(local)); // 将套接字地址结构体绑定到套接字if (n != 0){lg.LogMessage(Fatal, "bind err ,%d:%s", errno, strerror(errno));exit(Bind_Err);}ThreadPool<task_t>::Getinstance()->Start(); // 启动线程池}void AddOnlineUser(InetAddr addr) // 将addr插入到_online_user中{{Lockguard lockguard(&_user_mutex);// cout << "添加用户" << endl;for (size_t i = 0; i < _online_user.size(); i++){if (addr == _online_user[i]){// cout << "用户已存在" << endl;return;}}// cout << "添加用户成功" << endl;_online_user.push_back(addr);lg.LogMessage(Debug, "add user to onlinelist success, %s:%d\n", addr.Ip().c_str(), addr.Port());}}// 服务器路由void Route(int sock, const string &message){{Lockguard lockguard(&_user_mutex);for (auto &user : _online_user){// cout << "发送给client" << endl;sendto(sock, message.c_str(), message.size(), 0, (struct sockaddr *)&user.GetAddr(), sizeof(user.GetAddr()));lg.LogMessage(Debug, "send message to client success, %s:%d\n", user.Ip().c_str(), user.Port());}}}void Start(){// 服务器永远是在循环运行char buffer[defaultsize]; // 创建一个缓冲区for (;;){struct sockaddr_in peer; // 创建一个套接字地址空间,用于存储客户端的套接字地址socklen_t len = sizeof(peer); // 获取套接字的地址空间大小ssize_t n = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &len); // 用于接收来自客户端的数据,并将其存储在buffer中// cout << "判断服务器收到消息没" << buffer << endl;if (n > 0){InetAddr addr(peer);AddOnlineUser(addr);buffer[n] = 0;string message = "[";message += addr.Ip() + ":" + to_string(addr.Port()) + "]" + "#";message += buffer;task_t task = std::bind(&UdpServer::Route, this, _sockfd, message);ThreadPool<task_t>::Getinstance()->Push(task);// 处理信息// string response=_OnMessage(buffer);// sendto(_sockfd, response.c_str(), response.size(), 0, (struct sockaddr *)&peer, len); // 向指定的套接字peer进行接收数据}}}~UdpServer(){pthread_mutex_destroy(&_user_mutex);}private:string _ip;uint16_t _port;int _sockfd;vector<InetAddr> _online_user; // 会被多线程同时访问pthread_mutex_t _user_mutex; // 锁 };
main.cc
服务器端
#include"Udpserver.hpp" #include"Comm.hpp" #include<memory> #include<stdio.h>using namespace std;string OnMessageDefault(string request) {return request+"[haha, got you!!]"; }string ExecuteCommand(string command)//我们的处理客户端发来的消息,不一定直接返回字符串,我们还可以让客户端输入shell命令,然后执行 {cout<<"get a message :"<<command<<endl;FILE* fp=popen(command.c_str(),"r");if(fp==nullptr){return "execute error, reason is uknown";}string response;char buffer[1024]={0};while(true){char* s=fgets(buffer,sizeof(buffer),fp);if(!s) break;//如果读取为空就返回else response+=buffer;}pclose(fp);return response; }int main(int argc,char* argv[]) {if(argc!=2){cout<<"Usage:\n ./udp_echo_server <port>"<<endl;return -1;}// string ip=argv[1];uint16_t port=stoi(argv[1]);//unique_ptr<UdpServer> usvr=make_unique<UdpServer>();???//UdpServer* usvr=new UdpServer(OnMessageDefault,port);//UdpServer* usvr=new UdpServer(ExecuteCommand,port);UdpServer* usvr=new UdpServer(port);usvr->Init();usvr->Start();return 0; }
UdpClient.cc
客户端
#include "Log.hpp" #include <iostream> #include <string> #include <sys/types.h> #include <sys/socket.h> #include <cerrno> #include <cstring> #include <unistd.h> #include "Comm.hpp" #include <netinet/in.h> #include <strings.h> #include <arpa/inet.h> #include <cerrno> #include"thread.hpp" #include"InetAddr.hpp"using namespace std;class ThreadData {public:ThreadData(int sock,struct sockaddr_in& server):_sockfd(sock),_serveraddr(server){}~ThreadData(){}public:int _sockfd;InetAddr _serveraddr; };void RecvRoutine(ThreadData& td) {char buffer[4096];while(true){//收消息struct sockaddr_in temp;socklen_t len = sizeof(temp);size_t n = recvfrom(td._sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&temp, &len);if (n > 0){buffer[n] = 0;cerr<<buffer << endl;}elsebreak;} };void SendRoutine(ThreadData& td) {while(true){// 我们要发的数据string inbuffer;cout << "Please Enter#:";getline(cin, inbuffer);//cout << inbuffer << endl;// 我们要发给谁?serverauto server=td._serveraddr.GetAddr();int n = sendto(td._sockfd, inbuffer.c_str(), inbuffer.size(), 0, (struct sockaddr *)&server, sizeof(server));if(n<=0)cout<<"send error"<<endl;} };int main(int argc, char *argv[]) {if (argc != 3){cout << "Usage:\n ./udp_echo_client <ip> <port>" << endl;return -1;}string serverip = argv[1]; // 服务器ipuint16_t serverport = atoi(argv[2]); // 服务器端口号// 创建套接字int sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (sockfd < 0){cerr << "socket error" << strerror(errno) << endl;return 1;}cout << "client create socket success:" << sockfd << endl;// 2.客户端也需要绑定套接字空间,但是,不需要显示的绑定,client会在首次发送数据的时候自动绑定// 服务器的端口号,一定众所周知的,不可以随意改变,client需要port,客户端需要绑定随机端口// 因为客户端非常多,所以客户端需要绑定随机端口// 2.1填充一下server的信息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());ThreadData td(sockfd,server);Thread<ThreadData>recver("recver",RecvRoutine,td);Thread<ThreadData>sender("sender",SendRoutine,td);recver.Start();sender.Start();recver.Join();//主线程等待子线程sender.Join();close(sockfd);return 0; }
Makefile
工程管理,形成客户端和服务端
.PHONY:all all:udp_server udp_clientudp_server:main.ccg++ -o $@ $^ -std=c++11 -lpthreadudp_client:UdpClient.ccg++ -o $@ $^ -std=c++11 -lpthread.PHONY:clean clean:rm -f udp_server udp_client
运行结果
首先编译形成客户端和服务端
make
运行服务端
bch@hcss-ecs-6176:~/linux/4_3/UDPsever/udp_echo_server_chat.4.02$ ./udp_server 8888 [Info][2024年-5月-24日 15时:28分:36秒][93230]socket success ,sockfd:3 [Info][2024年-5月-24日 15时:28分:36秒][93230]单例创建成功... [Info][2024年-5月-24日 15时:28分:36秒][93230]thread -1 is created ... [Info][2024年-5月-24日 15时:28分:36秒][93230]thread -2 is created ... [Info][2024年-5月-24日 15时:28分:36秒][93230]thread -3 is created ... [Info][2024年-5月-24日 15时:28分:36秒][93230]thread -4 is created ... [Info][2024年-5月-24日 15时:28分:36秒][93230]thread -5 is created ... [Info][2024年-5月-24日 15时:28分:36秒][93230]thread -1 is running... [Info][2024年-5月-24日 15时:28分:36秒][93230]thread -2 is running... [Info][2024年-5月-24日 15时:28分:36秒][93230]thread -3 is running... [Info][2024年-5月-24日 15时:28分:36秒][93230]thread -4 is running... [Info][2024年-5月-24日 15时:28分:36秒][93230]thread -5 is running... [Debug][2024年-5月-24日 15时:28分:36秒][93230]no task,thread -3 is sleeping... [Debug][2024年-5月-24日 15时:28分:36秒][93230]no task,thread -2 is sleeping... [Debug][2024年-5月-24日 15时:28分:36秒][93230]no task,thread -1 is sleeping... [Debug][2024年-5月-24日 15时:28分:36秒][93230]no task,thread -5 is sleeping... [Debug][2024年-5月-24日 15时:28分:36秒][93230]no task,thread -4 is sleeping...
启动客户端
🌸🌸🌸如果大家还有不懂或者建议都可以发在评论区,我们共同探讨,共同学习,共同进步。谢谢大家! 🌸🌸🌸
这篇关于Linux第三十九章的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!