🐶博主主页:@ᰔᩚ. 一怀明月ꦿ
❤️🔥专栏系列:线性代数,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=nullptr char 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_routine bool Start() { //启动 for(auto& thread:_threads) { thread.Start(); lg.LogMessage(Info,"%s is running...\n",thread.Threadname().c_str()); } return true; } //封装了pthread_cond_wait void ThreadWait(const ThreadData &td) { lg.LogMessage(Debug,"no task,%s is sleeping...\n",td._threadname.c_str()); pthread_cond_wait(&_cond,&_mutex); } //封装了pthread_cond_signal void 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)); // 初始化local local.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; } else break; } }; void SendRoutine(ThreadData& td) { while(true) { // 我们要发的数据 string inbuffer; cout << "Please Enter#:"; getline(cin, inbuffer); //cout << inbuffer << endl; // 我们要发给谁?server auto 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]; // 服务器ip uint16_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_client udp_server:main.cc g++ -o $@ $^ -std=c++11 -lpthread udp_client:UdpClient.cc g++ -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...
启动客户端
🌸🌸🌸如果大家还有不懂或者建议都可以发在评论区,我们共同探讨,共同学习,共同进步。谢谢大家! 🌸🌸🌸