Linux第三十九章

news2024/12/24 3:59:04

🐶博主主页:@ᰔᩚ. 一怀明月ꦿ 

❤️‍🔥专栏系列:线性代数,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...

启动客户端

  🌸🌸🌸如果大家还有不懂或者建议都可以发在评论区,我们共同探讨,共同学习,共同进步。谢谢大家! 🌸🌸🌸 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1687440.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【全开源】JAVA同城搬家系统源码小程序APP源码

JAVA同城搬家系统源码 特色功能&#xff1a; 强大的数据处理能力&#xff1a;JAVA提供了丰富的数据结构和算法&#xff0c;以及强大的并发处理能力&#xff0c;使得系统能够快速地处理大量的货物信息、司机信息、订单信息等&#xff0c;满足大规模物流的需求。智能路径规划&a…

【Redis】String的介绍与应用详解

大家好&#xff0c;我是白晨&#xff0c;一个不是很能熬夜&#xff0c;但是也想日更的人。如果喜欢这篇文章&#xff0c;点个赞&#x1f44d;&#xff0c;关注一下&#x1f440;白晨吧&#xff01;你的支持就是我最大的动力&#xff01;&#x1f4aa;&#x1f4aa;&#x1f4aa…

设置 sticky 不生效?会不会是你还是没懂 sticky?

官方描述 基本上可以看懂的就会知道。sticky 是相对于存在滚动条的内容的&#xff0c;啥意思&#xff1f; 就是不论你被谁包着&#xff0c;你只会往上找有 overflow 属性的盒子进行定位&#xff0c;包括&#xff1a;overflow:hidden; overflow:scroll; overflow:auto; overflo…

一键批量提取TXT文档前N行,高效处理海量文本数据,省时省力新方案!

大量的文本信息充斥着我们的工作与生活。无论是研究资料、项目文档还是市场报告&#xff0c;TXT文本文档都是我们获取和整理信息的重要来源。然而&#xff0c;面对成百上千个TXT文档&#xff0c;如何快速提取所需的关键信息&#xff0c;提高工作效率&#xff0c;成为了许多人头…

EI稳定检索--人文社科类会议(ICBAR 2024)

【ACM独立出版】第四届大数据、人工智能与风险管理国际学术会议 (ICBAR 2024) 2024 4th International Conference on Big Data, Artificial Intelligence and Risk Management 【高录用•快检索&#xff0c;ACM独立出版-稳定快速EI检索 | 往届均已完成EI, Scopus检索】 【见…

运行vue2项目基本过程

目录 步骤1 步骤2 步骤3 补充&#xff1a; 解决方法&#xff1a; node-scss安装失败解决办法 步骤1 安装npm 步骤2 切换淘宝镜像 #最新地址 淘宝 NPM 镜像站喊你切换新域名啦! npm config set registry https://registry.npmmirror.com 步骤3 安装vue-cli npm install…

分布式中traceId链接服务间的日志

使用技术&#xff1a; 网关&#xff1a;SpringCloudGateway RPC调用&#xff1a;Feign 一&#xff1a;在网关入口处设置header&#xff1a;key-traceId&#xff0c;value-UUID import com.kw.framework.common.croe.constant.CommonConstant; import com.kw.framework.gateway…

机器学习高斯贝叶斯算法实战:判断肿瘤是良性还是恶性

概述 我们使用威斯康星乳腺肿瘤数据集&#xff0c;来构建一个机器学习模型&#xff0c;用来判断患者的肿瘤是良性还是恶性。 数据分析 威斯康星乳腺肿瘤数据集&#xff0c;包括569个病例的数据样本&#xff0c;每个样本具有30个特征值。 样本分为两类&#xff1a;恶性Malig…

SHA1获取

这里写目录标题 JDK获取uniapp开发Dcould获取 JDK获取 一、下载jdk 链接: http://www.oracle.com/ 二、安装直接下一步下一步 三、配置环境变量 先新增变量JAVA_HOME变量值为C:\devUtils\jdk (jdk安装路径位置)再配置Path(%JAVA_HOME%\bin) 四、创建SHA1安全证书 win r输入cmd…

常见应用流量特征分析

目录 1.sqlmap 1.常规GET请求 2.通过--os-shell写入shell 3.post请求 2.蚁剑 编码加密后 3.冰蝎 冰蝎_v4.1 冰蝎3.2.1 4.菜刀 5.哥斯拉 1.sqlmap 1.常规GET请求 使用的是sqli-labs的less7 &#xff08;1&#xff09;User-Agent由很明显的sqlmap的标志&#xff0c;展…

如何快速增加外链?

要快速增加外链并不难&#xff0c;相信各位都知道&#xff0c;难的是快速增加外链且没有风险&#xff0c;所以这时候GNB外链的重要性就出现了&#xff0c;这是一种自然的外链&#xff0c;何谓自然的外链&#xff0c;在谷歌的体系当中&#xff0c;自然外链指的就是其他网站资源给…

[Spring Boot]baomidou 多数据源

文章目录 简述本文涉及代码已开源 项目配置pom引入baomidouyml增加dynamic配置启动类增加注解配置结束 业务调用注解DS()TransactionalDSTransactional自定义数据源注解MySQL2 测试调用查询接口单数据源事务测试多数据源事务如果依然使用Transactional会怎样&#xff1f;测试正…

不同类型的区块链钱包有什么特点和适用场景?

区块链钱包是用于存储和管理加密货币的重要工具&#xff0c;市面上有许多不同类型的区块链钱包可供选择。以下是几种主要类型的区块链钱包及其特点和适用场景。 1.软件钱包&#xff1a; 特点&#xff1a;软件钱包是最常见的一种区块链钱包&#xff0c;通常作为软件应用程序提供…

docker不删除容器更改其挂载目录

场景&#xff1a;docker搭建的jenkins通常需要配置很多开发环境&#xff0c;当要更换挂载目录&#xff0c;每次都需要删除容器重新运行&#xff0c;不在挂载目录的环境通常不会保留。 先给一个参考博客docker不删除容器&#xff0c;修改容器挂载或其他_jenkins 修改容器挂载do…

第17讲:C语言内存函数

目录 1.memcpy使用和模拟实现2.memmove使用和模拟实现3.memset函数的使用4.memcmp函数的使用 1.memcpy使用和模拟实现 void * memcpy (void * destination, const void * source, size_t num);• 函数memcpy从source的位置开始向后复制num个字节的数据到destination指向的内存…

分析电脑上处理器的性能报告

这张图片给出了一份详细的第11代Intel(R) Core(TM) i7-1165G7 2.80GHz处理器的性能报告。 CPU型号&#xff1a;11th Gen Intel(R) Core(TM) i7-1165G7&#xff08;这是一个低功耗的移动处理器&#xff0c;常用于轻薄型笔记本电脑&#xff09; 基准速度&#xff1a;2.80 GHz&…

C语言-信号

信号 一、信号是什么东西 信号是事件发生时通知进程的一种机制&#xff0c;有时也称之为软件中断。 信号的到来会打断了程序执行的正常流程。 大多数情况下&#xff0c;无法预测信号到达的精确时间。 一个&#xff08;具有合适权限的&#xff09;进程能够向另一进程发送信…

python查找内容在文件中的第几行(利用了滑动窗口)

def find_multiline_content(file_path, multiline_content):with open(file_path, r) as file:# 文件内容file_lines file.readlines()# 待检测内容multiline_lines multiline_content.strip().split(\n)# 待检测内容总行数num_multiline_lines len(multiline_lines)matchi…

Postgresql源码(130)ExecInterpExpr转换为IR的流程

相关 《Postgresql源码&#xff08;127&#xff09;投影ExecProject的表达式执行分析》 《Postgresql源码&#xff08;128&#xff09;深入分析JIT中的函数内联llvm_inline》 《Postgresql源码&#xff08;129&#xff09;JIT函数中如何使用PG的类型llvmjit_types》 表达式计算…

计算机系统基础 8 循环程序

概要 两种实现方法——分支指令实现和专门的循环语句实现以及有关循环的优化。 分支指令实现 倒计数 …… MOV ECX&#xff0c;循环次数 LOOPA&#xff1a;…… …… DEC ECX JNE LOOPA 正计数 …… MOV ECX&#xff0c;0 LOOPA&#xff1a; …… INC ECX CMP …