TCP客户端服务器端通信(线程池版)

news2024/12/12 5:40:08

1、什么是监听套接字,和UDP相比,TCP为什么文件描述符变多了?

       在网络编程中,TCP和UDP是两种常见的传输协议,它们之间最大的不同之一在于连接的管理方式。为了更好地理解这个区别,我们可以用一个生动的比喻来说明。

        假设你在一家餐馆工作,其中张三负责把客人带到座位上,而李四和王五是服务员,他们负责为客人点菜和服务。张三的角色类似于TCP连接中的监听套接字。他的工作是确保当客户到达时,他们能够被快速、有效地分配给一个合适的服务员。

        在TCP协议中,监听套接字(就像张三)是独特的:它负责创建监听端口,通过这个端口,服务器可以接收到请求。当有新的连接请求时,该监听套接字的角色是“接客”,将请求转交给一个新的文件描述符(类似李四或王五),这个新的文件描述符负责具体的数据传输和处理。因此,虽然张三(监听套接字)数量是固定的,但却需要多个“服务员”来处理多个客户连接,即多个文件描述符。相比之下,UDP协议不需要这种复杂的连接管理机制,因为它是无连接的,不需要维护任何长时间的连接状态,因此它的文件描述符相对少。

2、如果现在还没写客户端,只写了服务器端,怎么测试当前服务器通信的时候会有别人来连我呢?工具:telnet 127.0.0.1 8888(底层默认tcp)

当你正在开发一个服务器应用程序,但还没有编写客户端时,你可能会想测试服务器的连接和通信功能。这时,我们可以使用一个简单的工具——telnet。假设你的服务器正在本地计算机上运行,并监听8888端口,你可以在命令行中输入以下命令来测试连接:

要检查当前活跃的网络连接,可以使用以下命令:

netstat -nltp 

服务器端代码写好了,直接用工具来测试。

#pragma once
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include "Log.hpp"
#include <string>
#include <arpa/inet.h>
#include <cstring>
#include <netinet/in.h>

const int defaultfd=-1;
const std::string defaultip="0.0.0.0";
const int backlog=10;//一般不要设置的太大,后面解释
Log lg;
enum{
    SOCKET_ERR=2,
    BIND_ERR,
    Listen_ERR
};
class TcpServer
{
public:
    TcpServer(const uint16_t &port,const std::string &ip=defaultip):listensock_(defaultfd),port_(port),ip_(ip)
    {};
    void InitServer()
    {
        listensock_=socket(AF_INET,SOCK_STREAM,0);
        if(listensock_<0)
        {
            lg(Fatal,"errno:%d,errrstring:%s",errno,strerror(errno));
            exit(SOCKET_ERR);
        }
        lg(Info,"create socket success,listensock_:%d",listensock_);
        struct sockaddr_in local; //服务器信息
        memset(&local,0,sizeof(local));
        local.sin_family=AF_INET;
        local.sin_port=htons(port_); //用户在构建服务器时就需要告诉我
        inet_aton(ip_.c_str(),&(local.sin_addr));
        local.sin_addr.s_addr=INADDR_ANY; //或者写成0
        if(bind(listensock_,(const sockaddr*)&local,sizeof(local))<0)
        {
            lg(Fatal,"bind error,errno:%d,errrstring:%s",errno,strerror(errno));
            exit(BIND_ERR);
        }
        lg(Info,"bind success");
        //和UDP不一样的地方,设为监听状态 Tcp是面向链接的,服务器一般是比较被动的。服务器一直处于一种,一直在等待连接到来的状态
        if(listen(listensock_,backlog)<0)
        {
            lg(Fatal,"errno:%d,errrstring:%s",errno,strerror(errno));
            exit(Listen_ERR);
        }
    }
    void Start()
    {
        lg(Info, "tcpserver is running....");
        while (true)
        {
            // 1.获取新连接
            struct sockaddr_in client;
            socklen_t len = sizeof(client);
            int sockfd = accept(listensock_, (struct sockaddr *)&client, &len);
            if (sockfd < 0)
            {
                lg(Warning, "errno:%d,errrstring:%s", errno, strerror(errno));//获取连接失败,就直接再去获取
                continue;
            }
            //到底是谁连接的我
            uint16_t clientport=ntohs(client.sin_port); //网络转主机
            char clientip[32];
            std::string ip=inet_ntop(AF_INET,&(client.sin_addr),clientip,sizeof(clientip));//把客户端的ip存到ipstr中

            //2.根据新连接进行通信
           // lg(Info,"get a new Link...,sockfd:%d,client ip:%s,client port:%d",sockfd,clientip,clientport);
            //version 1
            Service(sockfd,ip,clientport);//拿到客户端的端口和ip
        }
    }
    void Service(int sockfd,const std::string &clientip,uint16_t &clientport)
    {
        char buffer[4096];
        //测试代码:你给我发什么我就给你响应什么
        while(true)
        {
            ssize_t n=read(sockfd,buffer,sizeof(buffer));
            if(n>0)
            {
                buffer[n]=0;
                std::cout<<"client say#"<<buffer<<std::endl;
                std::string echo_string="tpserver echo#";
                echo_string+=buffer;
                write(sockfd,echo_string.c_str(),echo_string.size()); //再写回去
            }

        }
    }
    ~TcpServer()
    {}; 
private:
    int listensock_;
    uint16_t port_;
    std::string ip_;
};

测试:ctrl+] 进入    ctrl+] quit退出

 

发现客户端发的aaaaa,服务器端收到了。

问题1:要是客户端退出,服务器会怎么办?

服务器会读到0,服务中止,服务器端也直接退出

问题2:服务器向一个不存在的fd写入

signal(SIGPIPE,SIG_IGN);//防止出现写入的时候,向一个已经关闭的文件描述符写入的时候,此时连接没有意义,这时写时,os直接SIGPIPE掉

3、代码

TcpServer.hpp

#pragma once
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include "Log.hpp"
#include <string>
#include <arpa/inet.h>
#include <cstring>
#include <netinet/in.h>
#include <unistd.h>
#include <signal.h>
#include<sys/wait.h>
#include<pthread.h>
#include "ThreadPool.hpp"
#include"Task.hpp"
#include<signal.h>
const int defaultfd=-1;
const std::string defaultip="0.0.0.0";
const int backlog=10;//一般不要设置的太大,后面解释
Log lg;
enum{
    SOCKET_ERR=2,
    BIND_ERR,
    Listen_ERR
};
class TcpServer;
class ThreadData//把TcpServer本身传进来
{
public:
    ThreadData(int fd,const std::string &ip,const uint16_t &port,TcpServer *t):sockfd(fd),clientip(ip),clientport(port),tsvr(t)
    {}
public:
    int sockfd;
    std::string clientip;
    uint16_t clientport;
    TcpServer *tsvr;
};
class TcpServer
{
public:
    TcpServer(const uint16_t &port,const std::string &ip=defaultip):listensock_(defaultfd),port_(port),ip_(ip)
    {};
    void InitServer()
    {
        listensock_=socket(AF_INET,SOCK_STREAM,0);
        if(listensock_<0)
        {
            lg(Fatal,"errno:%d,errrstring:%s",errno,strerror(errno));
            exit(SOCKET_ERR);
        }
        lg(Info,"create socket success,listensock_:%d",listensock_);
        struct sockaddr_in local; //服务器信息
        memset(&local,0,sizeof(local));
        local.sin_family=AF_INET;
        local.sin_port=htons(port_); //用户在构建服务器时就需要告诉我
        inet_aton(ip_.c_str(),&(local.sin_addr));
        local.sin_addr.s_addr=INADDR_ANY; //或者写成0
        if(bind(listensock_,(const sockaddr*)&local,sizeof(local))<0)
        {
            lg(Fatal,"bind error,errno:%d,errrstring:%s",errno,strerror(errno));
            exit(BIND_ERR);
        }
        lg(Info,"bind success");
        //和UDP不一样的地方,设为监听状态 Tcp是面向链接的,服务器一般是比较被动的。服务器一直处于一种,一直在等待连接到来的状态
        if(listen(listensock_,backlog)<0)
        {
            lg(Fatal,"errno:%d,errrstring:%s",errno,strerror(errno));
            exit(Listen_ERR);
        }
    }
    // void Service(int sockfd,const std::string &clientip,uint16_t &clientport)
    // {
    //     char buffer[4096];
    //     //测试代码:你给我发什么我就给你响应什么
    //     while(true)
    //     {
    //         ssize_t n=read(sockfd,buffer,sizeof(buffer));
    //         if(n>0)
    //         {
    //             buffer[n]=0;
    //             std::cout<<"client say#"<<buffer<<std::endl;
    //             std::string echo_string="tpserver echo#";
    //             echo_string+=buffer;
    //             write(sockfd,echo_string.c_str(),echo_string.size()); //再写回去
    //         }
    //         else if(n==0)
    //         {
    //             //lg(Info,"%s:%d quit,server close sockfd:%d",clientip,clientport,sockfd);
    //             break;
    //         }
    //         else
    //         {
    //             lg(Warning,"read Error...,sockfd:%d,client ip:%s,client port:%d",sockfd,clientip.c_str(),clientport);
    //         }

    //     }
    // }
    // static void* routine(void *args)//静态函数,把this传进来
    // {
    //     pthread_detach(pthread_self());//把自己设置为分离状态,主线程一直再获取新连接,创建出线程就不管了,让新线程进行任务处理
    //     ThreadData *td=static_cast<ThreadData *>(args);
    //     td->tsvr->Service(td->sockfd,td->clientip,td->clientport);//由该线程提供服务
    //     delete td;//提供完服务,申请的堆空间释放掉
    //     return nullptr;
    // }
    void Start()
    {
        signal(SIGPIPE,SIG_IGN);//防止出现写入的时候,向一个已经关闭的文件描述符写入的时候,此时连接没有意义,这时写时,os直接SIGPIPE掉
        ThreadPool<Task>::GetInstance()->Start();//启动线程池
        lg(Info, "tcpserver is running....");
        while (true)
        {
            // 1.获取新连接
            struct sockaddr_in client;
            socklen_t len = sizeof(client);
            int sockfd = accept(listensock_, (struct sockaddr *)&client, &len);
            if (sockfd < 0)
            {
                lg(Warning, "errno:%d,errrstring:%s", errno, strerror(errno));//获取连接失败,就直接再去获取
                continue;
            }
            //到底是谁连接的我
            uint16_t clientport=ntohs(client.sin_port); //网络转主机
            char clientip[32];
            inet_ntop(AF_INET,&(client.sin_addr),clientip,sizeof(clientip));//把客户端的ip存到ipstr中

            //2.根据新连接进行通信
           //lg(Info,"get a new Link...,sockfd:%d,client ip:%s,client port:%d",sockfd,ip,clientport);
            //version 1
            // Service(sockfd,ip,clientport);//拿到客户端的端口和ip
            // close(sockfd);
            //version 2 --多进程版,因为创建一个进程成本太高了
            // pid_t id=fork();
            // if(id==0)
            // {
            //     close(listensock_);
            //     if(fork()>0) exit(0);
            //     //子进程 什么都可以看到
            //     Service(sockfd,ip,clientport);//孙子进程,wait立马返回,父进程和孙子进程并发访问,不用管孙子,不用等孙子,儿子已经挂了,孙子会被系统领养
            //     close(sockfd);
            //     exit(0);
            // }
            // close(sockfd);
            // //父进程继续获取新连接 打开文件描述符,打开之后交给子进程,自己就关掉了,如果不关,系统中会有非常多的文件没有关闭
            // pid_t rid=waitpid(id,nullptr,0);
            // (void)rid;
            //version 3 多线程版本
            // ThreadData *td=new ThreadData(sockfd,ip,clientport,this);//this把当前对象传进来
            // pthread_t tid;
            // pthread_create(&tid,nullptr,routine,td);
            //version 4 线程池版
            Task t(sockfd,clientip,clientport);
            ThreadPool<Task>::GetInstance()->Push(t);
        }
    }
    ~TcpServer()
    {}; 
private:
    int listensock_;
    uint16_t port_;
    std::string ip_;
};
ThreadPool.hpp
#pragma once

#include <iostream>
#include <vector>
#include <string>
#include <queue>
#include <pthread.h>
#include <unistd.h>

struct ThreadInfo
{
    pthread_t tid;
    std::string name;
};

static const int defalutnum = 10;

template <class T>
class ThreadPool
{
public:
    void Lock()
    {
        pthread_mutex_lock(&mutex_);
    }
    void Unlock()
    {
        pthread_mutex_unlock(&mutex_);
    }
    void Wakeup()
    {
        pthread_cond_signal(&cond_);
    }
    void ThreadSleep()
    {
        pthread_cond_wait(&cond_, &mutex_);
    }
    bool IsQueueEmpty()
    {
        return tasks_.empty();
    }
    std::string GetThreadName(pthread_t tid)
    {
        for (const auto &ti : threads_)
        {
            if (ti.tid == tid)
                return ti.name;
        }
        return "None";
    }

public:
    static void *HandlerTask(void *args)
    {
        ThreadPool<T> *tp = static_cast<ThreadPool<T> *>(args);
        std::string name = tp->GetThreadName(pthread_self());
        while (true)
        {
            tp->Lock();

            while (tp->IsQueueEmpty())
            {
                tp->ThreadSleep();
            }
            T t = tp->Pop();
            tp->Unlock();

            t();
        }
    }
    void Start()
    {
        int num = threads_.size();
        for (int i = 0; i < num; i++)
        {
            threads_[i].name = "thread-" + std::to_string(i + 1);
            pthread_create(&(threads_[i].tid), nullptr, HandlerTask, this);
        }
    }
    T Pop()
    {
        T t = tasks_.front();
        tasks_.pop();
        return t;
    }
    void Push(const T &t)
    {
        Lock();
        tasks_.push(t);
        Wakeup();
        Unlock();
    }
    static ThreadPool<T> *GetInstance()
    {
        if (nullptr == tp_) // ???
        {
            pthread_mutex_lock(&lock_);
            if (nullptr == tp_)
            {
                std::cout << "log: singleton create done first!" << std::endl;
                tp_ = new ThreadPool<T>();
            }
            pthread_mutex_unlock(&lock_);
        }

        return tp_;
    }

private:
    ThreadPool(int num = defalutnum) : threads_(num)
    {
        pthread_mutex_init(&mutex_, nullptr);
        pthread_cond_init(&cond_, nullptr);
    }
    ~ThreadPool()
    {
        pthread_mutex_destroy(&mutex_);
        pthread_cond_destroy(&cond_);
    }
    ThreadPool(const ThreadPool<T> &) = delete;
    const ThreadPool<T> &operator=(const ThreadPool<T> &) = delete; // a=b=c
private:
    std::vector<ThreadInfo> threads_;
    std::queue<T> tasks_;

    pthread_mutex_t mutex_;
    pthread_cond_t cond_;

    static ThreadPool<T> *tp_;
    static pthread_mutex_t lock_;
};

template <class T>
ThreadPool<T> *ThreadPool<T>::tp_ = nullptr;

template <class T>
pthread_mutex_t ThreadPool<T>::lock_ = PTHREAD_MUTEX_INITIALIZER;
TcpClient.cc
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <cstring>
//./tcpclient serverip serverport
void Usage(const std::string &proc)
{
    std::cout << "\n\rUsage: " << proc << " port[1024+]\n" << std::endl;
}
int main(int argc,char* argv[])
{
    if(argc!=3)
    {
        Usage(argv[0]);
        exit(0);
    }
    std::string serverip = argv[1];
    uint16_t serverport = std::stoi(argv[2]);
    struct sockaddr_in server;
    memset(&server, 0, sizeof(server));
    server.sin_family = AF_INET;
    server.sin_port = htons(serverport);
    inet_pton(AF_INET, serverip.c_str(), &(server.sin_addr)); // 字符串转为in_addr
    
   
    while (true)                                              // 每次翻译的时候都要重新建立连接,因为服务器每次只给我提供一次服务
    {
        int cnt = 5;
        int isreconnect = false;
        int sockfd = 0;
        sockfd = socket(AF_INET, SOCK_STREAM, 0);
        if (sockfd < 0)
        {
            std::cerr << "socket error" << std::endl;
            return 1;
        }
        do
        {
            // tcp客户端要不要bind,要不要显示的bind?
            // 客户端需要绑定,不需要显示的绑定,将来再通信时需要端口号和ip标识自己的唯一性,当发出消息的时候,服务器才能把消息转过来,但是对于客户端来说
            // 端口号具体是几不重要,唯一就行,由os随机选择,根据你的需要随机选中,UDP首次发送数据的时候
            // 客户端向服务器发起连接,TCP中客户端发起connect时,进行自动随机bind,这个函数最后几个参数是要知道服务器的ip

            int n = connect(sockfd, (struct sockaddr *)&server, sizeof(server));
            if (n < 0)
            {
                std::cerr << "connect error" << std::endl;
                isreconnect=true;
                cnt--;
                return 2;
            }
            else{
                break;
            }
        }while(cnt&&isreconnect);
        if(cnt==0)
        {
            std::cerr<<"user offline..."<<std::endl;
            break;
        }
        //上面的while循环是连接,下面的while是提供服务
        // while(true)
        // {
            std::string message;
            // 连接成功,可以直接发消息
            std::cout << "Please enter@" << std::endl;
            std::getline(std::cin, message);
            int n = write(sockfd, message.c_str(), message.size());
            if (n < 0)
            {
                std::cerr << "write error" << std::endl;
                break;
            }
            // 收到服务器消息
            char inbuffer[4096];
            n = read(sockfd, inbuffer, sizeof(inbuffer));
            if (n > 0)
            {
                inbuffer[n] = 0;
                std::cout << inbuffer << std::endl;
                break;
            }
            close(sockfd);
        }
    // }
    return 0;
}
Main.cc
#include"TcpServer.hpp"
#include<iostream>
#include<memory>
#include <pthread.h>

void Usage(std::string proc)
{
    std::cout << "\n\rUsage: " << proc << " port[1024+]\n" << std::endl;
};
//./tcpserver 8080
int main(int argc,char** argv)
{
    if(argc!=2)
    {
        Usage(argv[0]);
        exit(1);
    }
    uint16_t port=std::stoi(argv[1]);
    std::unique_ptr<TcpServer>tcp_svr(new TcpServer(port));
    tcp_svr->InitServer();
    tcp_svr->Start();
    return 0;
}
Task.hpp
#pragma once
#include <iostream>
#include <string>
#include<unistd.h>
#include <unordered_map>
#include"Log.hpp"
#include "Init.hpp"
extern Log lg;
Init init;
class Task
{
public:
    Task(int sockfd, const std::string &clientip, uint16_t &clientport) : sockfd_(sockfd), clientip_(clientip), clientport_(clientport)
    {
    }
    void run()
    {
        char buffer[4096];
        ssize_t n = read(sockfd_, buffer, sizeof(buffer));
        if (n > 0)
        {
            buffer[n] = 0;
            std::cout << "client key#" << buffer << std::endl;
            std::string echo_string=init.translation(buffer);
            write(sockfd_, echo_string.c_str(), echo_string.size()); // 再写回去
            if(n<0)
            {
                lg(Warning,"write error,errno:%d,errstring:%s",errno,strerror(errno));
            }
        }
        else if (n == 0)
        {
            // lg(Info,"%s:%d quit,server close sockfd:%d",clientip,clientport,sockfd);
            //break;
        }
        else
        {
            lg(Warning, "read Error...,sockfd:%d,client ip:%s,client port:%d", sockfd_, clientip_.c_str(), clientport_);
        }
        //只处理一次
        close(sockfd_);
    }

    void operator ()()
    {
        run();
    }
    
    ~Task()
    {
    }

private:
    int sockfd_;
    std::string clientip_;
    uint16_t clientport_;
};
Log.hpp
#pragma once

#include <iostream>
#include <time.h>
#include <stdarg.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>

#define SIZE 1024

#define Info 0
#define Debug 1
#define Warning 2
#define Error 3
#define Fatal 4

#define Screen 1
#define Onefile 2
#define Classfile 3

#define LogFile "log.txt"

class Log
{
public:
    Log()
    {
        printMethod = Screen;
        path = "./log/";
    }
    void Enable(int method)
    {
        printMethod = method;
    }
    std::string levelToString(int level)
    {
        switch (level)
        {
        case Info:
            return "Info";
        case Debug:
            return "Debug";
        case Warning:
            return "Warning";
        case Error:
            return "Error";
        case Fatal:
            return "Fatal";
        default:
            return "None";
        }
    }
    void printLog(int level, const std::string &logtxt)
    {
        switch (printMethod)
        {
        case Screen:
            std::cout << logtxt << std::endl;
            break;
        case Onefile:
            printOneFile(LogFile, logtxt);
            break;
        case Classfile:
            printClassFile(level, logtxt);
            break;
        default:
            break;
        }
    }
    void printOneFile(const std::string &logname, const std::string &logtxt)
    {
        std::string _logname = path + logname;
        int fd = open(_logname.c_str(), O_WRONLY | O_CREAT | O_APPEND, 0666); // "log.txt"
        if (fd < 0)
            return;
        write(fd, logtxt.c_str(), logtxt.size());
        close(fd);
    }
    void printClassFile(int level, const std::string &logtxt)
    {
        std::string filename = LogFile;
        filename += ".";
        filename += levelToString(level); // "log.txt.Debug/Warning/Fatal"
        printOneFile(filename, logtxt);
    }

    ~Log()
    {
    }
    void operator()(int level, const char *format, ...)
    {
        time_t t = time(nullptr);
        struct tm *ctime = localtime(&t);
        char leftbuffer[SIZE];
        snprintf(leftbuffer, sizeof(leftbuffer), "[%s][%d-%d-%d %d:%d:%d]", levelToString(level).c_str(),
                 ctime->tm_year + 1900, ctime->tm_mon + 1, ctime->tm_mday,
                 ctime->tm_hour, ctime->tm_min, ctime->tm_sec);

        va_list s;
        va_start(s, format);
        char rightbuffer[SIZE];
        vsnprintf(rightbuffer, sizeof(rightbuffer), format, s);
        va_end(s);

        // 格式:默认部分+自定义部分
        char logtxt[SIZE * 2];
        snprintf(logtxt, sizeof(logtxt), "%s %s", leftbuffer, rightbuffer);

        // printf("%s", logtxt); // 暂时打印
        printLog(level, logtxt);
    }

private:
    int printMethod;
    std::string path;
};
Init.hpp
#pragma once
#include<iostream>
#include<string>
#include <unordered_map>
#include <fstream>
#include "Log.hpp"
extern Log lg;
const std::string dictname="./dict.txt";
const std::string sep=":";
//apple:苹果
static bool Split(std::string &s,std::string *part1,std::string *part2)
{
    auto pos=s.find(sep);
    if(pos==std::string::npos) return false;
    *part1=s.substr(0,pos);
    *part2=s.substr(pos+1);//从pos到结尾
}
class Init
{
public:
    Init()
    {
        std::ifstream in(dictname);
        if(!in.is_open())
        {
            lg(Fatal,"ifstream open %s error",dictname.c_str());
            exit(1);
        }
        std::string line;
        while(std::getline(in,line))
        {
            std::string part1,part2;
            Split(line,&part1,&part2);
            dict.insert({part1,part2});
        }
        in.close();
    }
    std::string translation(const std::string &key)
    {
        auto iter=dict.find(key);
        if(iter==dict.end()) return  "Unknow";
        else return iter->second;
    }
private:
    std::unordered_map<std::string,std::string> dict;
};
Makefile
.PHONY:all
all:tcpserver tcpclient
tcpserver:Main.cc
	g++ -o $@ $^ -std=c++11 -lpthread
tcpclient:TcpClient.cc
	g++ -o $@ $^ -std=c++11
.PHONY:clean
clean:
	rm -f tcpserver tcpclient

4、理解前台进程和后台进程

在操作系统中,程序可以在两种模式下运行:前台后台

 4.1什么叫前台进程?

        前台进程是与用户直接交互的进程。当你在终端中运行程序时(例如,执行./process),它默认是在前台运行,这意味着它会占用终端并接收来自键盘的输入。这种进程会阻塞终端输入,直到进程结束,所以在它运行期间,像lspwd这样的命令通常不会有反应。

下面是一个简单的C++程序运行在前台的示例:

process.cc
#include<iostream>
#include<string>
#include<unistd.h>
int main()
{
    while(true)
    {
        std::cout<<"hello..."<<std::endl;
        sleep(1);
    }
    return 0;
}

 

4.2后台进程

有时,你可能希望程序在后台运行,以便不阻塞你的终端。这时你可以在命令后面加上符号&,例如: 

./process &

这样,程序就会在后台运行,你可以继续使用终端执行其他命令。

4.3切换前后台

如果需要将一个后台进程切换到前台,可以使用fg命令。例如fg 1会将后台任务列表中的第一个任务移至前台。  把后台进程提到前台   fg 1

 所以什么叫做前台,什么叫后台?

谁拥有键盘文件

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

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

相关文章

【Linux】通过crond服务设置定时执行shell脚本,实际执行时间却延迟了8小时

一、问题描述 通过使用crond服务设置定时任务&#xff0c;在每天凌晨的2:00执行脚本&#xff0c;但检查结果时发现&#xff0c;实际执行时间却在上午10点。 检查shell脚本执行结果发现&#xff0c;实际执行脚本时间在上午10:00&#xff0c;延迟了8小时。 检查系统时间&#xf…

Git基础笔记

目录 1.Git 常用命令 2.Git 分支操作 3.远程仓库操作 Git 概述 Git 是一个免费的、开源的 分布式版本控制系统 &#xff0c;可以快速高效地处理从小型到大型的各种 项目 1.Git 常用命令 1.设置用户签名 git config --global user.name 用户名 2.设置用户签名 git config…

PADS系列:绘制RTL8306原理图的过程

大家好&#xff0c;我是山羊君Goat。 在所有相关的元件都被创建到了原理图库之后&#xff0c;就可以正式开始原理图的绘制了。不过绘制过程中也是会按照一定的顺序来进行的&#xff0c;这样可以达到事半功倍的效果。 首先就是主芯片的放置&#xff0c;这里有三个主芯片&#x…

GCP Case:MountKirk Games

游戏后端 根据游戏活动动态放大或缩小。 连接到托管的nos0l数据库服务。 运行定制的linux发行版。 游戏分析平台 根据游戏活动来扩大或缩小规模直接处理来自游戏服务器的传入数据。 处理由于移动网络缓慢而迟到的数据。 通过sql查询来访问至少10tb的历史数据 处理由用户…

OpenCV相机标定与3D重建(10)眼标定函数calibrateHandEye()的使用

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 计算手眼标定&#xff1a; g T c _{}^{g}\textrm{T}_c g​Tc​ cv::calibrateHandEye 是 OpenCV 中用于手眼标定的函数。该函数通过已知的机器人…

【CSS in Depth 2 精译_072】第 12 章 CSS 排版与间距概述 + 12.1 间距设置(上):究竟该用 em 还是 px

当前内容所在位置&#xff08;可进入专栏查看其他译好的章节内容&#xff09; 第四部分 视觉增强技术 ✔️【第 12 章 CSS 排版与间距】 ✔️ 12.1 间距设置 ✔️ 12.1.1 使用 em 还是 px ✔️12.1.2 对行高的深入思考12.1.3 行内元素的间距设置 文章目录 第 12 章 排版与间距…

数据结构代码归纳

1.线性表 线性表的顺序表示 定义与初始化 typedef struct SqList{ElemType data[MaxSize];//ElemType *data 开动态数组 int length; }Sqlist; void InitList(SqList &L){L.length0;//若静态数组//若动态数组 //L.data(ElemType*)malloc(sizeof(ElemType)*MaxSize); }…

数据结构 (36)各种排序方法的综合比较

一、常见排序方法分类 插入排序类 直接插入排序&#xff1a;通过构建有序序列&#xff0c;对于未排序数据&#xff0c;在已排序序列中从后向前扫描&#xff0c;找到相应位置并插入。希尔排序&#xff1a;是插入排序的一种改进版本&#xff0c;先将整个待排序的记录序列分割成为…

SpringMVC全局异常处理

一、Java中的异常 定义&#xff1a;异常是程序在运行过程中出现的一些错误&#xff0c;使用面向对象思想把这些错误用类来描述&#xff0c;那么一旦产生一个错误&#xff0c;即创建某一个错误的对象&#xff0c;这个对象就是异常对象。 类型&#xff1a; 声明异常&#xff1…

【高中生讲机器学习】28. 集成学习之 Bagging 随机森林!

创建时间&#xff1a;2024-12-09 首发时间&#xff1a;2024-12-09 最后编辑时间&#xff1a;2024-12-09 作者&#xff1a;Geeker_LStar 嘿嘿&#xff0c;你好呀&#xff01;我又来啦~~ 前面我们讲完了集成学习之 Boooooosting&#xff0c;这篇我们来看看集成学习的另一个分支…

springSecurity权限控制

权限控制&#xff1a;不同的用户可以使用不同的功能。 我们不能在前端判断用户权限来控制显示哪些按钮&#xff0c;因为这样&#xff0c;有人会获取该功能对应的接口&#xff0c;就不需要通过前端&#xff0c;直接发送请求实现功能了。所以需要在后端进行权限判断。&#xff0…

李飞飞的生成式3D场景,对数字孪生的未来影响几何?

大家好&#xff0c;我是日拱一卒的攻城师不浪&#xff0c;致力于技术与艺术的融合。这是2024年输出的第47/100篇文章。 前言 这两天&#xff0c;AI界的教母李飞飞团队重磅发布了空间智能生成式AI大模型。 仅通过一张图片就能够生成一个可操作和交互的3D空间场景。 空间智能的…

意图识别模型使用 基于BERT的对话意图和槽位联合识别 CPU运行BERT模型-亲测成功

意图识别模型使用 基于BERT的对话意图和槽位联合识别 CPU运行BERT模型-亲测成功 我们在开发AI-Agent智能体时&#xff0c;通常会使用提示词工程设置场景的带入&#xff0c;在实际项目中会有很多场景&#xff0c;如果所有提示词都放一起就会超过Token限制&#xff0c;则不得不拆…

OSG开发笔记(三十七):OSG基于windows平台msvc2017x64编译器官方稳定版本OSG3.4.1搭建环境并移植Demo

​若该文为原创文章&#xff0c;未经允许不得转载 本文章博客地址&#xff1a;https://blog.csdn.net/qq21497936/article/details/144258047 各位读者&#xff0c;知识无穷而人力有穷&#xff0c;要么改需求&#xff0c;要么找专业人士&#xff0c;要么自己研究 长沙红胖子Qt…

《毛泽东思想和中国特色社会理论概述》课程报告Latex版本

所需要的图片: 源码(可运行): \documentclass[12pt]{article} \usepackage{ctex} \usepackage{graphicx} \usepackage{booktabs} \usepackage{titlesec} \usepackage{geometry} \usepackage{float} \usepackage{tabularx} \usepackage{enumitem} …

2024.12.6——攻防世界PHP2

知识点&#xff1a;目录扫描 代码审计 传参知识点补充&#xff1a; 后缀名为.phps的文件出现在无法使用web浏览器查看php源代码的情况下&#xff0c;.phps文件就是php的源代码文件&#xff0c;通常用于提供给用户&#xff08;访问者&#xff09;查看php代码&#xff0c;因为用…

OSI模型及各层缺陷

1&#xff0e;TCP/IP概述 &#xff08;1&#xff09;TCP/IP基本结构 TCP/IP是一组Internet协议&#xff0c;不但包括TCP和IP两个关键协议&#xff0c;还包括其他协议&#xff0c;如UDP、ARP、ICMP、Telnet和FTP等。TCP/IP的设计目标是使不同的网络互相连接&#xff0c;即实现互…

【论文阅读】Fifty Years of the ISCA: A Data-Driven Retrospective

学习体会&#xff1a; ISCA会议近五十年文章分析, 了解论文热点方向, 处理器依旧是热点! AI和并行是大趋势, 做XPU相关目前来说还是热点~ 摘录自原文 摘录: 数据来源和分析方法&#xff1a; 作者收集了 ACM 数字图书馆中所有 ISCA 论文&#xff0c;并使用 DBLP、Google Schol…

什么是MMD Maximum Mean Discrepancy 最大均值差异?

9多次在迁移学习看到了&#xff0c;居然还是Bernhard Schlkopf大佬的论文&#xff0c;仔细看看。 一.什么是MMD&#xff1f; 1. MMD要做什么&#xff1f; 判断两个样本&#xff08;族&#xff09;是不是来自于同一分布 2.怎么做&#xff1f;&#xff08;直观上&#xff09;…

LDR6500:音频双C支持,数字与模拟的完美结合

在当今数字化快速发展的时代&#xff0c;音频设备的兼容性和性能成为了用户关注的重点。LDR6500&#xff0c;作为乐得瑞科技精心研发的USB Power Delivery&#xff08;PD&#xff09;协议芯片&#xff0c;凭借其卓越的性能和广泛的应用兼容性&#xff0c;为音频设备领域带来了新…