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

文章目录

  • 网络编程套接字(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)

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

华为数通方向HCIP-DataCom H12-821题库(单选题:141-160)

第141题 Router-LSA 能够描述不同的链路类型&#xff0c;不属于Router LSA 链路类型的是以下哪一项? A、Link Type 可以用来描述到末梢网络的连接&#xff0c;即 SubNet B、Link Type 可以用来描述到中转网络的连接&#xff0c;即 TranNet C、Link Type 可以用来描述到另一…

mac软件安装后打开软件显示损坏

#mac传输安装包后安装后打开软件显示损坏处理方式 以postman为例&#xff0c;输入前面的代码&#xff0c;打开访达&#xff0c;把有问题的软件拉到命令行窗口&#xff0c;确认即可 sudo xattr -r -d com.apple.quarantine /Applications/Postman.app sudo xattr -r -d com.ap…

C语言的发展及特点

1. C语言的发展历程 C语言作为计算机编程领域的重要里程碑&#xff0c;其发展历程承载着无数开发者的智慧和创新。C语言诞生于20世纪70年代初&#xff0c;由计算机科学家Dennis Ritchie在贝尔实验室首次推出。当时&#xff0c;Ritchie的目标是为Unix操作系统开发一门能够更方便…

基于Java+SpringBoot+Vue前后端分离社区医院管理系统设计和实现

博主介绍&#xff1a;✌全网粉丝30W,csdn特邀作者、博客专家、CSDN新星计划导师、Java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专…

JavaScript—BOM

BOM是什么&#xff1f; Browser Object Model是浏览器对象模型 官方&#xff1a;浏览器对象模型提供了独立于内容的、可以与浏览器窗口进行互动的对象结构&#xff0c;BOM由多个对象构成&#xff0c;其中代表浏览器窗口的window对象是BOM的顶层对象&#xff0c;其他对象都是该…

HHDESK一键改密功能

HHDESK新增实用功能——使用SSH连接&#xff0c;对服务器/端口进行密码修改。 1 测试 首页点击资源管理——客户端&#xff0c;选择需要修改的连接&#xff1b; 可以先对服务器及端口进行测试&#xff0c;看是否畅通&#xff1b; 右键——测试——ping&#xff1b; 以及右…

【Prometheus】概述及部署

目录 Prometheus 概述 Prometheus 的生态组件 Prometheus 的工作模式 Prometheus 的工作流程 Prometheus 的局限性 部署 Prometheus Prometheust Server 端安装和相关配置 部署 Exporters 监控 MySQL 配置示例 监控 Nginx 配置示例 部署 Grafana 进行展示 部署 Pro…

Git仓库简介

1、工作区、暂存区、仓库 工作区&#xff1a;电脑里能看到的目录。 暂存区&#xff1a;工作区有一个隐藏目录.git&#xff0c;是Git的版本库&#xff0c;Git的版本库里存了很多东西&#xff0c;其中最重要的就是称为stage&#xff08;或者叫index&#xff09;的暂存区&#xf…

javacv基础02-调用本机摄像头并预览摄像头图像画面视频

引入架包&#xff1a; <dependency><groupId>org.openpnp</groupId><artifactId>opencv</artifactId><version>4.5.5-1</version></dependency><dependency><groupId>org.bytedeco</groupId><artifactId…

计算机网络MTU和MSS的区别

在计算机网络中&#xff0c;MTU代表最大传输单元&#xff08;Maximum Transmission Unit&#xff09;&#xff0c;而MSS代表最大分节大小&#xff08;Maximum Segment Size&#xff09;。 1.MTU&#xff08;最大传输单元&#xff09;&#xff1a; MTU是指在网络通信中&#x…

数据库——Redis 常见数据结构以及使用场景分析

文章目录 1. string2. list3. hash4. set5. sorted set 你可以自己本机安装 redis 或者通过 redis 官网提供的在线 redis 环境。 1. string 介绍 &#xff1a;string 数据结构是简单的 key-value 类型。虽然 Redis 是用 C 语言写的&#xff0c;但是 Redis 并没有使用 C 的字符串…

【Java架构-版本控制】-Git基础

本文摘要 Git作为版本控制工具&#xff0c;使用非常广泛&#xff0c;在此咱们由浅入深&#xff0c;分三篇文章&#xff08;Git基础、Git进阶、Gitlab搭那家&#xff09;来深入学习Git 文章目录 本文摘要1.Git仓库基本概念1.1 远程仓库(Remote)1.2 本地库(Repository) 2. Git仓库…

postman接口自动化测试框架实战!

什么是自动化测试 把人对软件的测试行为转化为由机器执行测试行为的一种实践。 例如GUI自动化测试&#xff0c;模拟人去操作软件界面&#xff0c;把人从简单重复的劳动中解放出来。 本质是用代码去测试另一段代码&#xff0c;属于一种软件开发工作&#xff0c;已经开发完成的用…

公网远程访问局域网SQL Server数据库

文章目录 1.前言2.本地安装和设置SQL Server2.1 SQL Server下载2.2 SQL Server本地连接测试2.3 Cpolar内网穿透的下载和安装2.3 Cpolar内网穿透的注册 3.本地网页发布3.1 Cpolar云端设置3.2 Cpolar本地设置 4.公网访问测试5.结语 1.前言 数据库的重要性相信大家都有所了解&…

Jackpack - Hilt

一、概念 类中使用的某个对象不是在这个类中实例化的&#xff08;如Activity无法手动实例化使用&#xff09;&#xff0c;而是通过外部注入&#xff08;从外部传入对象后使用&#xff09;&#xff0c;这种实现方式就称为依赖注入 Dependency Injection&#xff08;简称DI&#…

JVM 垃圾收集

垃圾收集 分代理论Java 堆的内存分区不同分代收集垃圾收集算法 分代理论 弱分代假说&#xff1a;绝大多数对象都是朝生夕灭&#xff0c;即绝大多数对象都是用完很快需要销毁的。强分代假说&#xff1a;熬过多次垃圾收集过程的对象就越难以消亡&#xff0c;即如果对象经过多次垃…

uniapp热更新

首先热更新需要wgt包&#xff1b; 其次先了解这两个组件 下载的方法 安装的组件 场景&#xff1a; 当你项目的js文件或者页面文件或者静态图片文件css文件更新的时候可以走热更新&#xff1b; 而当你安装新的组件插件或者开启新的权限等功能的时候就无法通过热更新进行更新了…

3、监测数据采集物联网应用开发步骤(3)

监测数据采集物联网应用开发步骤(2) 系统整体结构搭建 新建项目 输入项目名称&#xff1a;MonitorData 所谓兵马未动粮草先行&#xff0c;按下图创建好对应的模块备用&#xff1a; com.plugins 业务插件模块 com.zxy.adminlog 日志或文本文…

合宙Air724UG LuatOS-Air LVGL API控件--按钮 (Button)

按钮 (Button) 按钮控件&#xff0c;这个就不用多说了&#xff0c;界面的基础控件之一。 示例代码 – 按键回调函数 event_handler function(obj, event) if event lvgl.EVENT_CLICKED then print(“Clicked\n”) elseif event lvgl.EVENT_VALUE_CHANGED then print(“To…

云计算 - 百度AIStudio使用小结

云计算 - 百度AIStudio使用小结 前言 本文以ffmpeg处理视频为例&#xff0c;小结一下AI Studio的使用体验及一些避坑技巧。 算力获得 免费的算力获得方式为&#xff1a;每日登录后运行一个项目&#xff08;只需要点击运行&#xff0c;不需要真正运行&#xff09;即可获得8小…