【网络编程】实现UDP/TCP客户端、服务器

news2024/10/5 20:26:15


目录

一、UDP

1、Linux客户端、服务器

1.1udpServer.hpp

1.2udpServer.cc

1.3udpClient.hpp

1.4udpClient.cc

1.5onlineUser.hpp

2、Windows客户端

二、TCP

1、单进程版的TCP客户端、服务器

1.1tcpServer.hpp

1.2tcpServer.cc

1.3tcpClient.hpp

1.4tcpClient.cc

1.5log.hpp

2、多进程版的TCP客户端、服务器

3、多线程版的TCP客户端、服务器

4、线程池版的TCP客户端、服务器

4.1tcpServer.hpp

4.2ThreadPool.hpp 

4.3Task.hpp

5、守护进程+多线程版的TCP客户端、服务器

5.1daemon.hpp

5.2tcpServer.cc


UDP/TCP客户端、服务器代码可参考本人gitee

UDP/TCP套接字的创建流程可参考此处

一、UDP

1、Linux客户端、服务器

1.1udpServer.hpp

#pragma once
#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <arpa/inet.h>
#include <strings.h>
#include <functional>
namespace Server
{
    const static string defaultIp="0.0.0.0";//缺省的IP
    const static int gnum=1024;
    typedef function<void(int,string,uint16_t,string)> func_t;
    enum 
    {
        USAGE_ERR=1,
        SOCKET_ERR,
        BIND_ERR,
        OPEN_ERR,
    };
    class udpServer
    {
    public:
        udpServer(const func_t& callback,const uint16_t& port,const string& ip=defaultIp)
        :_callback(callback)//udpServer.cc传入的对客户端数据处理的函数
        ,_port(port)
        ,_ip(ip)
        ,_sockfd(-1)
        {}
        void initServer()
        {
             //1、创建socket
            _sockfd=socket(AF_INET,SOCK_DGRAM,0);//网络通信+数据报
            if(-1==_sockfd)
            {
                cout<<"socket error"<<errno<<":"<<strerror(errno)<<endl;
                exit(SOCKET_ERR);
            }
            cout<<"socket success"<<":"<<_sockfd<<endl;
            //2、绑定IP和端口号
            struct sockaddr_in local;
            bzero(&local,sizeof(local));//将一段内存初始化为全0
            local.sin_family=AF_INET;//协议族设置为网络通信
            local.sin_port=htons(_port);//设置端口号,需要转为大端,主机转网络
            local.sin_addr.s_addr=inet_addr(_ip.c_str());//将IP字符串转uint32_t的同时转为网络字节序
            //local.sin_addr.s_addr=htonl(INADDR_ANY);//INADDR_ANY就是0,表明任何IP都可以访问这个服务器的_port端口
            int n=bind(_sockfd,(struct sockaddr*)&local,sizeof(local));
            if(-1==n)
            {
                cout<<"bind error"<<errno<<":"<<strerror(errno)<<endl;
                exit(BIND_ERR);
            }
        }
        void start()
        {
            char buffer[gnum];
            while(1)
            {
                //循环读取数据
                struct sockaddr_in local;//输出型参数
                socklen_t len=sizeof(local);//必填
                size_t s=recvfrom(_sockfd,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&local,&len);//阻塞式读取
                //这里需要关心1、数据是什么2、数据是谁发的
                if(s>0)
                {
                    buffer[s]=0;//加上'\0'
                    //1、这是从网络读出来的IP,需要由网络字节序转主机字节序2、整数转点分十进制IP,用inet_ntoa进行转换
                    string clientIp=inet_ntoa(local.sin_addr);//将32位IPv4地址(in_addr结构体)转换成点分十进制字符串形式的IP地址
                    uint16_t clientPort=ntohs(local.sin_port);//一样需要转换字节序
                    string message=buffer;
                    cout<<clientIp<<"["<<clientPort<<"]#"<<message<<endl;
                    //对数据进行处理
                    _callback(_sockfd,clientIp,clientPort,message);
                }
            }
        }
        ~udpServer()
        {

        }
    private:
        uint16_t _port;//端口号
        string _ip;//IP地址(服务器不建议固定的绑定一个IP地址,因为服务器需要接收所有的IP)
        int _sockfd;//套接字文件描述符
        func_t _callback;//回调函数
    };
}

1.2udpServer.cc

#include <memory>
#include <unordered_map>
#include <fstream>
#include <signal.h>
using namespace std;
#include "udpServer.hpp"
#include "onlineUser.hpp"
using namespace Server;
// const std::string dictTxt="./dict.txt";
// unordered_map<string,string> dict;//字典
// std::string key,value;

static void Usage(string proc)
{
    cout<<"Usage:\n\t"<<proc<<"local_ip local_port\n\n";
}

// static bool cutString(const string& target,string* key,string* value,const string& sep)//字符串截取
// {
//     //string sep=":";
//     auto pos=target.find(sep,0);
//     if(pos==string::npos)
//     {
//         return false;
//     }
//     *key=target.substr(0,pos);
//     *value=target.substr(pos+sep.size());
//     return true;
// }
// static void initDict()//文件操作
// {
//     ifstream in(dictTxt,std::ios_base::binary);
//     if(!in.is_open())//如果文件打开失败
//     {
//         cerr<<"open file"<<dictTxt<<"error"<<endl;
//         exit(OPEN_ERR);
//     }
//     string line;
//     while(getline(in,line))
//     {
//         if(cutString(line,&key,&value,":"))//如果截取成功
//         {
//             dict.insert(make_pair(key,value));//dict.insert(key,value);
//         }
//         else //截取失败
//         {
//             //...
//         }
//     }
//     in.close();
//     cout<<"load dict success"<<endl;
// }
// static void debugPrint()//测试打印函数
// {
//     for(auto& dt:dict)
//     {
//         cout<<dt.first<<"/"<<dt.second<<endl;
//     }
// }
// //客户端单词翻译代码
// void handMessage(int sockfd,string clientIp,uint16_t clientPort,string message)
// {
//     //对客户端的信息进行特定的业务处理,实现了server通信和业务的解耦
//     string response_message;//将查找的字符串保存至此处
//     unordered_map<string,string>::iterator iter=dict.find(message);
//     if(iter==dict.end())
//     {
//         response_message="unknow";
//     }
//     else
//         response_message=iter->second;
//     //服务端向客户端回发数据
//     struct sockaddr_in client;
//     bzero(&client,sizeof(client));
//     client.sin_family=AF_INET;
//     client.sin_addr.s_addr=inet_addr(clientIp.c_str());
//     client.sin_port=htons(clientPort);
//     sendto(sockfd,response_message.c_str(),response_message.size(),0,(struct sockaddr*)&client,sizeof(client));
// }
// //解析客户端上传的命令
// void execCommand(int sockfd,string clientIp,uint16_t clientPort,string cmd)
// {
//     //对客户端的信息进行特定的业务处理,实现了server通信和业务的解耦
//     auto end=cmd.find("rm");
//     if(end!=string::npos)
//     {
//         cerr<<clientIp<<":"<<clientPort<<"非法操作"<<cmd<<endl;
//         return;
//     }
//     string response_message;//将客户端上传的指令保存至此处
//     FILE* fp=popen(cmd.c_str(),"r");
//     if(fp==nullptr)
//     {
//         response_message=cmd+" exec failed";
//     }
//     char line[1024];
//     while(fgets(line,sizeof(line),fp))
//     {
//         response_message+=line;//读出客户端传入的指令
//     }
//     pclose(fp);
//     //服务端向客户端回发数据
//     struct sockaddr_in client;
//     bzero(&client,sizeof(client));
//     client.sin_family=AF_INET;
//     client.sin_addr.s_addr=inet_addr(clientIp.c_str());
//     client.sin_port=htons(clientPort);
//     sendto(sockfd,response_message.c_str(),response_message.size(),0,(struct sockaddr*)&client,sizeof(client));
// }
//聊天室
OnlineUser olUser;
void routeMessage(int sockfd,string clientIp,uint16_t clientPort,string message)
{
    //上线就新增,下线就减掉
    if(message=="online")
    {
        olUser.addUser(clientIp,clientPort);
    }
    if(message=="offline")
    {
        olUser.delUser(clientIp,clientPort);
    }
    if(olUser.isOnline(clientIp,clientPort))
    {
        //广播消息
        olUser.broadcastMessage(sockfd,clientIp,clientPort,message);
    }
    else
    {
        //服务端向客户端回发数据
        string response_message="请先运行online";
        struct sockaddr_in client;
        bzero(&client,sizeof(client));
        client.sin_family=AF_INET;
        client.sin_addr.s_addr=inet_addr(clientIp.c_str());
        client.sin_port=htons(clientPort);
        sendto(sockfd,response_message.c_str(),response_message.size(),0,(struct sockaddr*)&client,sizeof(client));
    }
}
// void reload(int signo)//热加载回调函数
// {
//     (void)signo;
//     initDict();
// }
int main(int argc,char* argv[])//./udpServer port
{
    if(argc!=2)//判断外部传入的参数是否为3
    {
        Usage(argv[0]);
        exit(USAGE_ERR);
    }
    uint16_t port=atoi(argv[1]);//需要转uint16_t整型

    // signal(2,reload);//发送信号,实现文本的热加载
    // initDict();
    //std::unique_ptr<udpServer> usvr(new udpServer(handMessage,port));//在线翻译
    //std::unique_ptr<udpServer> usvr(new udpServer(execCommand,port));//指令解析
    std::unique_ptr<udpServer> usvr(new udpServer(routeMessage,port));//聊天室
    usvr->initServer();
    usvr->start();
    return 0;
}

1.3udpClient.hpp

#pragma once
#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <pthread.h>
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <arpa/inet.h>
#include <strings.h>
namespace Client
{
    using namespace std;
    class udpClient
    {
    public:
        udpClient(const string& serverIp,const uint16_t& serverPort)
        :_sockfd(-1)
        ,_serverPort(serverPort)
        ,_serverIp(serverIp)
        {}
        void initClient()
        {
            //创建socket
            _sockfd=socket(AF_INET,SOCK_DGRAM,0);
            if(-1==_sockfd)
            {
                cout<<"socket error"<<errno<<":"<<strerror(errno)<<endl;
                exit(2);
            }
            cout<<"socket syuccess"<<":"<<_sockfd<<endl;
        }
        static void* readMessage(void* args)//类内创建线程,有个this指针干扰
        {
            int sockfd=*static_cast<int*>(args);
            pthread_detach(pthread_self());
            while(1)
            {
                //接收服务端发送的数据
                char buffer[1024];
                struct sockaddr_in temp;
                socklen_t len=sizeof(temp);
                size_t s=recvfrom(sockfd,buffer,sizeof(buffer),0,(struct sockaddr*)&temp,&len);
                if(s>0)
                {
                    buffer[s]=0;//字符串以'\0'结尾
                }
                cout<<buffer<<endl;
            }
            return nullptr;
        }
        void run()
        {
            pthread_create(&_reader,nullptr,readMessage,(void*)&_sockfd);
            struct sockaddr_in server;
            memset(&server,sizeof(server),0);//初始化为全0
            server.sin_family=AF_INET;
            server.sin_addr.s_addr=inet_addr(_serverIp.c_str());
            server.sin_port=htons(_serverPort);//主机转网络
            string message;
            char cmdline[1024];
            while(1)
            {
                //cerr<<"Please Enter#";
                // cin>>message;
                fprintf(stderr,"Enter#");
                fflush(stderr);
                fgets(cmdline,sizeof(cmdline),stdin);
                cmdline[strlen(cmdline)-1]=0;
                message=cmdline;
                //发送数据,sendto的时候,操作系统会帮我们自动绑定客户端端口+IP地址
                sendto(_sockfd,message.c_str(),message.size(),0,(struct sockaddr*)&server,sizeof(server));
                
            }
        }
        ~udpClient()
        {}
    private:
        int _sockfd;
        uint16_t _serverPort;
        string _serverIp;
        pthread_t _reader;//读线程
    };
}

1.4udpClient.cc

#include <memory>
#include "udpClient.hpp"
using namespace Client;
static void Usage(string proc)
{
    cout<<"Usage:\n\t"<<proc<<"server_ip server_port\n\n";
}
int main(int argc,char* argv[])//./udpClient server_ip server_port
{
    if(argc!=3)
    {
        Usage(argv[0]);
        exit(1);
    }
    string serverIp=argv[1];
    uint16_t serverPort=atoi(argv[2]);
    unique_ptr<udpClient> ucli(new udpClient(serverIp,serverPort));
    ucli->initClient();
    ucli->run();
    return 0;
}

1.5onlineUser.hpp

#pragma once
#include <iostream>
#include <string>
#include <unordered_map>
#include <sys/types.h>
#include <sys/socket.h>
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <arpa/inet.h>
using namespace std;
class User
{
public:
    User(const string& ip,const uint16_t& port)
        :_ip(ip)
        ,_port(port)
    {

    }
    ~User()
    {}
    string ip()
    {
        return _ip;
    }
    uint16_t port()
    {
        return _port;
    }
private:
    string _ip;//用户IP
    uint16_t _port;//用户端口号

};
class OnlineUser
{
public:
    OnlineUser()
    {}
    ~OnlineUser()
    {}
    void addUser(const string& ip,const uint16_t& port)//新增用户
    {
        string id=ip+"-"+to_string(port);
        users.insert(make_pair(id,User(ip,port)));
    }
    void delUser(const string& ip,const uint16_t& port)//删除用户
    {
        string id=ip+"-"+to_string(port);
        users.erase(id);
    }
    bool isOnline(const string& ip,const uint16_t& port)//是否在线
    {
        string id=ip+"-"+to_string(port);
        return users.find(id)==users.end()?false:true;
    }
    void broadcastMessage(int sockfd,const string& ip,const uint16_t& port,const string& message)//给所有的user广播消息
    {
        for(auto& user:users)
        {
            //服务端向客户端回发数据
            struct sockaddr_in client;
            bzero(&client,sizeof(client));
            client.sin_family=AF_INET;
            client.sin_addr.s_addr=inet_addr(user.second.ip().c_str());
            client.sin_port=htons(user.second.port());
            string s=ip+"_"+to_string(port)+"# ";//id+"#"
            s+=message;
            sendto(sockfd,s.c_str(),s.size(),0,(struct sockaddr*)&client,sizeof(client));
        }
    }
private:
    unordered_map<string,User> users;//string:id=ip+"-"+to_string(port);User:User类
};

2、Windows客户端

        可先让上方Linux服务器先运行起来,再让Windows客户端连接上该服务端,实现网络通信。

#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <iostream>
#include <WinSock2.h>
#include <string>
#include <cstring>
#pragma comment(lib,"ws2_32.lib")
using namespace std;
uint16_t serverPort = 8080;
string serverIp = "43.XXX.105.XX";//你的云服务器IP
#define NUM 1024
int main()
{
	WSAData wsd;
	//启动Winsock
	if (WSAStartup(MAKEWORD(2, 2), &wsd) != 0)
	{
		cout << "WSAStartUp Error = " << WSAGetLastError() << endl;
		return -1;
	}
	else
	{
		cout << "WSAStartup Success" << endl;
	}
	SOCKET sock = socket(AF_INET, SOCK_DGRAM, 0);//创建套接字
	if (sock == SOCKET_ERROR)
	{
		cout<<"socket Error = "<< WSAGetLastError() << endl;
		return -2;
	}
	else
	{
		cout << "socket Success" << endl;
	}
	struct sockaddr_in server;
	memset(&server, 0, sizeof(server));
	server.sin_addr.s_addr = inet_addr(serverIp.c_str());
	server.sin_family = AF_INET;
	server.sin_port = htons(serverPort);
	string line;
	char buffer[NUM];
	while (1)
	{
		//发送数据
		cout << "Please Enter#";
		getline(cin, line);
		int n = sendto(sock, line.c_str(), line.size(), 0, (struct sockaddr*)&server, sizeof(server));
		if (n < 0)
		{
			cerr << "sendto Error" << endl;
			break;
		}
		cout << "发送成功" << endl;
		//接收数据
		buffer[0] = 0;//C式清空数组
		struct sockaddr_in peer;
		int len = (int)sizeof(peer);
		n = recvfrom(sock, buffer, sizeof(buffer)-1, 0, (struct sockaddr*)&peer, &len);
		if (n > 0)
		{
			buffer[n] = 0;
			cout << "server 返回的消息是" << buffer << endl;
		}
		else break;
	}
	closesocket(sock);//关闭套接字
	WSACleanup();
	return 0;
}

二、TCP

1、单进程版的TCP客户端、服务器

        单线程会一直在ServerIO读取写入数据,为一个客户端服务,如果此时连接的客户端不止一个,其他客户端发送的信息将不会被显示。需要使用多线程或多进程解决。

1.1tcpServer.hpp

#pragma once
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string>
#include <cstring>
#include "log.hpp"
namespace Server
{
    enum 
    {
        USAGE_ERR=1,
        SOCKET_ERR,
        BIND_ERR,
        LISTEN_ERR,
    };
    static const uint16_t gport=8080;//缺省的端口号
    static const int gbacklog=5;//最大连接数=5+1
    const static std::string defaultIp="0.0.0.0";//缺省的IP
    class TcpServer
    {
    public:
        TcpServer(const uint16_t& port=gport,const std::string& ip=defaultIp )
            :_listenSocket(-1)
            ,_port(port)   
            ,_ip(ip)
        {

        }
        void InitServer()//初始化服务器
        {
            //1、创建sockrt套接字
            _listenSocket=socket(AF_INET,SOCK_STREAM,0);
            if(_listenSocket<0)
            {
                LogMessage(FATAL,"create socket error");
                exit(SOCKET_ERR);
            }
            LogMessage(NORMAL,"create socket success");
            //2、绑定端口号+ip地址
            struct sockaddr_in local;
            memset(&local,0,sizeof(local));
            local.sin_addr.s_addr=inet_addr(_ip.c_str());
            local.sin_family=AF_INET;
            local.sin_port=htons(_port);
            if(bind(_listenSocket,(struct sockaddr*)&local,sizeof(local))<0)
            {
                LogMessage(FATAL,"bind socket error");
                exit(BIND_ERR);
            }
            LogMessage(NORMAL,"bind socket success");
            //3、设置监听状态
            if(-1==listen(_listenSocket,gbacklog))
            {
                LogMessage(FATAL,"listen socket error");
                exit(LISTEN_ERR);
            }
            LogMessage(NORMAL,"listen socket success");
        }
        void Start()//启动服务器
        {
            while(1)
            {
                //4、服务器获取客户端连接请求
                struct sockaddr_in peer;//输出型参数,拿到客户端的信息
                socklen_t len=sizeof(peer);
                int sock=accept(_listenSocket,(struct sockaddr*)&peer,&len);  
                if(-1==sock)      
                {
                    LogMessage(ERROR,"accept error,next");
                    continue;
                }           
                LogMessage(NORMAL,"accept a new link success");
                //5、使用accept的返回值sock进行通信,均为文件操作
                ServerIO(sock);
                close(sock);//必须关闭使用完毕的sock,否则文件描述符泄漏
            }
        }
        void ServerIO(int sock)
        {
            char buffer[1024];
            while(1)
            {
                //服务器读取客户端数据,通过套接字sock这个文件描述符读取数据
                ssize_t n=read(sock,buffer,sizeof(buffer)-1);
                if(n>0)
                {
                    buffer[n]=0;
                    std::cout<<"recv message:"<<buffer<<std::endl;
                    std::string outBuffer=buffer;
                    outBuffer+="[server echo]";
                    //服务器将数据处理后发送回客户端
                    write(sock,outBuffer.c_str(),outBuffer.size());
                }
                else if(0==n)//服务器read返回值为0,说明客户端关闭了
                {
                    LogMessage(NORMAL,"client quit,server quit");
                    break;
                }
            }
        }
        ~TcpServer()
        {}
    private:
        int _listenSocket;//监听客户端的连接请求,不用于数据通信
        uint16_t _port;//服务器端口号
        std::string _ip;//服务器ip地址
    };
}

1.2tcpServer.cc

#include "tcpServer.hpp"
#include "memory"
using namespace Server;
static void Usage(std::string proc)
{
    std::cout<<"Usage:\n\t"<<proc<<"serverPort\n\n";
}
//./tcpServer local_port
int main(int argc,char* argv[])
{
    if(argc!=2)//判断外部传入的参数是否为2
    {
        Usage(argv[0]);
        exit(USAGE_ERR);
    }
    uint16_t port=std::stoi(argv[1]);
    std::unique_ptr<TcpServer> tsvr(new TcpServer(port));
    tsvr->InitServer();
    tsvr->Start();
    return 0;
}

1.3tcpClient.hpp

#pragma once
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string>
#include <cstring>
#define NUM 1024
class TcpClient
{
public:
    TcpClient(const std::string& serverIp,const uint16_t& serverPort)
        :_serverIp(serverIp)
        ,_serverPort(serverPort)
        ,_sock(-1)
    {

    }
    void InitClient()
    {
        //1、创建套接字
        _sock=socket(AF_INET,SOCK_STREAM,0);
        if(_sock<0)
        {   
            std::cerr<<"cerete socket err"<<std::endl;
            exit(2);
        }
        //2、客户端需要bind,但是客户端的绑定不需要我们自己写,操作系统会去绑定;(无需程序员bind)
    }
    void Start()
    {
        //3、客户端发起连接
        struct sockaddr_in server;
        memset(&server,0,sizeof(server));
        server.sin_addr.s_addr=inet_addr(_serverIp.c_str());
        server.sin_family=AF_INET;
        server.sin_port=htons(_serverPort);
        if(connect(_sock,(struct sockaddr*)&server,sizeof(server))<0)//连接失败
        {
            std::cerr<<"sock connect error"<<std::endl;
        }
        else//连接成功
        {
            //4、客户端发送/接收消息,文件操作
            std::string msg;
            while(1)
            {
                std::cout<<"Enter:";
                std::getline(std::cin,msg);
                write(_sock,msg.c_str(),msg.size());
                char buffer[NUM];
                int n=read(_sock,buffer,sizeof(buffer)-1);
                if(n>0)
                {
                    buffer[n]=0;
                    std::cout<<"Server 回显消息:"<<buffer<<std::endl;
                }
                else
                    break;
            }
        }
    }
    ~TcpClient()
    {
        if(_sock>=0)
        {
            close(_sock);
        }
    }
private:
    int _sock;//客户端套接字
    uint16_t _serverPort;//服务器端口号
    std::string _serverIp;//服务器ip
};

1.4tcpClient.cc

#include "tcpClient.hpp"
#include <memory>
static void Usage(std::string proc)
{
    std::cout<<"Usage:\n\t"<<proc<<"local_ip local_port\n\n";
}
//./tcpClient serverIp serverPort
int main(int argc,char* argv[])
{
    if(argc!=3)
    {
        Usage(argv[0]);
        exit(1);
    }
    std::string serverIp=argv[1];
    uint16_t serverPort=std::stoi(argv[2]);
    std::unique_ptr<TcpClient> tcli(new TcpClient(serverIp,serverPort));
    tcli->InitClient();
    tcli->Start();
    return 0;
}

1.5log.hpp

#pragma once
#include <iostream>
#include <string>
#define DEBUG 0
#define NORMAL 1
#define WARNING 2
#define ERROR 3
#define FATAL 4
//日志功能
void LogMessage(int level,const std::string& message)
{
    //[日志等级][时间戳/时间][pid][message]
    std::cout<<message<<std::endl;
}

2、多进程版的TCP客户端、服务器

        更换tcpServer.hpp即可,其他文件和单进程版一样。

#pragma once
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string>
#include <cstring>
#include <cstdlib>
#include "log.hpp"
namespace Server
{
    enum 
    {
        USAGE_ERR=1,
        SOCKET_ERR,
        BIND_ERR,
        LISTEN_ERR,
    };
    static const uint16_t gport=8080;//缺省的端口号
    static const int gbacklog=5;//最大连接数=5+1
    const static std::string defaultIp="0.0.0.0";//缺省的IP
    class TcpServer
    {
    public:
        TcpServer(const uint16_t& port=gport,const std::string& ip=defaultIp )
            :_listenSocket(-1)
            ,_port(port)   
            ,_ip(ip)
        {

        }
        void InitServer()//初始化服务器
        {
            //1、创建sockrt套接字
            _listenSocket=socket(AF_INET,SOCK_STREAM,0);
            if(_listenSocket<0)
            {
                LogMessage(FATAL,"create socket error");
                exit(SOCKET_ERR);
            }
            LogMessage(NORMAL,"create socket success");
            //2、绑定端口号+ip地址
            struct sockaddr_in local;
            memset(&local,0,sizeof(local));
            local.sin_addr.s_addr=inet_addr(_ip.c_str());
            local.sin_family=AF_INET;
            local.sin_port=htons(_port);
            if(bind(_listenSocket,(struct sockaddr*)&local,sizeof(local))<0)
            {
                LogMessage(FATAL,"bind socket error");
                exit(BIND_ERR);
            }
            LogMessage(NORMAL,"bind socket success");
            //3、设置监听状态
            if(-1==listen(_listenSocket,gbacklog))
            {
                LogMessage(FATAL,"listen socket error");
                exit(LISTEN_ERR);
            }
            LogMessage(NORMAL,"listen socket success");
        }
        void Start()//启动服务器
        {
            while(1)
            {
                //4、服务器获取客户端连接请求
                struct sockaddr_in peer;//输出型参数,拿到客户端的信息
                socklen_t len=sizeof(peer);
                int sock=accept(_listenSocket,(struct sockaddr*)&peer,&len);  
                if(-1==sock)      
                {
                    LogMessage(ERROR,"accept error,next");
                    continue;
                }           
                LogMessage(NORMAL,"accept a new link success");
                // //5、使用accept的返回值sock进行通信,均为文件操作
                pid_t id=fork();
                if(id==0)//子进程
                {
                    close(_listenSocket);//子进程的
                    if(fork()>0) exit(0);//让子进程退出,孙子进程成为孤儿进程,交给1号进程托管回收其退出资源
                    ServerIO(sock);
                    close(sock);//必须关闭使用完毕的sock,否则文件描述符泄漏(虽然下一句代码exit(0),孙子进程退出也会释放文件描述符,最好还是手动关一下)
                    exit(0);
                }
                close(sock);//这是用于通信的套接字fd,父进程和孙子进程都有这个文件描述符,父进程关了,该文件描述符引用技术-1,直至孙子进程退出,该fd才会减为0,关闭
                //父进程
                //waitpid()
                pid_t ret=waitpid(id,nullptr,0);//这里不能用非阻塞等待,否则父进程先跑去执行其他代码,可能会被卡在accept出不来了(没有新的客户端来连接的话)
                if(ret>0)
                {
                    std::cout<<"waitsucceess"<<ret<<std::endl;
                }
            }
        }       
        void ServerIO(int sock)
        {
            char buffer[1024];
            while(1)
            {
                //服务器读取客户端数据,通过套接字sock这个文件描述符读取数据
                ssize_t n=read(sock,buffer,sizeof(buffer)-1);
                if(n>0)
                {
                    buffer[n]=0;
                    std::cout<<"recv message:"<<buffer<<std::endl;
                    std::string outBuffer=buffer;
                    outBuffer+="[server echo]";
                    //服务器将数据处理后发送回客户端
                    write(sock,outBuffer.c_str(),outBuffer.size());
                }
                else if(0==n)//服务器read返回值为0,说明客户端关闭了
                {
                    LogMessage(NORMAL,"client quit,server quit");
                    break;
                }
            }
        }
        ~TcpServer()
        {}
    private:
        int _listenSocket;//监听客户端的连接请求,不用于数据通信
        uint16_t _port;//服务器端口号
        std::string _ip;//服务器ip地址
    };
}

区别在于这张图里的代码:

1、close(_listenSocket):关闭子进程的监听fd(虽然手动关不关都行,因为下一句代码就让子进程退出了,最好还是手动关一下)

2、if(fork()>0) exit(0):让子进程创建孙子进程,子进程退出。提前干掉子进程,这样父进程在外部就可以不用阻塞式等待子进程退出了。同时孙子进程成为孤儿进程,会被1号进程领养,程序员无需关心孤儿进程的退出善后工作。

3、孙子进程close(sock):必须关闭使用完毕的sock,否则文件描述符泄漏(虽然下一句代码exit(0),孙子进程退出也会释放文件描述符,最好还是手动关一下)

4、父进程close(sock):这是用于通信的套接字fd,父进程和孙子进程都有这个文件描述符,父进程关了,该文件描述符引用计数-1,直至孙子进程退出,该fd才会减为0,关闭,所以父进程提前关闭该fd不会影响孙子进程。但是这里父进程如果不关,客户端连一个fd+1,存在文件描述符泄露。

5、pid_t ret=waitpid(id,nullptr,0):这里不能用非阻塞等待,否则父进程先跑去执行其他代码,可能会被卡在accept出不来了(如果没有新的客户端来连接的话,将一直卡在accept)

3、多线程版的TCP客户端、服务器

更换tcpServer.hpp即可,其他文件和单进程版一样。

#pragma once
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string>
#include <cstring>
#include <cstdlib>
#include <pthread.h>
#include "log.hpp"
namespace Server
{
    enum 
    {
        USAGE_ERR=1,
        SOCKET_ERR,
        BIND_ERR,
        LISTEN_ERR,
    };
    static const uint16_t gport=8080;//缺省的端口号
    static const int gbacklog=5;//最大连接数=5+1
    const static std::string defaultIp="0.0.0.0";//缺省的IP
    class TcpServer;
    struct ThreadData//用于线程函数传参
    {
        ThreadData(TcpServer* self,const int& sock)
            :_self(self)
            ,_sock(sock)
        {}
        TcpServer* _self;//this
        int _sock;//通信fd
    };
    class TcpServer
    {
    public:
        TcpServer(const uint16_t& port=gport,const std::string& ip=defaultIp )
            :_listenSocket(-1)
            ,_port(port)   
            ,_ip(ip)
        {

        }
        void InitServer()//初始化服务器
        {
            //1、创建sockrt套接字
            _listenSocket=socket(AF_INET,SOCK_STREAM,0);
            if(_listenSocket<0)
            {
                LogMessage(FATAL,"create socket error");
                exit(SOCKET_ERR);
            }
            LogMessage(NORMAL,"create socket success");
            //2、绑定端口号+ip地址
            struct sockaddr_in local;
            memset(&local,0,sizeof(local));
            local.sin_addr.s_addr=inet_addr(_ip.c_str());
            local.sin_family=AF_INET;
            local.sin_port=htons(_port);
            if(bind(_listenSocket,(struct sockaddr*)&local,sizeof(local))<0)
            {
                LogMessage(FATAL,"bind socket error");
                exit(BIND_ERR);
            }
            LogMessage(NORMAL,"bind socket success");
            //3、设置监听状态
            if(-1==listen(_listenSocket,gbacklog))
            {
                LogMessage(FATAL,"listen socket error");
                exit(LISTEN_ERR);
            }
            LogMessage(NORMAL,"listen socket success");
        }
        void Start()//启动服务器
        {
            while(1)
            {
                //4、服务器获取客户端连接请求
                struct sockaddr_in peer;//输出型参数,拿到客户端的信息
                socklen_t len=sizeof(peer);
                int sock=accept(_listenSocket,(struct sockaddr*)&peer,&len);  
                if(-1==sock)      
                {
                    LogMessage(ERROR,"accept error,next");
                    continue;
                }           
                LogMessage(NORMAL,"accept a new link success");
                //5、使用accept的返回值sock进行通信,均为文件操作
                //多线程版
                pthread_t tid;
                ThreadData* td=new ThreadData(this,sock);
                pthread_create(&tid,nullptr,threadRoutine,(void*)td);
            }
        }   
        static void* threadRoutine(void* args)
        {
            pthread_detach(pthread_self());//线程分离
            ThreadData* td=static_cast<ThreadData*>(args);
            td->_self->ServerIO(td->_sock);//线程调用服务函数
            close(td->_sock);
            delete td;
            return nullptr;
        }
        void ServerIO(int sock)
        {
            char buffer[1024];
            while(1)
            {
                //服务器读取客户端数据,通过套接字sock这个文件描述符读取数据
                ssize_t n=read(sock,buffer,sizeof(buffer)-1);
                if(n>0)
                {
                    buffer[n]=0;
                    std::cout<<"recv message:"<<buffer<<std::endl;
                    std::string outBuffer=buffer;
                    outBuffer+="[server echo]";
                    //服务器将数据处理后发送回客户端
                    write(sock,outBuffer.c_str(),outBuffer.size());
                }
                else if(0==n)//服务器read返回值为0,说明客户端关闭了
                {
                    LogMessage(NORMAL,"client quit,server quit");
                    break;
                }
            }
        }
        ~TcpServer()
        {}
    private:
        int _listenSocket;//监听客户端的连接请求,不用于数据通信
        uint16_t _port;//服务器端口号
        std::string _ip;//服务器ip地址
    };
}

        在一个进程中的所有线程都可以访问到文件描述符表,属于共享资源,一个线程所对应的fd在使用完毕后需要进行关闭。

4、线程池版的TCP客户端、服务器

        其他文件和单进程版一样。

4.1tcpServer.hpp

#pragma once
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string>
#include <cstring>
#include <cstdlib>
#include <pthread.h>
#include "log.hpp"
#include "Task.hpp"
#include "ThreadPool.hpp"
namespace Server
{
    enum 
    {
        USAGE_ERR=1,
        SOCKET_ERR,
        BIND_ERR,
        LISTEN_ERR,
    };
    static const uint16_t gport=8080;//缺省的端口号
    static const int gbacklog=5;//最大连接数=5+1
    const static std::string defaultIp="0.0.0.0";//缺省的IP
    class TcpServer;
    struct ThreadData//用于线程函数传参
    {
        ThreadData(TcpServer* self,const int& sock)
            :_self(self)
            ,_sock(sock)
        {}
        TcpServer* _self;//this
        int _sock;//通信fd
    };
    class TcpServer
    {
    public:
        TcpServer(const uint16_t& port=gport,const std::string& ip=defaultIp )
            :_listenSocket(-1)
            ,_port(port)   
            ,_ip(ip)
        {

        }
        void InitServer()//初始化服务器
        {
            //1、创建sockrt套接字
            _listenSocket=socket(AF_INET,SOCK_STREAM,0);
            if(_listenSocket<0)
            {
                LogMessage(FATAL,"create socket error");
                exit(SOCKET_ERR);
            }
            LogMessage(NORMAL,"create socket success");
            //2、绑定端口号+ip地址
            struct sockaddr_in local;
            memset(&local,0,sizeof(local));
            local.sin_addr.s_addr=inet_addr(_ip.c_str());
            local.sin_family=AF_INET;
            local.sin_port=htons(_port);
            if(bind(_listenSocket,(struct sockaddr*)&local,sizeof(local))<0)
            {
                LogMessage(FATAL,"bind socket error");
                exit(BIND_ERR);
            }
            LogMessage(NORMAL,"bind socket success");
            //3、设置监听状态
            if(-1==listen(_listenSocket,gbacklog))
            {
                LogMessage(FATAL,"listen socket error");
                exit(LISTEN_ERR);
            }
            LogMessage(NORMAL,"listen socket success");
        }
        void Start()//启动服务器
        {
            //4、线程池初始化
            ThreadPool<Task>::getInstance()->run();//线程启动
            while(1)
            {
                //5、服务器获取客户端连接请求
                struct sockaddr_in peer;//输出型参数,拿到客户端的信息
                socklen_t len=sizeof(peer);
                int sock=accept(_listenSocket,(struct sockaddr*)&peer,&len);  
                if(-1==sock)      
                {
                    LogMessage(ERROR,"accept error,next");
                    continue;
                }           
                LogMessage(NORMAL,"accept a new link success");
                ThreadPool<Task>::getInstance()->push(Task(sock,ServerIO));
            }
        }   
        ~TcpServer()
        {}
    private:
        int _listenSocket;//监听客户端的连接请求,不用于数据通信
        uint16_t _port;//服务器端口号
        std::string _ip;//服务器ip地址
    };
}

4.2ThreadPool.hpp 

#pragma once
#include <vector>
#include <queue>
#include <pthread.h>
#include <unistd.h>
#include <mutex>
#include "Thread.hpp"
#include "LockGuard.hpp"
using namespace ThreadNs;
const int gnum =5;

template <class T>//声明
class ThreadPool;

template <class T>
struct ThreadData
{
    ThreadData(ThreadPool<T>* tp,const std::string& s)
    :_threadPool(tp)
    ,_name(s)
    {}
    ThreadPool<T>* _threadPool;
    std::string _name;
};
template <class T>
class ThreadPool
{
private:
    //因为普通成员函数第一个参数是this指针,和回调方法不匹配,故改成static类型
    static void* handlerTask(void* args)//args是ThreadData对象指针
    {
        ThreadData<T>* td=static_cast<ThreadData<T>*>(args);
        while(1)
        {
            T t;
            {   //RAII,出了作用域LockGuard会销毁,将析构锁
                LockGuard lockGuard(td->_threadPool->mutex());//加锁
                while(td->_threadPool->IsQueueEmpty())//如果队列为空,则等待
                {
                    td->_threadPool->ThreadWait();
                }
                //线程能走到这里,说明队列一定有任务给线程
                t=td->_threadPool->Pop();//从队列中取出任务
            }
            t();//Task的operator()
        }
        delete td;//析构ThreadData对象
        return nullptr;
    }
    ThreadPool(const int& num=gnum)
    :_num(num)
    {
        pthread_mutex_init(&_mutex,nullptr);
        pthread_cond_init(&_cond,nullptr);
        //创建线程
        for(int i=0;i<_num;++i)
        {
            _threads.push_back(new Thread());
        }
    }
    ThreadPool(const ThreadPool<T>&)=delete;//禁用拷贝构造
    ThreadPool<T>& operator=(const ThreadPool<T>&)=delete;//禁用赋值运算符重载

public://解决静态handlerTask是静态函数的问题,这几个都是偷家函数
    void LockQueue()   {pthread_mutex_lock(&_mutex);}
    void UnLockQueue() {pthread_mutex_unlock(&_mutex);}
    bool IsQueueEmpty(){return _taskQueue.empty();}
    void ThreadWait()  {pthread_cond_wait(&_cond,&_mutex);}
    T Pop()         
    {
        T t=_taskQueue.front();
        _taskQueue.pop();
        return t;
    } 
    pthread_mutex_t* mutex()
    {
        return &_mutex;
    }
public: 
    void run()//线程启动
    {
        for(const auto& t:_threads)
        {
            ThreadData<T>* td=new ThreadData<T>(this,t->threadName());
            t->start(handlerTask,(void*)td);
            std::cout<<t->threadName()<<"start..."<<std::endl;
        }
    }
    void push(const T& in)
    {
        //RAII,出了作用域,锁将会被释放
        LockGuard lockGuard(&_mutex);
        _taskQueue.push(in);
        pthread_cond_signal(&_cond);
        std::cout<<"任务发送成功"<<std::endl;
    }
    ~ThreadPool()
    {
        pthread_mutex_destroy(&_mutex);
        pthread_cond_destroy(&_cond);
        for(const auto& t:_threads)
        {
            delete t;
        }
    }
    static ThreadPool<T>* getInstance()//这里的static的作用是让这个函数只有一份,获取单例对象。tp是临界资源,需要加锁
    {
        if(nullptr==tp)//因为锁只创建一次,防止线程进来被锁阻塞
        {
            //只进来一次就够了
            _singletonLock.lock();
            if(nullptr==tp)//说明对象还没有被创建
            {
                tp=new ThreadPool<T>(); 
            }
            _singletonLock.unlock();
        }
        return tp;
    }
private:
    int _num;//线程个数
    std::vector<Thread*> _threads;//使用vector存放线程
    std::queue<T> _taskQueue;//任务队列,往里面放任务,它是共享资源,需要加锁保护
    pthread_mutex_t _mutex;//互斥锁
    pthread_cond_t _cond;//条件变量

    static ThreadPool<T>* tp;//单例模式静态的对象指针
    static std::mutex _singletonLock;//获取单例对象使用的锁

};
template <class T>
ThreadPool<T>* ThreadPool<T>::tp=nullptr;

template <class T>
std::mutex ThreadPool<T>::_singletonLock;

4.3Task.hpp

#pragma once
#include <iostream>
#include <functional>
#include <string>
void ServerIO(int sock)
{
    char buffer[1024];
    while(1)//适应快速响应的任务,这个任务while其实不太合适
    {
        //服务器读取客户端数据,通过套接字sock这个文件描述符读取数据
        ssize_t n=read(sock,buffer,sizeof(buffer)-1);
        if(n>0)
        {
            buffer[n]=0;
            std::cout<<"recv message:"<<buffer<<std::endl;
            std::string outBuffer=buffer;
            outBuffer+="[server echo]";
            //服务器将数据处理后发送回客户端
            write(sock,outBuffer.c_str(),outBuffer.size());
        }
        else if(0==n)//服务器read返回值为0,说明客户端关闭了
        {
            close(sock);
            LogMessage(NORMAL,"client quit,server quit");
            break;
        }
    }
}
class Task
{
    //using func_t=std::function<int(int,int,char)>;
    typedef std::function<void(int)> func_t;//函数对象
public:
    Task()
    {}
    Task(int sock,func_t func)
    :_sock(sock)
    ,_callBack(func)
    {}
    void operator()()//消费者调用
    {
        _callBack(_sock);
    }
private:
    int _sock;
    func_t _callBack;//回调函数
};

5、守护进程+多线程版的TCP客户端、服务器

        其他文件和单进程版一样。

5.1daemon.hpp

#pragma once
#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <cstdlib>
#include <cassert>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define DEV "/dev/null"//数据黑洞,向它写入的数据会被吃掉,读取数据什么都读不到(不会使进程退出)
void DaemonSele(const char* currrPath=nullptr)
{
    //1、让调用进程屏蔽异常的信号
    //SIGPIPE信号会在进程向一个已经关闭的socket连接写数据时产生,如果不处理这个信号,进程会被强制退出。通过忽略SIGPIPE信号,可以避免进程因为这个信号而退出。
    signal(SIGPIPE,SIG_IGN);
    //2、让自己不是组长,调用setsid
    if(fork()>0) exit(0);//守护进程也称精灵进程,本质就是一个孤儿进程
    pid_t n=setsid();
    assert(n!=-1);//失败返回-1
    //3、守护进程脱离终端,所以要关闭或重定向进程默认打开的文件及文件描述符
    int fd=open(DEV,O_RDWR);//以读写的方式打开文件黑洞
    if(fd>=0)//创建成功:重定向
    {
        dup2(fd,0);//将fd覆盖标准输入
        dup2(fd,1);
        dup2(fd,2);
        close(fd);
    }
    else//创建失败:手动关闭文件描述符
    {
        close(0);
        close(1);
        close(2);
    }
    //4、进程执行路径更改(可改可不改)
    if(currrPath)
    {
        chdir(currrPath);
    }
}

5.2tcpServer.cc

#include "tcpServer.hpp"
#include "memory"
#include "daemon.hpp"
using namespace Server;
static void Usage(std::string proc)
{
    std::cout<<"Usage:\n\t"<<proc<<"serverPort\n\n";
}
//./tcpServer local_port
int main(int argc,char* argv[])
{
    if(argc!=2)//判断外部传入的参数是否为2
    {
        Usage(argv[0]);
        exit(USAGE_ERR);
    }
    uint16_t port=std::stoi(argv[1]);
    std::unique_ptr<TcpServer> tsvr(new TcpServer(port));
    tsvr->InitServer();
    DaemonSele();//守护进程化,让这个独立的孤儿进程去启动服务器
    tsvr->Start();
    return 0;
}

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

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

相关文章

K8s进阶1——kubeadm工具搭建K8s高可用集群

文章目录 一、资源清单二、系统初始化2.1 所有服务器配置2.2 master节点配置 三、nginxkeepalived3.1 主备机器上进行3.2 配置主节点3.3 配置备节点3.4 启动服务 四、部署etcd集群4.1 资源清单4.2 生成Etcd证书4.3 部署Etcd集群 五、安装Docker/kubeadm/kubelet5.1 安装docker5…

ESP32-C3入门教程 问题篇⑱——VSCode ESP-IDF Monitor device 波特率不对导致乱码

文章目录 一、前言二、发现问题三、分析问题一、前言 本文基于VS Code IDE进行编程、编译、下载、运行等操作 基础入门章节请查阅:ESP32-C3入门教程 基础篇①——基于VS Code构建Hello World 教程目录大纲请查阅:ESP32-C3入门教程——导读 二、发现问题 升级了VSCode,没注意…

GDB调试无行号,报dwarf error问题解决

背景 近期我开发的一个C程序&#xff0c;在生产环境产生了coredump&#xff0c;但是在调试该core文件时&#xff0c;打出的debug信息并不全。 这种debug信息丢失&#xff0c;其实说白了&#xff0c;就是符号表丢失。一般由两种情况造成&#xff0c;一种是编译的时候没有加-g参…

EasyRecovery16适用于Windows和Mac的专业硬盘恢复软件

无论你对数据恢复了解多少&#xff0c; 我们将为您处理所有复杂的流程并简化恢复!适用于Windows和Mac的 专业硬盘恢复软件 硬盘数据无法保证绝对安全。有时会发生数据丢失&#xff0c;需要使用硬盘恢复工具。支持恢复不同存储介质数据&#xff1a;硬盘、光盘、U盘/移动硬盘、数…

类似于ChatGPT的优秀应用notion

notion 是一款流行的笔记应用。不过功能实际远超笔记&#xff0c;官方自己定义是&#xff1a;“将笔记、知识库和任务管理无缝整合的协作平台”。其独特的 block 概念&#xff0c;极大的扩展了笔记文档的作用&#xff0c;一个 block 可以是个数据库、多媒体、超链接、公式等等。…

如何把Docker容器变成物理机系统

如何把容器变成物理机 本文的主题是把容器变成物理机&#xff0c;根据所学的知识。以及通过各种搜索引擎。他们都告诉我们&#xff0c;这是不可能的。这真的是不可能的吗&#xff1f;我不信&#xff0c;那我就要创造奇迹。请继续往下看。本文将教你如何把容器变成物理机。作品…

PBDF8WN、FPBJXDN、FPBMXDN插装式比例阀放大器

PBHB8WN、PBFB8WN、PBDB8WN、PBHF8WN、PBFF8WN、PBDF8WN、PBJB8WN、RPEILAN、RBAPXAN、RBANXAN、FPBGXDN、FPBDXDN、FPBJXDN、FPBMXDN、FPBFXDN、FPBIXDN、FREPXAN比例插装阀一种高精度液压控制元件&#xff0c;其采用了先进的比例控制技术&#xff0c;可以根据控制信号快速地调…

linux介绍

/ 是所有目录的源点目录结构整体是一棵倒挂的树bin&#xff1a;存放二进制可执行文件boot&#xff1a;存放系统引导时使用的各类文件dev&#xff1a;存放设备文件etc&#xff1a;存放系统配置文件home&#xff1a;存放系统用户的文件lib&#xff1a;存放系统运行所需的共享库和…

传输层:TCP协议

传输层中有两个重要的协议&#xff1a;TCP协议和UDP协议。本博文分享的是TCP协议&#xff0c;不仅分享其协议格式&#xff0c;特点等等&#xff0c;还有应答机制、超时传送机制、连接管理机制、滑动窗口、阻塞控制等等。 TCP协议 TCP全称为 "传输控制协议(Transmission C…

Python中的自定义函数创建方法和应用举例

Python中的自定义函数创建方法和应用举例 在Python语言中&#xff0c;函数是一组能够完成特定任务的语句模块&#xff0c;可分为内置函数、第三方模块函数和自定义函数。其中&#xff0c;内置函数是Python系统自带的函数&#xff1b;模块函数是NumPy等库中的函数。 1.自定义函…

Java【网络原理2】TCP 协议的三次握手和四次挥手到底是什么意思?

文章目录 前言一、三次握手三次握手的作用 二、四次挥手总结 前言 各位读者好, 我是小陈, 这是我的个人主页, 希望我的专栏能够帮助到你: &#x1f4d5; JavaSE基础: 基础语法, 类和对象, 封装继承多态, 接口, 综合小练习图书管理系统等 &#x1f4d7; Java数据结构: 顺序表, 链…

mysqlbinlog delete恢复成insert

不小心把数据删掉了 首先要拿到binlog文件 执行以下命令行 /usr/local/mysql/bin/mysqlbinlog --base64-outputdecode-rows --start-datetime“2023-05-19 09:01:32” --stop-datetime“2023-05-19 09:01:35” -v /Users/zylong/Downloads/mysql-bin.003178 --result-file/Use…

第一章 数据库操作

一、 数据库操作 1.1 创建数据库 创建数据库是指在数据库系统中划分一块空间&#xff0c;用来存储相应的数据&#xff0c;这是进行表操作的基础&#xff0c;也是数据库管理的基础在MySQL中创建数据库之前&#xff0c;可以使用show语句来显示当前已经存在的数据库&#xff0c;…

java基于springboot协同过滤算法的网上图书商城推荐系统m44xq2

开发环境 开发语言&#xff1a;Java 框架&#xff1a;springboot 技术&#xff1a;JSP JDK版本&#xff1a;JDK1.8 数据库工具&#xff1a;Navicat11 开发软件&#xff1a;eclipse/myeclipse/idea Maven包&#xff1a;Maven3.3.9 浏览器&#xff1a;谷歌浏览器功能介绍 Spring…

微服务—Redis实用篇-黑马头条项目用户签到功能(使用bitmap实现)与UV统计

微服务—Redis实用篇-黑马头条项目用户签到功能&#xff08;使用bitmap实现&#xff09;与UV统计 1、用户签到 1.1、用户签到-BitMap功能演示 我们针对签到功能完全可以通过mysql来完成&#xff0c;比如说以下这张表 用户一次签到&#xff0c;就是一条记录&#xff0c;假如有…

npm 账户的创建、测试、登录

目录 1、账户的创建 2、测试创建的账户 3、通过电子邮件接收一次性密码 3.1 使用一次性密码登录&#xff1a; 3.2 启用双因素身份验证&#xff1a; 3.3 无法访问电子邮件 1、账户的创建 如果您还没有npm用户帐户&#xff0c;您可以创建一个帐户&#xff0c;以便在公共注…

MATLAB 之 二维图形绘制的基本函数和辅助操作

文章目录 一、绘制二维曲线的基本函数1. plot 函数的基本用法2. 含多个输入参数的 plot 函数3. 含选项的 plot 函数4. 双纵坐标函数 plotyy 二、绘制绘制图像的辅助操作1. 图形标注2. 坐标控制3. 图形保持4. 图形窗口的分割 二维图形是将平面坐标上的数据点连接起来的平面图形。…

C++的cout详解

2023年5月20日&#xff0c;周六早上&#xff1a; 我发现我找不到非常详细的cout类的成员函数&#xff0c;只好自己写了。 不定期更新。 cout的继承关系 cout类继承自ostream类&#xff0c;ostream类继承自ios类&#xff0c;ios类继承自ios_base类 cout类拥有的所有成员函数 …

包图的画法

包图 1.包图 1.1概念 包图是UML中用来组织模型元素的模型元素。 可以把包图比作一个存放模型元素的箱子或者是容器&#xff0c;里面可以可以存放各种各样的模型元素。 包图中可以包含的信息主要有&#xff1a;类 构件 用例 结点 活动 状态 等其他的相关的模型元素。 1.2包…

秒杀场景checklist

在面试中经常碰到的问题&#xff0c;列下来备忘。