【Linux】udp客户端windows版以及Tcp服务器的实现

news2024/11/26 19:41:26

windows版客户端更适合大多数人~

文章目录

  • 一. udp客户端windows版
  • 二.Tcp服务器的实现
  • 总结


一、udp客户端windows版

首先我们将上一篇文章中实现的udp大型聊天室的代码进行修改,注意我们只修改服务端代码将代码修改的很简单就好,因为我们只是做一个如何用windows做一个客户端的例子。

我们服务端头文件不变,将.cc文件中的hander方法简化一下:

static void Usage(string proc)
{
    cout<<"Usage:\n\t"<<proc<<" local_port\n\n";
}
void handerMessage(int sockfd,string clientip,uint16_t clientport,string message)
{
    string response = message;
    response += "[server echo]: ";
    struct sockaddr_in client;
    socklen_t len = sizeof(client);
    bzero(&client, sizeof(client));
    client.sin_family = AF_INET;
    client.sin_port = htons(clientport);
    client.sin_addr.s_addr = inet_addr(clientip.c_str());
    // 构建好结构体后,我们要把处理的数据发给谁呢?当然是客户端了,客户端给我们发数据我们再将处理后的数据发回给客户端
    sendto(sockfd, response.c_str(), response.size(), 0, (struct sockaddr *)&client, len);
}
// ./udpServer port
int main(int argc,char* argv[])
{
    if (argc!=2)
    {
        Usage(argv[0]);
        exit(USAGE_ERR);
    }
    uint16_t port = atoi(argv[1]);
    unique_ptr<udpServer> usvr(new udpServer(handerMessage,port));
    usvr->InitServer();
    usvr->start();
    return 0;
}

接下来我们开始演示如何在windows环境下编写udp客户端的代码:

首先我们需要包含头文件以及lib的一个库:

#include <iostream>
#include <WinSock2.h>
#include <string>
#pragma comment(lib,"ws2_32.lib")

然后我们需要启动windows的套接字,并且对winsocket进行初始化:

int main()
{
	WSAData wsd;
	//启动Winsock
	//进行Winsocket的初始化,windows初始化socket网络库,申请2.2的版本
	if (WSAStartup(MAKEWORD(2, 2), &wsd) != 0)
	{
		cout << "WSAStartup Error =" << WSAGetLastError() << endl;
		return 0;
	}
	else
	{
		cout << "WSAStartup Success" << endl;
	}
}

startup就是启动的接口,里面的参数的意思是:初始化socket网络库,申请2.2的版本。如果startup这个函数的返回值等于0就说明启动成功,否则就启动失败我们就打印一下。然后就和linux上的一样,创建套接字即可:

当然我们客户端需要知道服务端的ip和端口号,并且用户一般是不知道这些东西的,所以我们要做到让用户直接启动就能连接windows,我们可以将服务器的ip和端口号放在一个文件中,也可以直接定义:

 这里的ip和端口号填的是你的服务器,可不要和我一样-.-。

然后我们就创建套接字:

SOCKET sock = socket(AF_INET, SOCK_DGRAM, 0);
	if (sock == SOCKET_ERROR)
	{
		cout << "socket ERROR = " << WSAGetLastError() << endl;
		return 1;
	}
	else
	{
		cout << "socket success" << endl;
	}
	struct sockaddr_in server;
	memset(&server, 0, sizeof(server));
	server.sin_family = AF_INET;
	server.sin_port = htons(serverport);
	server.sin_addr.s_addr = inet_addr(serverip.c_str());
	string line;
	while (true)
	{
		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;
		}
		//接收服务器的数据
		char buffer[1024];
		struct sockaddr_in client;
		int len = sizeof(client);
		n = recvfrom(sock, buffer, sizeof(buffer) - 1, 0, (struct sockaddr*)&client, &len);
		if (n >= 0)
		{
			buffer[n] = 0;
		}
		cout << "[server echo]: " << buffer << endl;
	}

这里与linux中是完全一样的,最后我们还需要将使用库的相关资源全部释放掉:

//最后将使用库的相关资源全部释放掉 关闭套接字的文件描述符
	closesocket(sock);
	WSACleanup();
	return 0;
}
int main()
{
	WSAData wsd;
	//启动Winsock
	//进行Winsocket的初始化,windows初始化socket网络库,申请2.2的版本
	if (WSAStartup(MAKEWORD(2, 2), &wsd) != 0)
	{
		cout << "WSAStartup Error =" << WSAGetLastError() << endl;
		return 0;
	}
	else
	{
		cout << "WSAStartup Success" << endl;
	}
	SOCKET sock = socket(AF_INET, SOCK_DGRAM, 0);
	if (sock == SOCKET_ERROR)
	{
		cout << "socket ERROR = " << WSAGetLastError() << endl;
		return 1;
	}
	else
	{
		cout << "socket success" << endl;
	}
	struct sockaddr_in server;
	memset(&server, 0, sizeof(server));
	server.sin_family = AF_INET;
	server.sin_port = htons(serverport);
	server.sin_addr.s_addr = inet_addr(serverip.c_str());
	string line;
	while (true)
	{
		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;
		}
		//接收服务器的数据
		char buffer[1024];
		struct sockaddr_in client;
		int len = sizeof(client);
		n = recvfrom(sock, buffer, sizeof(buffer) - 1, 0, (struct sockaddr*)&client, &len);
		if (n >= 0)
		{
			buffer[n] = 0;
		}
		cout << "[server echo]: " << buffer << endl;
	}

	//最后将使用库的相关资源全部释放掉 关闭套接字的文件描述符
	closesocket(sock);
	WSACleanup();
	return 0;
}

所以我们可以发现,windows客户端和linux客户端的区别在于,windows需要先启动winsocket并且初始化网络库,最后还需要手动释放使用库的相关资源,并且也要关闭套接字的文件描述符。

我们在运行的时候发现inet_addr这个函数会报错,原因是vs编译器认为这个函数不安全,大家可以将这个报错禁掉或者使用vs推荐的函数:

 注意:4996代表的是我刚说的报错信息,不要理解为可以禁掉任意报错,要禁什么需要看你报错的编号。

二、Tcp服务器的实现

首先tcp服务器的实现一定是比udp困难的,但是因为udp的特性是面向数据报所以在日常生活中使用的没有tcp广泛,所以tcp我们必须要掌握,和之前一样我们先创建需要用到的头文件,比如tcpserver.hpp,tcpserver.cc。

接下来我们先将服务端的框架写出来:

namespace server
{
    static const uint16_t gport = 8080;
    class TcpServer
    {

    public:
       TcpServer(const uint16_t& port = gport)
          :_port(port)
          ,_sock(-1)
       {

       }
       void initServer()
       {
          
       }
       void start()
       {
           
       }
       ~TcpServer()
       {

       }
    private: 
       int _sock;    
       uint16_t _port;
    };
}

 我们将udp绑定ip和端口号的时候说过,实际上一款服务器的启动只需要端口号,因为在绑定IP的时候我们会绑定任意ip,这样只要用户知道我们的端口号他就可以访问我们的服务器,所以我们的私有变量中没有ip只有port和文件描述符。在构造函数中我们直接给一个默认的端口号,这样我们启动的时候可以设置自己想用的端口号也可以直接用缺省的。然后我们将server.cc文件也写一下:

#include "TcpServer.hpp"
#include <memory>
using namespace server;
static void Usage(string proc)
{
    cout<<"\nUasge:\n\t"<<proc<<" port\n\n";
}
//./tcpserver port
int main(int argc,char* argv[])
{
    if (argc!=2)
    {
        Usage(argv[0]);
        exit(USE_ERR);
    }
    uint16_t port = atoi(argv[1]);
    unique_ptr<TcpServer> tsvr(new TcpServer(port));
    tsvr->initServer();
    tsvr->start();
    return 0;
}

这里还是和之前udp服务器一样,唯一要说明的是我们在服务器构造的时候给了缺省,其实一个参数就可以运行,但是由于后面要进行演示,我们还是按照./tcpserver port这样的启动方法来走。

然后我们再写一下客户端的框架:

namespace client
{
    class TcpClient
    {

    public:
      TcpClient(const string& serverip,const uint16_t& serverport)
        :_serverip(serverip)
        ,_serverport(serverport)
        ,_sock(-1)
      {

      }
      void initClient()
      {

      }
      void start()
      {
        
      }
      ~TcpClient()
      {

      }

    private:
      int _sock;
      string _serverip;
      uint16_t _serverport;
    };
}

同样的udp客户端的一样我们就不解释了,顺便也把client.cc写出来:

#include "TcpClient.hpp"
#include <memory>
using namespace client;
static void Usage(string proc)
{
    cout<<"\nUsage:\n\t"<<proc<<" serverip serverport\n\n";
}
// ./tcpclient serverip serverport
int main(int argc,char* argv[])
{
    if (argc!=3)
    {
        Usage(argv[0]);
        exit(1);
    }
    uint16_t serverport = atoi(argv[2]);
    string serverip = argv[1];
    unique_ptr<TcpClient> tcet(new TcpClient(serverip,serverport));
    tcet->initClient();
    tcet->start();
    return 0;
}

准备工作做完后,我们就开始进行服务器的初始化函数的编写:

我们第一步还是创建套接字,不过这次我们可以加入一个日志的功能,每次服务器启动可以告诉我们那些函数接口是否调用成功,所以我们再创建一个log.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;
}

我们将等级分为5个,0,1,2都可以算作正常的,3,4就说明是某部分写错或者函数运行失败,然后我们就先简单的打印一下,等后期Tcp服务器实现完了我们再给日志添加好玩的功能。

  void initServer()
       {
           //1.创建文件套接字对象
           _sock = socket(AF_INET,SOCK_STREAM,0);
           if (_sock==-1)
           {
              logMessage(FATAL,"create socket error");
              exit(SOCKET_ERR);
           }
           logMessage(NORMAL,"socket success");
           //2.进行bind
           struct sockaddr_in local;
           bzero(&local,sizeof(local));
           local.sin_family = AF_INET;
           local.sin_port = htons(_port);
           local.sin_addr.s_addr = INADDR_ANY; //INADDR_ANY绑定任意地址IP
           if (bind(_sock,(struct sockaddr*)&local,sizeof(local))<0)
           {
              logMessage(FATAL,"bind socket error");
              exit(BIND_ERR);
           }
           logMessage(NORMAL,"bind socket success"); 
       }

tcp服务器初始化的前两步与udp是一模一样的,都是先创建套接字,然后再bind。首先我们创建套接字,然后因为tcp是面向字节流的,所以socket的第二个参数我们选择sock_stream。如果创建失败,那么我们就向日志中打印信息,像这种使用接口失败的那么错误等级一定是严重错误fatal,然后我们还可以写一个枚举来保存所有的退出码。

    enum
    {
        SOCKET_ERR = 2
        ,USE_ERR
        ,BIND_ERR
        ,LISTEN_ERR
    };

如果创建套接字成功,我们就向日志中打印创建成功。绑定的时候我们可以看到我们将IP用INADDR_ANY绑定,这个选项的意思就是绑定任意IP,然后我们判断绑定是否成功,如果失败就向日志中写信息并且退出。下面我们将解Tcp服务器初始化与udp不一样的点:

 //3.Tcp需要将套接字状态设为listen状态来一直监听(因为Tcp是面向字节流的)
           if (listen(_sock,gbacklog)<0)
           {
               logMessage(FATAL,"listen socket error");
               exit(LISTEN_ERR);
           }
           logMessage(NORMAL,"listen socket success");
           
       }

首先一款Tcp服务器是面向链接的,当客户端要正常的向服务器发起请求的时候,客户端不能直接给服务器发送消息,而是需要先建立链接,这就意味着我们的服务器必须要时时刻刻准备接受客户端向我们发送的链接,那么如何做到呢?我们需要将socket设置为监听状态。

下面我们先看看listen这个接口的文档:

第一个参数是我们使用套接字返回的文件描述符,第二个参数是底层全链接长度+1,这里我们就不详细的解释第二个参数了,后面讲tcp原理的时候再详细的讲解。要使用这个参数我们首先定义一个变量:

 这个变量可以是5,10,20之类的不能太大。如果监听成功我们就像日志写入成功的信息。

这样我们就写完了tcp服务器初始化的接口,不知道大家有没有这样的疑问,为什么udp不需要监听呢?这是因为udp不需要链接,客户端发给我们的数据就是数据本身。由于tcp是面向链接的,所以tcp的第一步不是发数据而是建立链接(这就是Tcp的三次握手,后面讲原理的时候细谈)。

下面我们编写start接口:

对于一款服务器,一旦启动那么必定是死循环:

       void start()
       {
           for (;;)
           {
            
           }
       }

 那么启动后我们该干什么呢?在udp那里我们是接收客户端发来的消息,然后再将消息处理后发回给客户端,而对于tcp服务器我们刚刚也说了需要先建立链接,建立链接就需要用到accept接口:

注意:之前udp服务器中接收消息的recvfrom在tcp是用不了的。

第一个参数是一个文件描述符,后面两个参数是输出型参数,调用接口会接口会自动帮我们填充结构体,填充的结构体的信息是客户端的ip和端口等。accept的返回值是一个文件描述符,下面我们解释一下:

首先accept的后两个参数和recvfrom的后两个参数的含义是一模一样的,都是帮我们填充客户端的ip和端口号,最重要的是第一个参数,这个参数的含义是不一样的,因为accept的返回值是一个文件描述符,这个文件描述符和我们之前创建套接字返回的那个文件描述符是什么关系呢?我们在用listen接口的时候说过,将套接字设置为监听状态就可以一直监听客户端是否要给我们发现请求链接,而accept的第一个参数实际上就是监听的那个套接字,因为只有成功监听到客户端的请求链接我们才可以和客户端通信,所以accept返回的那个套接字才是我们真正用来和客户端通信的套接字,所以我们应该将刚开始创建的私有成员变量sock改名为listensock,因为这个变量只是起到监听新链接的作用。

 改名后我们就可以更容易理解这两个套接字的关系了。

       void start()
       {
           for (;;)
           {
              //4.server获取新链接  未来真正使用的是accept返回的文件描述符
              struct sockaddr_in peer;
              socklen_t len = sizeof(peer);
              //  sock是和client通信的fd
              int sock = accept(_listensock,(struct sockaddr*)&peer,&len);
              //accept失败也无所谓,继续让accept去获取新链接
              if (sock<0)
              {
                  logMessage(ERROR,"accept error,next");
                  continue;
              }
              logMessage(NORMAL,"accept a new link success");
              cout<<"sock: "<<sock<<endl;
              //5.用sock和客户端通信,面向字节流的,后续全部都是文件操作
              serviceIO(sock);
              //对于一个已经使用完毕的sock,我们要关闭这个sock,要不然会导致文件描述符泄漏
              close(sock);
           }
       }

要使用accept需要先创建结构体,然后拿到返回的套接字后我们可以打印一下这个套接字。注意:即使我们accept失败也无所谓,因为listensock会持续监听客户端的新链接,所以我们accept失败不能退出。当我们成功拿到和客户端通信需要的sock后,我们就要考虑和客户端通信了,这里我们专门写一个服务器与客户端通信的函数,将sock传入:

       void serviceID(int sock)
       { 
           while (true)
           {
             
           }
       }

 首先我们要能读到客户端发来的消息,所以我们直接用以前学文件用到的read接口:

 这个接口很简单,第一个参数是我们从哪个文件描述符里读,第二个参数是要读到哪个缓冲区,第三个参数是缓冲区的大小。

       void serviceID(int sock)
       {
           char buffer[1024];
           while (true)
           {
              ssize_t n = read(sock,buffer,sizeof(buffer)-1);
              if (n>0)
              {
                 //目前我们先把读到的数据当成字符串
                 buffer[n] = 0;
                 cout<<"recv message: "<<buffer<<endl;
              } 
           }
       }

我们定义一个缓冲区,然后如果读取成功就在前面加上"接收消息:"然后把消息打印出来,接下里我们再简单的处理一下数据把数据转回的客户端,注意:我们的只是方便演示用的字符串,实际上这里的数据可以是任意的,比如结构化的。

write接口我们也用过,第一个参数要向哪个文件描述符写入,第二个参数是写入的消息的缓冲区,第三个参数是缓冲区的大小。 

       void serviceIO(int sock)
       {
           char buffer[1024];
           while (true)
           {
              ssize_t n = read(sock,buffer,sizeof(buffer)-1);
              if (n>0)
              {
                 //目前我们先把读到的数据当成字符串
                 buffer[n] = 0;
                 cout<<"recv message: "<<buffer<<endl;
                 //将消息转回客户端
                 string outbuffer = buffer;
                 outbuffer+="[serverecho]";
                 write(sock,outbuffer.c_str(),outbuffer.size());  //多路转接解释write返回值
              }
              else if(n==0)
              {
                 //n==0说明客户端退出了
                 logMessage(NORMAL,"client quit,server me to!");
                 break;
              }
           }
       }

read的返回值是读到的数据的大小,如果读到0说明读到了文件结尾,既然读到了文件结尾那么客户端肯定是退出了。这就类似于管道,当写端不写了并且把文件描述符关闭了,我们的读端就会把数据读完后然后读到文件结尾返回0,所以读到0就代表客户端退出了。这也就是为什么我们在start中一旦serviceIO后就直接关闭了文件描述符,因为serviceIO是一个死循环,一旦循环退出就说明客户端退出了,既然客户端退出了那么我们的服务端当然要将与客户端通信的文件描述符关闭。(注意:对于已经使用完的文件套接字我们必须关闭,如果不关闭则会造成文件描述符泄漏)

下面我们编写客户端的代码:

客户端初始化同样也需要创建套接字,我们在udp的时候就说过,客户端一定要bind,但是不需要程序员明确的bind,在tcp中也一样。那么tcp客户端需要listen吗?当然不需要了,客户端又不是服务器,没人会链接客户端的所以不需要监听,那么需要accept吗?答案是也不需要,因为客户端没有人去链接所以不需要。

      void initClient()
      {
         //   1.创建套接字
         _sock = socket(AF_INET,SOCK_STREAM,0);
         if (_sock<0)
         {
            cout<<"socket error"<<endl;
            exit(2);
         }
         //  2.客户端要bind吗?必须要! 要程序员显式的bind吗?不需要
         //  3.客户端要listen吗?不需要,没人去连客户端所以不需要
         //  4.客户端要accept吗?不需要
         //  5.客户端要发起链接。
      }

没错,我们客户端初始化的代码非常的简洁,就只需要创建套接字即可。

那么客户端启动需要干什么呢?实际上就是我们的第5点,我们要发起链接。

下面我们认识一下connect接口:

 第一个参数是文件描述符,第二个参数和第三个参数是我们要传结构体,这个结构体是我们要和哪个服务器建立链接里面就是哪个服务器的ip和port。仔细看我们红色圈出来的部分就会发现,我们刚刚初始化的时候说客户端不用显式的bind,可以看到在connect接口中会自动帮我们绑定,如果绑定成功会返回0.

      void start()
      {
         struct sockaddr_in server;
         bzero(&server,sizeof(server));
         server.sin_family = AF_INET;
         server.sin_port = htons(_serverport);
         server.sin_addr.s_addr = inet_addr(_serverip.c_str());
         //connet的时候操作系统会帮客户端bind   返回值等于0成功
         if (connect(_sock,(struct sockaddr*)&server,sizeof(server))!=0)
         {
             cerr<<"socket connect error"<<endl;
         }
         else 
         {
            //链接成功客户端要干什么?与服务端通信
         }
      }

我们首先填充结构体,将服务端的ip和port填充完毕后,我们就可以进行连接,如果连接失败我们就打印错误,只有成功我们才开始与服务端通信,那么如何通信呢?实际上与服务端accept后一样,因为tcp是面向字节流的,所以我们通信的过程都是文件操作。

   else 
         {
            //链接成功客户端要干什么?与服务端通信
            string message;
            while (true)
            {
                cout<<"Enter# ";
                getline(cin,message);
                //将消息发送给服务端
                write(_sock,message.c_str(),message.size());

                //读取服务端给我们发送的消息
                char buffer[1024];
                int n = read(_sock,buffer,sizeof(buffer)-1);
                if (n>0)
                {
                    buffer[n] = 0;
                    cout<<"Server回显# "<<buffer<<endl;
                }
                else 
                {
                    break;
                }
            }
         }

这里就是死循环式的给服务器发消息,我们直接用getline将cin中输入的消息保存到string中,然后将这个消息写入文件描述符,接下来我们还需要读取服务端给我们发送的消息,因为read接口需要缓冲区,所以我们定一个缓冲区,如果read的返回值大于0说明消息读到缓冲区中我们就在读取到字节数的位置放一个\0,然后打印这个消息,如果返回值等于0说明写端不写入了并且关闭了文件描述符,所以我们就直接退出循环并且关闭文件描述符,我们这里就直接用析构函数关闭即可。

      ~TcpClient()
      {
         if (_sock!=-1)
         {
            close(_sock);
         }
      }

下面我们就运行起来演示一下:

 运行后我们可以用netstat命令查看服务器信息,n代表将端口号等信息用数字显示,l代表监听,tp代表tcp,下面我们开启客户端:

 大家可以猜一下为什么我们创建的套接字是4号呢?首先我们都知道OS会默认打开3个文件描述符分别是0,1,2,那么我们为什么不是创建的3呢不是说文件描述符对应数组的下标吗?这是因为我们服务器中创建listensock套接字用了3号,所以我们调用accept创建的套接字是4号套接字。

 当我们用客户端连接上服务端后查询所有的tcp发现有两个服务端:

 这是因为我们当前是在本地通信的,所以有两个链接,客户端到服务端,服务端到客户端。正常情况下都是不同的电脑进行连接,这种情况下查看就只有一条链接了。

当然我们在写代码的时候其实故意留了一个问题,如果有多个客户端发消息是这样的:

 为什么只有先链接的那个客户端可以发消息呢?

 当我们将先链接的那个客户端退出后为什么消息一股脑全发来了,这是因为我们当时写代码的时候只针对一个客户端,谁进来谁就死循环式的收发消息:

 只有当一个客户端退出了另一个客户端才会收到消息,在下一篇文章中我们将用多进程,多线程,线程池版本的serviceIO,届时这种问题就不会存在了。


总结

tcp服务器的实现相较于udp服务器并没有多多少东西,所以只有udp明白了那么简单的tcp服务器还是很好实现的,下一篇才是我们tcp服务器的重点。

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

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

相关文章

spring-事务

spring-事务 事务( transaction)是访问并可能操作各种数据项的一个数据库操作序列&#xff0c;这些操作要么全部执行,要么全部不执行&#xff0c;是一个不可分割的工作单位。事务由事务开始与事务结束之间执行的全部数据库操作组成&#xff0c;它具有ACID特性。 为了在spring中…

UE特效案例 —— 骷髅爆点

一&#xff0c;环境配置 创建默认地形Landscape&#xff0c;如给地形上材质需确定比例&#xff1b;添加环境主光源DirectionalLight&#xff0c;设置相应的强度和颜色&#xff1b;添加天光反射SkyLight&#xff0c;用于天空反射&#xff1b;添加指数级高度雾ExponentialHeightF…

C++笔记之循环引用与环状引用

C笔记之循环引用与环状引用 code review! 文章目录 C笔记之循环引用与环状引用1.C循环引用和环状引用描述的是同一种现象吗?2.环状引用3.环状引用现象举例——常见环状引用4.解决环状引用——使用前向声明5.环状引用现象举例——使用对象本身可能造成环状引用 1.C循环引用和…

MySQL_4

目录 一、日志 1、错误日志 2、二进制日志 2.1 介绍 2.2 格式 2.3 删除 3、查询日志 4、慢查询日志 二、主从复制 1、概述 2、原理 3、搭建 三、分库分表 1、介绍 1.1背景 1.2拆分策略 1.3垂直拆分 1.4水平拆分 1.5实现技术 2、Mycat概述 2.1 介绍 2.2 安装 2.3…

每日一题2023.7.22|链表的基本操作

链表的基本操作 题目描述 考察链表的基本操作 输入 输入数据只有一组&#xff0c;第一行有n1个整数&#xff0c;第一个整数是这行余下的整数数目n&#xff0c;后面是n个整数。这一行整数用来初始化列表的&#xff0c;并且输入的顺序与列表中的顺序相反&#xff0c;也就是说如果…

htmlCSS-----浮动

目录 前言&#xff1a; 浮动 1.浮动的效果 2.浮动的特点 3.浮动的写法 4.浮动的原理 5.浮动的作用 6.案例 7.浮动的缺陷与解决方式 浮动问题 解决方式 8.补充说明 前言&#xff1a; 浮动是html里面重要的一部分&#xff0c;前面我们学习了三种元素的类型&#xff08;…

element中table的表格更新数据之后保留原来的勾选状态

reserve-selection: 仅对 typeselection 的列有效&#xff0c;类型为 Boolean&#xff0c;为 true 则会在数据更新之后保留之前选中的数据&#xff08;需指定 row-key&#xff09; <el-table ref"table" :data"DataList" :row-key"rowKey"&g…

使用rknn-toolkit2把YOLOV5部署到OK3588上

使用rknn-toolkit2把YOLOV5部署到OK3588上 虚拟环境搭建软件包安装在PC机上运行yolov5目标检测 虚拟环境搭建 首先在PC的ubuntu系统安装虚拟环境&#xff1a; 我的服务器是ubuntu18.04版本&#xff0c;所以安装python3.6 conda create -n ok3588 python3.6 需要键盘输入y&…

【博客681】k8s list机制与resourceVersion语义

k8s list机制与resourceVersion语义 K8s 架构&#xff1a;环形层次视图 对于 K8s 集群&#xff0c;从内到外的几个组件和功能&#xff1a; etcd&#xff1a;持久化 KV 存储&#xff0c;集群资源&#xff08;pods/services/networkpolicies/…&#xff09;的唯一的权威数据&…

实例023 建立字体形状窗体

实例说明 大家都见过不规则形状的窗体吧&#xff0c;那么如何制作一个文字形的窗体呢&#xff1f;文字形窗体一般应用在屏幕提示中&#xff0c;如收款机屏幕等。运行本例&#xff0c;效果如图1.23所示。 技术要点 以前&#xff0c;创建字体形窗体是一个既费时又费人力的过程&…

K8S初级入门系列之七-控制器(Job/CronJob/Daemonset)

一、前言 前一章节我们介绍了RC&#xff0c;RS控制器&#xff0c;其主要针对在线业务Pod部署&#xff0c;比如nginx&#xff0c;这些业务是需要确保7*24持续运行的&#xff0c;还有一类离线业务&#xff0c;比如定时任务&#xff0c;大数据离线计算等&#xff0c;在有任务的才需…

在命令行模式、eclipse console下执行Java程序输入中文的几种情况尝试

介绍 在命令行模式下执行Java程序&#xff0c;如果输入中文&#xff0c;经常会出现和代码中的解码字符集不匹配的情况&#xff0c;导致结果不正确。 在命令行模式下执行Java程序&#xff0c;输入中文&#xff0c;其实是用某种字符集编码成字节流&#xff0c;Java程序读取该字节…

rk3588 双HDMI冲突问题与HDMI不能热插拔问题

问题一:HDMI不能热插拔问题 现象 rk3588在开发时现在发现只能在插入HDMI时上电才能输出信号,而当开机之后,再插入HDMI显示器则无信号。 分析 通过kernel显示,在开机后,插拔HMDI是有log提示的,说明内核能够检测到HDMI的插拔动作。 首先先排查软件问题,尝试重启显示进…

ssm项目配置,不再支持源选项 5。请使用 6 或更高版本。 不再支持目标选项 1.5。请使用 1.6 或更高版本。

ssm项目启动报错&#xff1a; 不再支持源选项 5。请使用 6 或更高版本。 不再支持目标选项 1.5。请使用 1.6 或更高版本 1.模块语言级别 2.设置模块目标字节码版本 3.pom文件指定jdk版本 <properties><project.build.soutceEncoding>UTF-8</project.build.sout…

SpringBoot3自动配置流程 SPI机制 核心注解 自定义starter

1. 自动配置流程 导入starter依赖导入autoconfigure寻找类路径下 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件启动&#xff0c;加载所有 自动配置类 xxxAutoConfiguration 给容器中配置功能组件组件参数绑定到 属性类中。xxxPrope…

运维高级学习---MySQL主从复制

MySQL内建的复制功能是构建大型&#xff0c;高性能应用程序的基础 通过将MySQL的某一台主机 (master)的数据复制到其他主机(slaves)上&#xff0c;并重新执行一遍来执行复制过程中一台服务器充当主服务器&#xff0c;而其他一个或多个其他服务器充当从服务器 为什么要做主从复…

用eNSP搭建一个最简单的网络系统

要求搭建如下网络系统&#xff1a; 相关知识&#xff1a; 路由器命令界面&#xff1a; 所有的设备进入后均名字为Huawai&#xff1b; < >代表了当下所在的配置模式&#xff1b; 不同模式具有不同的管理权限&#xff0c;可以完成不同的配置要求&#xff1b; 第一级模式 …

Excel 端口操作指南

通过将 EDI 报文可视化为 Excel&#xff0c;企业可以更好地了解和处理数据&#xff0c;提高工作效率&#xff0c;减少错误率。在未实现 EDI 系统和内部业务系统集成之前&#xff0c;Excel 方案则是一项可供选择的临时替代方案。 Excel方案的优点在于&#xff0c;无需对业务系统…

【深度学习笔记】随机梯度下降法

本专栏是网易云课堂人工智能课程《神经网络与深度学习》的学习笔记&#xff0c;视频由网易云课堂与 deeplearning.ai 联合出品&#xff0c;主讲人是吴恩达 Andrew Ng 教授。感兴趣的网友可以观看网易云课堂的视频进行深入学习&#xff0c;视频的链接如下&#xff1a; 神经网络和…

Qt图片编辑 - 在直线添加文字

在绘制一条直线时&#xff0c;比如说在直线中间输出文字&#xff0c;如下图所示 本质上不难&#xff0c;定位到位置&#xff0c;然后drawText就可以了 难就难在 文字要跟随线的斜率&#xff0c;例如 还有&#xff0c;文字最好保证在线的“上方” 首先是角度问题 这个角度跟线…