Linux第三十九章

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

❤️‍🔥专栏系列:线性代数C初学者入门训练题解CC的使用文章「初学」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...

启动客户端

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

  • 19
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 《Linux+, 第七章: Linux 文件系统》是《Linux+认证教程》中的第七章,重点介绍了Linux操作系统中的文件系统。在这一章中,读者将了解到Linux文件系统的基本概念、常用的文件系统类型以及文件系统的管理和维护等内容。 首先,本章开始介绍了Linux文件系统的基本概念,包括文件和目录的概念以及在Linux中的表示方式。读者将学习到如何使用文件路径和文件名来访问文件和目录。同时,还介绍了Linux系统中的特殊目录,如根目录、用户主目录和共享目录等。 接着,本章继续讲解了常见的文件系统类型。Linux支持多种文件系统类型,如EXT4、XFS、Btrfs等。读者将了解到每种文件系统类型的特点和适用场景,并学习如何在Linux中创建和格式化不同类型的文件系统。 然后,本章讲解了文件系统的管理和维护。读者将学习如何使用命令行工具来管理和操作文件系统,如创建、删除、复制和移动文件等。同时,还介绍了文件系统的权限和属性配置,如设置文件的拥有者和权限等。 最后,本章还涉及了文件系统的备份和恢复。读者将学习如何使用备份工具来对文件系统进行备份,以及如何从备份中恢复数据。 通过学习本章内容,读者将深入了解Linux文件系统的原理和操作方法。这对于Linux系统管理员和开发人员来说非常重要,因为文件系统是Linux操作系统中最基础和核心的部分之一。掌握了文件系统的知识,读者将能够更好地管理和维护Linux系统,并解决文件系统相关的问题。 ### 回答2: 《Linux头歌》是一本关于Linux操作系统的技术书籍,第七章主要讲述了Linux文件系统的管理和操作。本章主要涵盖了以下几个方面的内容。 首先,本章介绍了Linux文件系统的基本概念和特点。Linux文件系统采用了类似于树状结构的层次目录结构,以根目录"/"为起点,通过不同的目录和文件来组织和管理数据。同时,Linux文件系统还具有对文件和目录进行权限管理的功能,确保安全性。 接着,本章详细介绍了如何在Linux系统中创建、删除、复制和移动文件和目录。通过命令行工具或图形界面工具,用户可以轻松地对文件和目录进行操作,满足不同的需求。此外,还介绍了文件和目录的属性和权限设置,确保文件的安全性。 然后,本章讲解了Linux文件系统中的硬链接和软链接的概念和用法。硬链接是指多个文件指向同一个物理存储空间,而软链接是指一个文件指向另一个文件,类似于快捷方式。用户可以根据需要使用不同的链接方式,帮助管理和组织文件。 最后,本章还介绍了Linux文件系统的挂载和卸载操作。在Linux系统中,用户可以通过挂载操作将外部设备或远程文件系统与本地文件系统连接起来,以便访问和操作外部文件。同时,也可以通过卸载操作安全地断开与外部文件系统的连接。 总之,第七章《Linux头歌》详细介绍了Linux文件系统的管理和操作技巧,帮助读者更好地理解和掌握Linux操作系统的文件系统特性和使用方法。无论是初学者还是有一定经验的用户,都可以从中获得实用的知识和技能,提高对Linux系统的操作能力。 ### 回答3: 《Linux内核设计与实现》是一本经典的Linux内核相关书籍,其中第七章主要介绍了Linux内核的关键组成部分——系统调用及库函数。系统调用是操作系统内核提供给用户程序的一组接口,它们允许用户程序通过内核来访问底层系统资源和服务。这一章内容对于深入理解 Linux 内核的使用和开发具有重要意义。 首先,第七章详细介绍了系统调用的实现原理和机制。作者解析了系统调用表的结构和功能,并且详细说明了Linux内核是如何通过系统调用号来定位和调用相应的系统调用函数的。这些系统调用函数在内核空间中运行,并能够处理用户程序发起的请求,实现具体的功能。 其次,本章还介绍了用户空间和内核空间之间的切换机制。当用户程序发起系统调用的时候,会触发用户态和内核态之间的转换,在转换过程中,内核会根据系统调用号找到对应的系统调用函数,并执行相应的操作。这一章详细分析了切换过程的细节以及相关的数据结构。 此外,本章还对一些常用的系统调用进行了详细介绍,如文件操作相关的系统调用(如open、read、write等),进程管理相关的系统调用(如fork、execve、wait等),以及网络通信相关的系统调用(如socket、bind、connect等)。对于开发者来说,熟悉这些系统调用的使用方法和原理非常重要,能够帮助他们更好地设计和开发Linux内核的应用程序。 总之,第七章《Linux内核设计与实现》详细介绍了Linux内核的系统调用和库函数的实现原理和机制,以及常用系统调用的使用方法。通过学习这一章的内容,读者能够深入理解Linux内核的工作原理,对于使用和开发Linux内核应用程序具有指导意义。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值