网络编程套接字(3): 简单的TCP网络程序

news2024/7/4 6:15:09

文章目录

  • 网络编程套接字(3)
    • 4. 简单的TCP网络程序
      • 4.1 服务端创建
        • (1) 创建套接字
        • (2) 绑定端口
        • (3) 监听
        • (4) 获取新连接
        • (5) 处理读取与写入
      • 4.2 客户端创建
        • (1)连接服务器
      • 4.3 代码编写
        • (1) v1__简单发送消息
        • (2) v2_多进程版本
        • (3) v3_多线程版本
        • (4) v4_线程池版本

网络编程套接字(3)

4. 简单的TCP网络程序

4.1 服务端创建

(1) 创建套接字

还是之前udp部分的socket函数,这里只是简单说明一下与udp的差异

int socket(int domain, int type, int protocol);

只需将第二个参数type换成:
SOCK_STREAM: 基于TCP的网络通信,流式套接字,提供的是流式服务(对应TCP的特点:面向字节流)

(2) 绑定端口

还是和之前一样的接口

(3) 监听

UDP服务器的初始化操作只有2步,第一步:创建套接字,第二步:是绑定。但是TCP服务器是面向连接的,客户端在正式向TCP服务器发送数据之前,需要先与TCP服务器建立连接,然后才能与服务器进行通信。

因此TCP服务器需要时刻注意是否有客户端发来连接请求,此时就需要将TCP服务器创建的套接字设置为监听状态

在这里插入图片描述

listen for connections on a socket: 监听套接字上的连接

头文件:
			#include <sys/types.h>         
			#include <sys/socket.h>

函数原型:
			int listen(int sockfd, int backlog);

参数说明:
			第一个参数sockfd:  需要设置为监听状态的套接字对应的文件描述符
            第二个参数backlog: 这里当成一个整数,后续详细解释
                
返回值:
			监听成功: 返回0
            监听失败: 失败返回-1,并设置错误码

(4) 获取新连接

客户端有新链接到来,服务端可以获取到新链接,这一步需要死循环获取客户端新链接。

在这里插入图片描述

accept a connection on a socket: 接收套接字上的连接

头文件:
			#include <sys/types.h>         
			#include <sys/socket.h>

函数原型:
			int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

参数说明:
			第一个参数sockfd:  监听套接字
            第二个参数addr:    获取对方一端网络相关的属性信息
            第三个参数addrlen: addr的长度
                
返回值:
			连接成功: 返回接收到的套接字的文件描述符
            连接失败: 失败返回-1,并设置错误码

关于accept的返回值: 也是一个文件描述符

为什么又返回一个新的文件描述符??返回的这个新的文件描述符跟旧的文件描述符_sockfd有什么关系?

感性理解:

在这里插入图片描述

对比listen监听套接字与accept函数返回的套接字

  • listen监听套接字:用于获取客户端发来的连接请求。accept函数会不断从监听套接字当中获取新连接
  • accept函数返回的套接字:用于为本次accept获取到的连接提供服务。
  • 而listen监听套接字的任务只是不断获取新连接,而真正为这些连接提供服务的套接字是accept函数返回的套接字,而不是监听套接字。

(5) 处理读取与写入

因为TC 提供的是流式服务,所以这里利用read和write来实现读取与写入

4.2 客户端创建

4步:创建套接字,客户端向服务器发起连接请求,bind(不需要自己绑定,由OS自动分配),处理数据读取与写入

(1)连接服务器

在这里插入图片描述

initiate a connection on a socket: 在套接字上发起连接

头文件:
        #include <sys/types.h>
        #include <sys/socket.h>
 
函数原型:
        int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
 
参数说明:
        第一个参数sockfd: 表示通过该套接字发起连接请求
        第二个参数addr: 对方一端网络相关的属性信息
        第三个参数addrlen: addr的长度
 
返回值:
    	连接成功: 返回0
    	连接失败: 失败返回-1,并设置错误码

4.3 代码编写

这里一共提供4个版本的tcp代码

err.hpp:这个代码是公用的后续不在给出

#pragma once

enum
{
    USAGE_ERR=1,
    SOCKET_ERR,
    BIND_ERR,
    LISTEN_ERR,
    CONNECT_ERR,
};

(1) v1__简单发送消息

客户端向服务端发送消息,服务端收到后再把消息发回给客户端

tcpServer.hpp

#include<iostream>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<cstring>
#include<unistd.h>
#include<memory>
#include<functional>
#include"err.hpp"
using namespace std;

// 问题: 目前的服务器, 无法处理多个client的问题, 为什么?
// 单进程服务, 当服务端向客户端提供业务处理服务时, 没有办法accet, 不能处理连接

namespace ns_server
{
    static const uint16_t defaultport=8081;
    static int backlog=32;

    using func_t=function<string(const string&)>;   // 回调函数,一种处理逻辑

    class TcpServer
    {
    public:
        TcpServer(func_t func, uint16_t port=defaultport)
            :func_(func)
            ,port_(port)
            ,quit_(true)
        {

        }

        void InitServer()
        {
            // 1. 创建socket文件
            listensock_=socket(AF_INET,SOCK_STREAM,0);
            if(listensock_<0)
            {
                cerr<<"create socket error"<<endl;
                exit(SOCKET_ERR);
            }

            // 2. bind
            struct sockaddr_in local;
            memset(&local,0,sizeof(local));
            local.sin_port=htons(port_);
            local.sin_family=AF_INET;
            local.sin_addr.s_addr=INADDR_ANY;

            int n=bind(listensock_,(struct sockaddr*)&local,sizeof(local));
            if(n<0)
            {
                cerr<<"bind socket error"<<endl;
                exit(BIND_ERR);
            }

            // 3. 监听
            int m=listen(listensock_,backlog);
            if(m<0)
            {
                cerr<<"listen socket error"<<endl;
                exit(LISTEN_ERR);
            }
        }

        void Start()
        {
            quit_=false;

            while(!quit_)
            {
                struct sockaddr_in client;
                socklen_t len=sizeof(client);

                // 4. 获取连接, accept
                int sock=accept(listensock_,(struct sockaddr*)&client,&len);
                if (sock < 0)   // accept失败并不会终止进程, 只要获取下一个连接
                {
                    cerr << "accept error" << endl;
                    continue;
                }

                // 提取client信息   --- debug
                string clientip=inet_ntoa(client.sin_addr); // 把4字节对应的IP转化成字符串风格
                uint16_t clientport=ntohs(client.sin_port);  // 网络序列转主机序列

                // 5. 获取新连接成功后, 开始进行业务处理
                cout<<"获取新连接成功: "<<sock<<" from "<<listensock_<<", "<< clientip << "-				   " <<clientport<<endl;

                // v1
                service(sock,clientip,clientport);
            }
        }

        // 流式 - 利用read和write
        void service(int sock, const string&clientip,const uint16_t clientport)
        {
            string who=clientip + "-" + to_string(clientport);
            char buffer[1024];
            while(true)
            {
                ssize_t s=read(sock,buffer,sizeof(buffer)-1);
                if(s>0)
                {
                    buffer[s]=0;
                    string res=func_(buffer);     // 进行回调
                    cout<<who<< ">>> " <<res<<endl;

                    // 把收到的消息返回(写给客户端)
                    write(sock,res.c_str(),res.size());
                }
                else if(s==0)
                {
                    // 对方将连接关闭了
                    close(sock);
                    cout<< who <<" quit, me too"<<endl;
                    break;
                }
                else
                {
                    close(sock);
                    cerr<<"read error: "<<strerror(errno)<<endl;
                    break;
                }
            }
        }

        ~TcpServer()
        {
            
        }

    private:
        uint16_t port_;
        int listensock_;
        bool quit_;      // 标志服务器是否运行字段
        func_t func_;
    };
}

tcpServer.cc

#include"tcpServer.hpp"
using namespace ns_server;

// ./tcp_server port

// 使用手册
static void usage(string proc)
{
    cout<<"usage:\n\t"<<proc<<" port\n"<<endl;
}

string echo(const string&message)
{
    return message;
}

int main(int argc,char*argv[])
{
    if(argc!=2)
    {
        usage(argv[0]);
        exit(USAGE_ERR);
    }

    uint16_t port=atoi(argv[1]);
    unique_ptr<TcpServer> tsvr(new TcpServer(echo,port));

    tsvr->InitServer();
    tsvr->Start();

    return 0;
}

tcpClient.cc

#include<iostream>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<cstring>
#include<unistd.h>
#include<memory>
#include<functional>
#include"err.hpp"
using namespace std;

static void usage(string proc)
{
    cout<<"usage:\n\t"<<proc<<" serverip serverport\n" <<endl;
}

// ./tcp_client serverip serverport
int main(int argc,char*argv[])
{
    // 准备工作
    if(argc!=3)
    {
        usage(argv[0]);
        exit(USAGE_ERR);
    }

    string serverip=argv[1];
    uint16_t serverport=atoi(argv[2]);

    // 1.创建套接字
    int sock=socket(AF_INET,SOCK_STREAM,0);
    if (sock < 0)
    {
        cerr << "create socket error: " << strerror(errno) << endl;
        exit(SOCKET_ERR);
    }

    // (2) 客户端要不要bind呢? 要
    //     要不要自己bind呢? 不要, 因为client要让OS自动给用户进行bind
    // (3) 要不要listen?不要, 客户端连别人, 永远都是别人listen; 要不要accept?不要, 服务器来连接

    // 2. connect  客户端向服务器发起连接请求
    struct sockaddr_in server;
    memset(&server,0,sizeof(server));
    server.sin_port=htons(serverport);
    server.sin_family=AF_INET;
    // server.sin_addr.s_addr=INADDR_ANY;   绝对不是
    inet_aton(serverip.c_str(),&(server.sin_addr));    // 字符串风格ip转成点分十进制
    
    int cnt=5;

    while(connect(sock,(struct sockaddr*)&server,sizeof(server))!=0)   // 连接失败
    {
        sleep(1);
        cout<<"正在给你重连, 重连次数还有: "<<cnt--<<endl;
        if(cnt<=0)
            break;
    }
    if(cnt<=0)
    {
        cerr<<"连接失败"<<endl;
        exit(CONNECT_ERR);
    }

    char buffer[1024];
    // 3. 连接成功
    while(true)
    {
        string line;
        cout<<"Enter>> ";
        getline(cin,line);

        write(sock,line.c_str(),line.size());
        ssize_t s = read(sock, buffer, sizeof(buffer)-1);
        if (s > 0)
        {
            buffer[s] = 0;
            cout<<"server echo >>>"<<buffer<<endl;
        }
        else if (s == 0)
        {
            cerr << "server quit" << endl;
            break;
        }
        else
        {
            cerr << "read error: " << strerror(errno) << endl;
            break;
        }
    }

    close(sock);
    return 0;
}

运行结果:

在这里插入图片描述

(2) v2_多进程版本

v2版本是把单执行流服务器改成多进程版的服务器

  • 在accept获取新连接成功后,fork创建创建子进程,此时子进程对外提供服务, 父进程只进行accept

  • 父进程的文件描述符会被子进程继承,但并不是父子共用同一张文件描述符表,因为子进程会拷贝继承父进程的文件描述符表

  • 对于套接字文件也是相同的,父进程创建的子进程也会继承父进程的套接字文件,此时子进程就能够对特定的套接字文件进行读写操作,进而完成对对应客户端的服务

关于阻塞等待与非阻塞等待

  • 若采用阻塞式等待,那么服务端还是需要等待服务完当前客户端,才能继续获取下一个连接请求,此时服务端仍然是以一种串行的方式为客户端提供服务
  • 若采用非阻塞式等待,虽然在子进程为客户端提供服务期间服务端可以继续获取新连接,但此时服务端就需要将所有子进程的PID保存下来,并且需要不断花费时间检测子进程是否退出
  • 由此可见两种都有缺陷,所以我们可以考虑让服务端不等待子进程退出

常见的方式有两种:

  1. 捕捉SIGCHLD信号,将其处理动作设置为忽略。
  2. 让父进程创建子进程,子进程再创建孙子进程,子进程退出,让孙子进程为客户端提供服务,孙进程的回收工作由OS来承担

下面是创建孙进程的方案:

tcpServer.hpp

#include<iostream>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<cstring>
#include<unistd.h>
#include<memory>
#include<functional>
#include<sys/wait.h>
#include"err.hpp"
using namespace std;


namespace ns_server
{

    static const uint16_t defaultport=8081;
    static int backlog=32;

    using func_t=function<string(const string&)>;   // 回调函数,一种处理逻辑

    class TcpServer
    {
    public:
        TcpServer(func_t func, uint16_t port=defaultport)
            :func_(func)
            ,port_(port)
            ,quit_(true)
        {

        }

        void InitServer()
        {
            // 1. 创建socket文件
            listensock_=socket(AF_INET,SOCK_STREAM,0);
            if(listensock_<0)
            {
                cerr<<"create socket error"<<endl;
                exit(SOCKET_ERR);
            }

            // 2. bind
            struct sockaddr_in local;
            memset(&local,0,sizeof(local));
            local.sin_port=htons(port_);
            local.sin_family=AF_INET;
            local.sin_addr.s_addr=INADDR_ANY;

            int n=bind(listensock_,(struct sockaddr*)&local,sizeof(local));
            if(n<0)
            {
                cerr<<"bind socket error"<<endl;
                exit(BIND_ERR);
            }

            // 3. 监听
            int m=listen(listensock_,backlog);
            if(m<0)
            {
                cerr<<"listen socket error"<<endl;
                exit(LISTEN_ERR);
            }
        }

        void Start()
        {
            // signal(SIGCHLD,SIG_IGN);  // ok, 最推荐
            // signal(SIGCHLD,handler);  // 回收子进程, 不太推荐

            quit_=false;

            while(!quit_)
            {
                struct sockaddr_in client;
                socklen_t len=sizeof(client);

                // 4. 获取连接, accept
                int sock=accept(listensock_,(struct sockaddr*)&client,&len);
                if (sock < 0)   // accept失败并不会终止进程, 只要获取下一个连接
                {
                    cerr << "accept error" << endl;
                    continue;
                }

                // 提取client信息   --- debug
                string clientip=inet_ntoa(client.sin_addr); // 把4字节对应的IP转化成字符串风格
                uint16_t clientport=ntohs(client.sin_port);  // 网络序列转主机序列

                // 5. 获取新连接成功后, 开始进行业务处理
                cout<<"获取新连接成功: "<<sock<<" from "<<listensock_<<", "<< clientip << "-                 " <<clientport<<endl;

                // v2: 多进程版本
                // 子进程对外提供服务, 父进程只进行accept

                pid_t id=fork();
                if(id<0)
                {
                    close(sock);
                    continue;
                }
                else if(id==0) // child, 父进程的fd会被子进程继承吗? 会; 父子会用同一张文件描述符表吗?不会, 子进程会拷贝继承父进程的fd table
                {
                    // 建议关闭掉不需要的fd
                    close(listensock_);

                    if(fork()>0) exit(0);   // 就这一行代码

                    // 子进程已经退了(则下面的wait立马返回, 回收子进程资源), 孙子进程在运行(无父进程, 变成孤儿进程, 被系统领养),提供服务
                    // 孙子进程的回收工作由系统来承担
                    service(sock,clientip,clientport);
                    exit(0);
                }
                
                // 父进程, 一定要关闭不需要的fd(否则会导致父进程的文件描述符变少, 即父进程文件描述符资源的浪费[文件描述符泄露])
                close(sock);

                // 不等待子进程, 会导致子进程僵尸之后无法回收, 近而导致内存泄漏
                pid_t ret=waitpid(id,nullptr,0);    // 父进程默认是阻塞的, waitpid(id,nullptr,WNOHANG);不推荐
                if(ret==id)
                    cout<< "wait child "<<id<< " success" <<endl;

            }
        }

        // 流式 - 利用read和write
        void service(int sock, const string&clientip,const uint16_t clientport)
        {
            string who=clientip + "-" + to_string(clientport);
            char buffer[1024];
            while(true)
            {
                ssize_t s=read(sock,buffer,sizeof(buffer)-1);
                if(s>0)
                {
                    buffer[s]=0;
                    string res=func_(buffer);     // 进行回调
                    cout<<who<< ">>> " <<res<<endl;

                    // 把收到的消息返回(写给客户端)
                    write(sock,res.c_str(),res.size());
                }
                else if(s==0)
                {
                    // 对方将连接关闭了
                    close(sock);
                    cout<< who <<" quit, me too"<<endl;
                    break;
                }
                else
                {
                    close(sock);
                    cerr<<"read error: "<<strerror(errno)<<endl;
                    break;
                }
            }
        }

         ~TcpServer()
        {
            
        }

    private:
        uint16_t port_;
        int listensock_;
        bool quit_;      // 标志服务器是否运行字段
        func_t func_;
    };
}

tcpServer.cc

#include"tcpServer.hpp"
using namespace ns_server;

// ./tcp_server port

// 使用手册
static void usage(string proc)
{
    cout<<"usage:\n\t"<<proc<<" port\n"<<endl;
}

string echo(const string&message)
{
    return message;
}

int main(int argc,char*argv[])
{
    if(argc!=2)
    {
        usage(argv[0]);
        exit(USAGE_ERR);
    }

    uint16_t port=atoi(argv[1]);
    unique_ptr<TcpServer> tsvr(new TcpServer(echo,port));

    tsvr->InitServer();
    tsvr->Start();

    return 0;
}

tcpClient.cc

#include<iostream>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<cstring>
#include<unistd.h>
#include<memory>
#include<functional>
#include"err.hpp"
using namespace std;

static void usage(string proc)
{
    cout<<"usage:\n\t"<<proc<<" serverip serverport\n" <<endl;
}

// ./tcp_client serverip serverport
int main(int argc,char*argv[])
{
    // 准备工作
    if(argc!=3)
    {
        usage(argv[0]);
        exit(USAGE_ERR);
    }

    string serverip=argv[1];
    uint16_t serverport=atoi(argv[2]);

    // 1.创建套接字
    int sock=socket(AF_INET,SOCK_STREAM,0);
    if (sock < 0)
    {
        cerr << "create socket error: " << strerror(errno) << endl;
        exit(SOCKET_ERR);
    }

    // (2) 客户端要不要bind呢? 要
    //     要不要自己bind呢? 不要, 因为client要让OS自动给用户进行bind
    // (3) 要不要listen?不要, 客户端连别人, 永远都是别人listen; 要不要accept?不要, 服务器来连接

    // 2. connect  客户端向服务器发起连接请求
    struct sockaddr_in server;
    memset(&server,0,sizeof(server));
    server.sin_port=htons(serverport);
    server.sin_family=AF_INET;
    // server.sin_addr.s_addr=INADDR_ANY;   绝对不是
    inet_aton(serverip.c_str(),&(server.sin_addr));    // 字符串风格ip转成点分十进制
    
    int cnt=5;

    while(connect(sock,(struct sockaddr*)&server,sizeof(server))!=0)   // 连接失败
    {
        sleep(1);
        cout<<"正在给你重连, 重连次数还有: "<<cnt--<<endl;
        if(cnt<=0)
            break;
    }
    if(cnt<=0)
    {
        cerr<<"连接失败"<<endl;
        exit(CONNECT_ERR);
    }

    char buffer[1024];
    // 3. 连接成功
    while(true)
    {
        string line;
        cout<<"Enter>> ";
        getline(cin,line);

        write(sock,line.c_str(),line.size());
        ssize_t s = read(sock, buffer, sizeof(buffer)-1);
        if (s > 0)
        {
            buffer[s] = 0;
            cout<<"server echo >>>"<<buffer<<endl;
        }
        else if (s == 0)
        {
            cerr << "server quit" << endl;
            break;
        }
        else
        {
            cerr << "read error: " << strerror(errno) << endl;
            break;
        }
    }

    close(sock);
    return 0;
}

运行结果:

在这里插入图片描述

(3) v3_多线程版本

频繁的创建进程会给OS带来巨大的负担,并且创建线程的成本比创建线程高得多。因此在实现多执行流的服务器时最好采用多线程进行实现。

主线程创建出新线程后,也是需要等待新线程退出的,否则也会造成类似于僵尸进程这样的问题。但对于线程来说,如果不想让主线程等待新线程退出,直接线程分离即可,当这个线程退出时系统会自动回收该线程所对应的资源。

各个线程共享是同一张文件描述符表,也就是说服务进程(主线程)调用accept函数获取到一个文件描述符后,其他创建的新线程是能够直接访问这个文件描述符的。

所以不能关闭不要的套接字文件描述符,该文件描述符的关闭操作应该又新线程来执行。因为是新线程为客户端提供服务的,只有当新线程为客户端提供的服务结束后才能将该文件描述符关闭。

tcpServer.hpp

#include<iostream>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<cstring>
#include<unistd.h>
#include<memory>
#include<functional>
#include<sys/wait.h>
#include<pthread.h>
#include"err.hpp"
using namespace std;

namespace ns_server
{

    static const uint16_t defaultport=8081;
    static int backlog=32;

    using func_t=function<string(const string&)>;   // 回调函数,一种处理逻辑

    class TcpServer;

    class ThreadData
    {
    public:
        ThreadData(int fd, const string&ip,const uint16_t&port,TcpServer*ts)
            :sock(fd)
            ,clientip(ip)
            ,clientport(port)
            ,current(ts)
        {

        }

    public:
        int sock;
        string clientip;
        uint16_t clientport;
        TcpServer*current;
    };


    class TcpServer
    {
    public:
        TcpServer(func_t func, uint16_t port=defaultport)
            :func_(func)
            ,port_(port)
            ,quit_(true)
        {

        }

        void InitServer()
        {
            // 1. 创建socket文件
            listensock_=socket(AF_INET,SOCK_STREAM,0);
            if(listensock_<0)
            {
                cerr<<"create socket error"<<endl;
                exit(SOCKET_ERR);
            }

            // 2. bind
            struct sockaddr_in local;
            memset(&local,0,sizeof(local));
            local.sin_port=htons(port_);
            local.sin_family=AF_INET;
            local.sin_addr.s_addr=INADDR_ANY;

            int n=bind(listensock_,(struct sockaddr*)&local,sizeof(local));
            if(n<0)
            {
                cerr<<"bind socket error"<<endl;
                exit(BIND_ERR);
            }

            // 3. 监听
            int m=listen(listensock_,backlog);
            if(m<0)
            {
                cerr<<"listen socket error"<<endl;
                exit(LISTEN_ERR);
            }
        }

        void Start()
        {
            // signal(SIGCHLD,SIG_IGN);  // ok, 最推荐
            // signal(SIGCHLD,handler);  // 回收子进程, 不太推荐

            quit_=false;

            while(!quit_)
            {
                struct sockaddr_in client;
                socklen_t len=sizeof(client);

                // 4. 获取连接, accept
                int sock=accept(listensock_,(struct sockaddr*)&client,&len);
                if (sock < 0)   // accept失败并不会终止进程, 只要获取下一个连接
                {
                    cerr << "accept error" << endl;
                    continue;
                }

                // 提取client信息   --- debug
                string clientip=inet_ntoa(client.sin_addr); // 把4字节对应的IP转化成字符串风格
                uint16_t clientport=ntohs(client.sin_port);  // 网络序列转主机序列

                // 5. 获取新连接成功后, 开始进行业务处理
                cout<<"获取新连接成功: "<<sock<<" from "<<listensock_<<", "<< clientip << "-                " <<clientport<<endl;

                // v3: 多线程版本 --- 原生多线程
                // 1. 要不要关闭不要的socket?  绝对不能,一个进程的文件描述符表共享, 关了影响其他线程
                // 2. 要不要回收线程?要;如何回收?会不会阻塞

                pthread_t tid;
                ThreadData*td=new ThreadData(sock,clientip,clientport,this);    // 要开出一块独立的空间
                pthread_create(&tid,nullptr,threadRoutine,td);
            }
        }

        static void*threadRoutine(void*args)
        {
            pthread_detach(pthread_self());

            ThreadData*td=static_cast<ThreadData*>(args);
            td->current->service(td->sock,td->clientip,td->clientport);
            delete td;
        }

        // 流式 - 利用read和write
        void service(int sock, const string&clientip,const uint16_t clientport)
        {
            string who=clientip + "-" + to_string(clientport);
            char buffer[1024];
            while(true)
            {
                ssize_t s=read(sock,buffer,sizeof(buffer)-1);
                if(s>0)
                {
                    buffer[s]=0;
                    string res=func_(buffer);     // 进行回调
                    cout<<who<< ">>> " <<res<<endl;

                    // 把收到的消息返回(写给客户端)
                    write(sock,res.c_str(),res.size());
                }
                else if(s==0)
                {
                    // 对方将连接关闭了
                    close(sock);
                    cout<< who <<" quit, me too"<<endl;
                    break;
                }
                else
                {
                    close(sock);
                    cerr<<"read error: "<<strerror(errno)<<endl;
                    break;
                }
            }
        }

        ~TcpServer()
        {
            
        }

    private:
        uint16_t port_;
        int listensock_;
        bool quit_;      // 标志服务器是否运行字段
        func_t func_;
    };
}

tcpServer.cc

#include"tcpServer.hpp"
using namespace ns_server;

// ./tcp_server port

// 使用手册
static void usage(string proc)
{
    cout<<"usage:\n\t"<<proc<<" port\n"<<endl;
}

string echo(const string&message)
{
    return message;
}

int main(int argc,char*argv[])
{
    if(argc!=2)
    {
        usage(argv[0]);
        exit(USAGE_ERR);
    }

    uint16_t port=atoi(argv[1]);
    unique_ptr<TcpServer> tsvr(new TcpServer(echo,port));

    tsvr->InitServer();
    tsvr->Start();

    return 0;
}

tcpClient.cc

#include<iostream>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<cstring>
#include<unistd.h>
#include<memory>
#include<functional>
#include"err.hpp"
using namespace std;


static void usage(string proc)
{
    cout<<"usage:\n\t"<<proc<<" serverip serverport\n" <<endl;
}

// ./tcp_client serverip serverport
int main(int argc,char*argv[])
{
    // 准备工作
    if(argc!=3)
    {
        usage(argv[0]);
        exit(USAGE_ERR);
    }

    string serverip=argv[1];
    uint16_t serverport=atoi(argv[2]);

    // 1.创建套接字
    int sock=socket(AF_INET,SOCK_STREAM,0);
    if (sock < 0)
    {
        cerr << "create socket error: " << strerror(errno) << endl;
        exit(SOCKET_ERR);
    }


    // (2) 客户端要不要bind呢? 要
    //     要不要自己bind呢? 不要, 因为client要让OS自动给用户进行bind
    // (3) 要不要listen?不要, 客户端连别人, 永远都是别人listen; 要不要accept?不要, 服务器来连接

    // 2. connect  客户端向服务器发起连接请求
    struct sockaddr_in server;
    memset(&server,0,sizeof(server));
    server.sin_port=htons(serverport);
    server.sin_family=AF_INET;
    // server.sin_addr.s_addr=INADDR_ANY;   绝对不是
    inet_aton(serverip.c_str(),&(server.sin_addr));    // 字符串风格ip转成点分十进制
    
    int cnt=5;

    while(connect(sock,(struct sockaddr*)&server,sizeof(server))!=0)   // 连接失败
    {
        sleep(1);
        cout<<"正在给你重连, 重连次数还有: "<<cnt--<<endl;
        if(cnt<=0)
            break;
    }
    if(cnt<=0)
    {
        cerr<<"连接失败"<<endl;
        exit(CONNECT_ERR);
    }

    char buffer[1024];
    // 3. 连接成功
    while(true)
    {
        string line;
        cout<<"Enter>> ";
        getline(cin,line);

        write(sock,line.c_str(),line.size());
        ssize_t s = read(sock, buffer, sizeof(buffer)-1);
        if (s > 0)
        {
            buffer[s] = 0;
            cout<<"server echo >>>"<<buffer<<endl;
        }
        else if (s == 0)
        {
            cerr << "server quit" << endl;
            break;
        }
        else
        {
            cerr << "read error: " << strerror(errno) << endl;
            break;
        }
    }

    close(sock);
    return 0;
}

运行结果:

在这里插入图片描述

(4) v4_线程池版本

多线程版的问题:

  • 每当有新连接到来时,服务端的主线程都会为该客户端创建提供服务的新线程,当服务结束时就会将新线程销毁,这样做既麻烦又效率低下,每当有新连接到来才开始创建提供服务的新线程
  • 若有大量的客户端请求,此时服务端要为每一个客户端创建对应的服务线程。计算机中的线程越多,CPU的压力越大

线程池

  • 在服务端预先创建一批线程,当有客户端请求连接时就让这些线程为客户端提供服务,此时客户端一来就有线程为其提供服务,而不是当客户端来了才创建对应的服务线程(减少了频繁创建线程的开销)
  • 当某个线程为客户端提供完服务后,不要让该线程退出,而是让该线程继续为下一个客户端提供服务,如果当前没有客户端连接请求,则可以让该线程先进入休眠状态,当有客户端连接到来时再将该线程唤醒。
  • 服务端创建的这一批线程的数量不能太多,此时CPU的压力也就不会太大

task.hpp

#pragma once
#include<iostream>
#include<string>
#include<unistd.h>
#include<string.h>
#include<functional>
using namespace std;

using cb_t=function<void(int sock, const string&,const uint16_t&)>;


class Task
{
public:
    Task()
    {

    }

    Task(int sock, const string& ip,const uint16_t&port,cb_t cb)
        :_sock(sock)
        ,_ip(ip)
        ,_port(port)
        ,_cb(cb)
    {

    }

    void operator()()
    {
        _cb(_sock,_ip,_port);
    }

    ~Task()
    {

    }

private:
    
    int _sock;
    string _ip;
    uint16_t _port;
    cb_t _cb;
};

LockGuard.hpp

#include<iostream>
#include<pthread.h>
using namespace std;

class Mutex   //自己不维护锁,由外部传入
{
public:
    Mutex(pthread_mutex_t* mutex)
        :_pmutex(mutex)
    {

    }

    void lock()
    {
        pthread_mutex_lock(_pmutex);
    }

    void unlock()
    {
        pthread_mutex_unlock(_pmutex);
    }

    ~Mutex()
    {}

private:
    pthread_mutex_t* _pmutex;   //锁的指针
};

class LockGuard  //自己不维护锁,由外部传入
{
public:
    LockGuard(pthread_mutex_t* mutex)
        :_mutex(mutex)
    {
        _mutex.lock();
    }

    ~LockGuard()
    {
        _mutex.unlock();
    }

private:
    Mutex _mutex;   //锁的指针
};

thread.hpp

#include<iostream>
#include<string>
using namespace std;

class Thread
{
public:

    typedef enum
    {
        NEW=0,
        RUNNING,
        EXITED
    }ThreadStatus;

    typedef void (*func_t)(void*);      //函数指针, 参数是void*


    Thread(int num, func_t func, void*args)
        :_tid(0)
        ,_status(NEW)
        ,_func(func)
        ,_args(args)
    {
        char name[128];
        snprintf(name,sizeof(name),"thread-%d",num);
        _name=name;
    }


    int status() {return _status;}

    string threadname() {return _name;}

    pthread_t thread_id()
    {
        if(_status==RUNNING)
            return _tid;
        else
            return 0;
    }

    // runHelper是不是类的成员函数, 而类的成员函数, 具有默认参数this, 需要static
    // void*runHelper(Thread*this, void*args) , 而pthread_create要求传的参数必须是: void*的, 即参数不匹配
    // 但是static会有新的问题: static成员函数, 无法直接访问类属性和其他成员函数
    static void*runHelper(void*args)
    {
        Thread*ts=(Thread*)args;   //就拿到了当前对象
        // _func(_args);
        (*ts)();
    }

    //仿函数
    void operator()()
    {
        _func(_args);
    }

    void run()
    {
        int n=pthread_create(&_tid,nullptr,runHelper,this);  //this: 是当前线程对象Thread
        if(n!=0) exit(-1);
        _status=RUNNING;
    }

    void join()
    {
        int n=pthread_join(_tid,nullptr);
        if(n!=0)
        {
            cerr<<" main thread join thread "<< _name << " error "<<endl;
        }
        _status=EXITED;
    }


    ~Thread()
    {}

private:
    pthread_t _tid;
    string _name;
    func_t _func;  //线程未来要执行的回调
    void*_args;     //调用回调函数时的参数
    ThreadStatus _status;
};

threadPool_v4.hpp

#include<iostream>
#include<memory>
#include<vector>
#include<queue>
#include<unistd.h>
#include"thread.hpp"
#include"lockGuard.hpp"
using namespace std;

const static int N=5;

template<class T>
class threadPool
{
public:

    pthread_mutex_t* getlock()
    {
        return &_lock;
    }

    void threadWait()
    {
       pthread_cond_wait(&_cond,&_lock);
    }

    void threadWakeup()
    {
        pthread_cond_signal(&_cond);  // 唤醒在条件变量下等待的线程
    }

    bool isEmpty()
    {
        return _tasks.empty();
    }


    T popTask()
    {
        T t=_tasks.front();
        _tasks.pop();
        return t;
    }


    static void threadRoutine(void*args)      
    {
       
        threadPool<T>*tp=static_cast<threadPool<T>*>(args);
        while(true)
        {
            // 1. 检测有没有任务 --- 本质是看队列是否为空
            // --- 本质就是在访问共享资源  --- 必定加锁
            // 2. 有: 处理
            // 3. 无: 等待
            // 细节: 必定加锁
            T t;
            {
                LockGuard lockguard(tp->getlock());
                while (tp->isEmpty())
                {
                    // 等待, 在条件变量下等待
                    tp->threadWait();
                }
                t = tp->popTask(); // 把任务从公共区域拿到私有区域
            }
            // for test

            // 处理任务应不应该在临界区中处理, 不应该, 这是线程自己私有的事情
            t();   
        }
    }

    static threadPool<T> * getinstance()
    {

        if (instance == nullptr)  // 为什么要这样? 提高效率, 减少加锁的次数
        {
            LockGuard lockguard(&instance_lock);

            if (instance == nullptr)
            {
                cout<<"线程池单例形成"<<endl;
                instance = new threadPool<T>();
                instance->init();
                instance->start();
            }
        }

        return instance;
    }


    void init()
    {
         for(int i=0;i<_num;++i)
        {
            _threads.push_back(Thread(i,threadRoutine,this));
            cout<<i<<" thread running"<<endl;
        }
    }

    void check()
    {
        for(auto&t:_threads)
        {
           cout<<t.threadname()<<" running..."<<endl;
        }
    }

    void start()
    {
        for(auto&t:_threads)
        {
            t.run();
        }
    }

    void pushTask(const T&t)
    {
        LockGuard lockguard(&_lock);
        _tasks.push(t);
        threadWakeup();
    }

    ~threadPool()
    {
        for(auto&t:_threads)
        {
            t.join();
        }
        pthread_mutex_destroy(&_lock);
        pthread_cond_destroy(&_cond);
    }

private:
    threadPool(int num=N)
        :_num(num)
    {
        pthread_mutex_init(&_lock,nullptr);
        pthread_cond_init(&_cond,nullptr);
    }

    threadPool(const threadPool<T>&tp)=delete;

    void operator=(const threadPool<T>&tp)=delete;

private:
    vector<Thread> _threads;   
    int _num;                     

    queue<T> _tasks;               

    pthread_mutex_t _lock;
    pthread_cond_t  _cond;

    static threadPool<T>*instance;

    static pthread_mutex_t instance_lock;
};

template<class T>
threadPool<T> * threadPool<T>::instance=nullptr;

template<class T>
pthread_mutex_t  threadPool<T>::instance_lock=PTHREAD_MUTEX_INITIALIZER;

tcpServer.hpp

#include<iostream>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<cstring>
#include<unistd.h>
#include<memory>
#include<functional>
#include<sys/wait.h>
#include<pthread.h>
#include"err.hpp"
#include"threadPool_v4.hpp"
#include"task.hpp"
using namespace std;


namespace ns_server
{

    static const uint16_t defaultport=8081;
    static int backlog=32;

    using func_t=function<string(const string&)>;   // 回调函数,一种处理逻辑

    class TcpServer;

    class ThreadData
    {
    public:
        ThreadData(int fd, const string&ip,const uint16_t&port,TcpServer*ts)
            :sock(fd)
            ,clientip(ip)
            ,clientport(port)
            ,current(ts)
        {

        }

    public:
        int sock;
        string clientip;
        uint16_t clientport;
        TcpServer*current;
    };


    class TcpServer
    {
    public:
        TcpServer(func_t func, uint16_t port=defaultport)
            :func_(func)
            ,port_(port)
            ,quit_(true)
        {

        }

        void InitServer()
        {
            // 1. 创建socket文件
            listensock_=socket(AF_INET,SOCK_STREAM,0);
            if(listensock_<0)
            {
                cerr<<"create socket error"<<endl;
                exit(SOCKET_ERR);
            }

            // 2. bind
            struct sockaddr_in local;
            memset(&local,0,sizeof(local));
            local.sin_port=htons(port_);
            local.sin_family=AF_INET;
            local.sin_addr.s_addr=INADDR_ANY;

            int n=bind(listensock_,(struct sockaddr*)&local,sizeof(local));
            if(n<0)
            {
                cerr<<"bind socket error"<<endl;
                exit(BIND_ERR);
            }

            // 3. 监听
            int m=listen(listensock_,backlog);
            if(m<0)
            {
                cerr<<"listen socket error"<<endl;
                exit(LISTEN_ERR);
            }
        }

        void Start()
        {
            quit_=false;

            while(!quit_)
            {
                struct sockaddr_in client;
                socklen_t len=sizeof(client);

                // 4. 获取连接, accept
                int sock=accept(listensock_,(struct sockaddr*)&client,&len);
                if (sock < 0)   // accept失败并不会终止进程, 只要获取下一个连接
                {
                    cerr << "accept error" << endl;
                    continue;
                }

                // 提取client信息   --- debug
                string clientip=inet_ntoa(client.sin_addr); // 把4字节对应的IP转化成字符串风格
                uint16_t clientport=ntohs(client.sin_port);  // 网络序列转主机序列

                // 5. 获取新连接成功后, 开始进行业务处理
                cout<<"获取新连接成功: "<<sock<<" from "<<listensock_<<", "<< clientip << "-                 " <<clientport<<endl;

                // v4: 线程池版本 
                //  一旦用户来了,你才创建线程, 线程池吗

                // 使用线程池的时候, 一定是有限的线程个数, 一定要处理短任务
                Task t(sock,clientip,clientport, bind(&TcpServer::service,                                      this,placeholders::_1,placeholders::_2,placeholders::_3));
                threadPool<Task>::getinstance()->pushTask(t);
            }
        }

        static void*threadRoutine(void*args)
        {
            pthread_detach(pthread_self());

            ThreadData*td=static_cast<ThreadData*>(args);
            td->current->service(td->sock,td->clientip,td->clientport);
            delete td;
        }

        // 流式 - 利用read和write
        void service(int sock, const string&clientip,const uint16_t clientport)
        {
            string who=clientip + "-" + to_string(clientport);
            char buffer[1024];

            ssize_t s = read(sock, buffer, sizeof(buffer) - 1);
            if (s > 0)
            {
                buffer[s] = 0;
                string res = func_(buffer); // 进行回调
                cout << who << ">>> " << res << endl;

                // 把收到的消息返回(写给客户端)
                write(sock, res.c_str(), res.size());
            }
            else if (s == 0)
            {
                cout << who << " quit, me too" << endl;
            }
            else
            {
                close(sock);
                cerr << "read error: " << strerror(errno) << endl;
            }

            close(sock);
        }

        ~TcpServer()
        {
            
        }

    private:
        uint16_t port_;
        int listensock_;
        bool quit_;      // 标志服务器是否运行字段
        func_t func_;
    };
}

tcpServer.cc

#include"tcpServer.hpp"
using namespace ns_server;

// ./tcp_server port

// 使用手册
static void usage(string proc)
{
    cout<<"usage:\n\t"<<proc<<" port\n"<<endl;
}


string echo(const string&message)
{
    return message;
}

int main(int argc,char*argv[])
{
    if(argc!=2)
    {
        usage(argv[0]);
        exit(USAGE_ERR);
    }

    uint16_t port=atoi(argv[1]);
    unique_ptr<TcpServer> tsvr(new TcpServer(echo,port));

    tsvr->InitServer();
    tsvr->Start();

    return 0;
}

tcpClient.cc

#include<iostream>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<cstring>
#include<unistd.h>
#include<memory>
#include<functional>
#include"err.hpp"
using namespace std;


static void usage(string proc)
{
    cout<<"usage:\n\t"<<proc<<" serverip serverport\n" <<endl;
}

// ./tcp_client serverip serverport
int main(int argc,char*argv[])
{
    // 准备工作
    if(argc!=3)
    {
        usage(argv[0]);
        exit(USAGE_ERR);
    }

    string serverip=argv[1];
    uint16_t serverport=atoi(argv[2]);

    // 1.创建套接字
    int sock=socket(AF_INET,SOCK_STREAM,0);
    if (sock < 0)
    {
        cerr << "create socket error: " << strerror(errno) << endl;
        exit(SOCKET_ERR);
    }

    // (2) 客户端要不要bind呢? 要
    //     要不要自己bind呢? 不要, 因为client要让OS自动给用户进行bind
    // (3) 要不要listen?不要, 客户端连别人, 永远都是别人listen; 要不要accept?不要, 服务器来连接

    // 2. connect  客户端向服务器发起连接请求
    struct sockaddr_in server;
    memset(&server,0,sizeof(server));
    server.sin_port=htons(serverport);
    server.sin_family=AF_INET;
    // server.sin_addr.s_addr=INADDR_ANY;   绝对不是
    inet_aton(serverip.c_str(),&(server.sin_addr));    // 字符串风格ip转成点分十进制
    
    int cnt=5;

    while(connect(sock,(struct sockaddr*)&server,sizeof(server))!=0)   // 连接失败
    {
        sleep(1);
        cout<<"正在给你重连, 重连次数还有: "<<cnt--<<endl;
        if(cnt<=0)
            break;
    }
    if(cnt<=0)
    {
        cerr<<"连接失败"<<endl;
        exit(CONNECT_ERR);
    }

    char buffer[1024];
    // 3. 连接成功
    while(true)
    {
        string line;
        cout<<"Enter>> ";
        getline(cin,line);

        write(sock,line.c_str(),line.size());
        ssize_t s = read(sock, buffer, sizeof(buffer)-1);
        if (s > 0)
        {
            buffer[s] = 0;
            cout<<"server echo >>>"<<buffer<<endl;
        }
        else if (s == 0)
        {
            cerr << "server quit" << endl;
            break;
        }
        else
        {
            cerr << "read error: " << strerror(errno) << endl;
            break;
        }
    }

    close(sock);
    return 0;
}

运行结果:

ip serverport\n" <<endl;
}

// ./tcp_client serverip serverport
int main(int argc,char*argv[])
{
// 准备工作
if(argc!=3)
{
usage(argv[0]);
exit(USAGE_ERR);
}

string serverip=argv[1];
uint16_t serverport=atoi(argv[2]);

// 1.创建套接字
int sock=socket(AF_INET,SOCK_STREAM,0);
if (sock < 0)
{
    cerr << "create socket error: " << strerror(errno) << endl;
    exit(SOCKET_ERR);
}

// (2) 客户端要不要bind呢? 要
//     要不要自己bind呢? 不要, 因为client要让OS自动给用户进行bind
// (3) 要不要listen?不要, 客户端连别人, 永远都是别人listen; 要不要accept?不要, 服务器来连接

// 2. connect  客户端向服务器发起连接请求
struct sockaddr_in server;
memset(&server,0,sizeof(server));
server.sin_port=htons(serverport);
server.sin_family=AF_INET;
// server.sin_addr.s_addr=INADDR_ANY;   绝对不是
inet_aton(serverip.c_str(),&(server.sin_addr));    // 字符串风格ip转成点分十进制

int cnt=5;

while(connect(sock,(struct sockaddr*)&server,sizeof(server))!=0)   // 连接失败
{
    sleep(1);
    cout<<"正在给你重连, 重连次数还有: "<<cnt--<<endl;
    if(cnt<=0)
        break;
}
if(cnt<=0)
{
    cerr<<"连接失败"<<endl;
    exit(CONNECT_ERR);
}

char buffer[1024];
// 3. 连接成功
while(true)
{
    string line;
    cout<<"Enter>> ";
    getline(cin,line);

    write(sock,line.c_str(),line.size());
    ssize_t s = read(sock, buffer, sizeof(buffer)-1);
    if (s > 0)
    {
        buffer[s] = 0;
        cout<<"server echo >>>"<<buffer<<endl;
    }
    else if (s == 0)
    {
        cerr << "server quit" << endl;
        break;
    }
    else
    {
        cerr << "read error: " << strerror(errno) << endl;
        break;
    }
}

close(sock);
return 0;

}


运行结果:

![在这里插入图片描述](https://img-blog.csdnimg.cn/eadac05d3a6c43dbb8c5e44f9ccebca6.png)

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

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

相关文章

博客系统后端(项目系列2)

目录 前言 &#xff1a; 1.准备工作 1.1创建项目 1.2引入依赖 1.3创建必要的目录 2.数据库设计 2.1博客数据 2.2用户数据 3.封装数据库 3.1封装数据库的连接操作 3.2创建两个表对应的实体类 3.3封装一些必要的增删改查操作 4.前后端交互逻辑的实现 4.1博客列表页 …

中国建筑出版传媒许少辉博士八一新书乡村振兴战略下传统村落文化旅游设计日京东当当畅销榜自由营九三学

中国建筑出版传媒许少辉博士八一新书乡村振兴战略下传统村落文化旅游设计日京东当当畅销榜自由营九三学

大数据Flink(六十九):SQL 数据类型

文章目录 SQL 数据类型 一、原子数据类型 二、​​​​​​复合数据类型 SQL 数据类型 在介绍完一些基本概念之后,我们来认识一下

二级MySQL(二)——编程语言,函数

SQL语言又称为【结构化查询语言】 请使用FLOOR&#xff08;x&#xff09;函数求小于或等于5.6的最大整数 请使用TRUNCATE&#xff08;x&#xff0c;y&#xff09;函数将数字1.98752895保留到小数点后4位 请使用UPPER&#xff08;&#xff09;函数将字符串‘welcome’转化为大写…

SOLIDWORKS工程图转DWG图层映射技巧

DWG格式的图纸在工程制图中有着非常重要的地位&#xff0c;工程实践中常常就需要将SOLIDWORKS工程图进行转换。对于两者之间数据衔接的妥善处理&#xff0c;是提升工作效率的有效手段。基于此目的&#xff0c;本次我们将介绍数据衔接的一个有效解决方案&#xff1a;图层数据的映…

中国建筑出版传媒许少辉八一新书乡村振兴战略下传统村落文化旅游设计日

中国建筑出版传媒许少辉八一新书乡村振兴战略下传统村落文化旅游设计日

Jmeter(二十九):Jmeter常用场景梳理

一、每秒钟固定调用次数 如果想控制每秒发送请求数量,仅仅通过线程数与循环次数是不够的,因为这只能控制发送总数,而要控制每秒发送数量,需要线程数与常数吞吐量控制器的搭配使用,这种场景在性能测试中使用不多。 例如每秒钟调用30次接口,那么把线程数设置为30,将常数…

c# modbus CRC计算器(查表法)

一、简介&#xff1a; 本案例为crc计算器&#xff0c;通过查表法计算出结果 1.窗体后台源代码 using Crc; using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text…

Matlab图像处理-除法运算

除法运算 除法运算可用于去除数字化器灵敏度随空间变化造成的影响。图像的除法运算给出的是两幅图像相应像素值的变化比率&#xff0c;而不是每个像素的绝对差异&#xff0c;因而图像除法也称为比率变换&#xff0c;常用于校正成像设备的非线性影响。 在 MATLAB图像处理工具箱…

IDEA集成Git相关操作知识(pull、push、clone)

一&#xff1a;集成git 1&#xff1a;初始化git&#xff08;新版本默认初始化&#xff09; 老版本若没有&#xff0c;点击VCS&#xff0c;选中import into Version Controller中的Create git Repository(创建git仓库)&#xff0c;同理即可出现git符号。 也可查看源文件夹有没有…

IT和OT:如何弥合差距?

两则企业故事 我们曾经碰到一家食品工厂由于订单数据转换不正确&#xff0c;导致加载的产品不正确&#xff0c;将错误的液体装满了卡车。因为IT和OT不能很好地协同工作。因此&#xff0c;它必须被抽回去&#xff0c;导致大量的时间和金钱损失。将新的业务系统集成到整个IT环境…

Java并发编程第6讲——线程池(万字详解)

Java中的线程池是运用场景最多的并发框架&#xff0c;几乎所有需要异步或并发执行任务的程序都可以使用线程池&#xff0c;本篇文章就详细介绍一下。 一、什么是线程池 定义&#xff1a;线程池是一种用于管理和重用线程的技术&#xff08;池化技术&#xff09;&#xff0c;它主…

Python学习之二 变量与简单数据类型

(一) 变量 在学习之一 中&#xff0c;我们直接将一些数进行运算&#xff0c;在实际编程过程中&#xff0c;我们往往使用变量来保存一些内容。变量的命名需要遵循标识符的命名规则&#xff0c;只能包括字母、数字和_&#xff0c;并且必须以字母或_开始。强烈建议Python PEP8 编…

vulnhub Seattle-0.0.3

环境&#xff1a;vuluhub Seattle-0.0.3 1.catelogue处任意文件下载(目录穿越) http://192.168.85.139/download.php?item../../../../../../etc/passwd 有个admin目录&#xff0c;可以下载里面的文件进行读取 2.cltohes详情页面处(参数prod)存在sql报错注入 http://192.16…

三、Nginx 安装集

一、Nginx CentOS Yum 安装 1. 前置准备 # 默认情况 CentOS-7 中没有 Nginx 的源 # Nginx 官方提供了源&#xff0c;所以执行如下命令添加源 rpm -Uvh http://nginx.org/packages/centos/7/noarch/RPMS/nginx-release-centos-7-0.el7.ngx.noarch.rpm2. 安装 # 安装 yum insta…

ISO 22737-2021预定轨迹低速自动驾驶系统-系统要求、性能要求和性能测试规范(中文全文版)

简介 自动驾驶系统的发展导致了人员、货物和服务运输方式的转变。其中一种新的运输方式是低速自动驾驶(LSAD)系统,它在预定的路线上运行。LSAD系统将被用于最后一英里的运输、商业区的运输、商业或大学校园区以及其他低速环境的应用。 由LSAD系统驾驶的车辆(可以包括与基…

归并排序(Java 实例代码)

目录 归并排序 一、概念及其介绍 二、适用说明 三、过程图示 四、Java 实例代码 MergeSort.java 文件代码&#xff1a; 归并排序 一、概念及其介绍 归并排序&#xff08;Merge sort&#xff09;是建立在归并操作上的一种有效、稳定的排序算法&#xff0c;该算法是采用分…

纯 CSS 开关切换按钮

<!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8"><title>纯 CSS 开关切换按钮</title><style>html {font-size: 62.5%;}body {background-color: #1848a0;}.wrapper {position: absolute;left: …

克服紧张情绪:程序员面试心理准备的关键

&#x1f337;&#x1f341; 博主猫头虎 带您 Go to New World.✨&#x1f341; &#x1f984; 博客首页——猫头虎的博客&#x1f390; &#x1f433;《面试题大全专栏》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33a; &a…

安卓webview,网页端生成安卓项目(极速生成)教程

安卓webview&#xff0c;网页端生成安卓项目&#xff08;极速生成&#xff09;教程 一&#xff0c;前言 当自己做了一个PC端的页面&#xff0c;也就是前端的页面&#xff0c;或者已经上服的页面&#xff0c;但也想生成一个安卓端供用户使用&#xff0c;本教程详细讲解如何把前…