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

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.进行bindstruct 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绑定任意地址IPif (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通信的fdint 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.mzph.cn/news/6545.shtml

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

相关文章

【Flume 01】Flume简介、部署、组件

1 Flume简介 Flume是一个高可用、高可靠、分布式的海量日志采集、聚合和传输的系统 主要特性&#xff1a; 它有一个简单、灵活的基于流的数据流结构&#xff08;使用Event封装&#xff09;具有负载均衡机制和故障转移机制一个简单可扩展的数据模型(Source、Channel、Sink) Sou…

Zookeeper的基本概念以及安装

Zookeeper简介 Zookeeper是一个分布式的(多台机器同时干一件事情),开源的分布式应用程序协调服务,是Google公司Chubby产品,是Hadoop和Base重要的组件,.它是一个分布式应用程序提供一致性的服务的软件,提供的功能包括:配置服务,域名服务,分布式同步,组服务等 Zookeeper目…

多肽试剂1801415-23-5,Satoreotide,UNII-S58172SSTS,应用在多肽标记及修饰上

资料编辑|陕西新研博美生物科技有限公司小编MISSwu​ Satoreotide&#xff0c;UNII-S58172SSTS Product structure Product specifications 1.CAS No&#xff1a;1801415-23-5 2.Molecular formula&#xff1a;C58H72ClN15O14S2 3.Molecular weight&#xff1a;1302.9 4.Packa…

手机word文档怎么转换成pdf?分享两种方法

手机word文档怎么转换成pdf&#xff1f;在如今信息化的时代&#xff0c;电子文档已经成为人们日常办公不可或缺的一部分。随着科技的不断进步&#xff0c;电子文档的格式也在不断发展。PDF作为电子文档的一种重要格式&#xff0c;被广泛使用。那么&#xff0c;如何将手机上的Wo…

一)Stable Diffusion使用教程:安装

目前AI绘画最火的当属Midjorney和Stable Diffusion&#xff0c;但是由于Midjourney没有开源&#xff0c;因此我们主要分享下Stable Diffusion&#xff0c;后面有望补上Midjourney教程。 本节主要讲解Stable Diffusion&#xff08;以下简述SD&#xff09;的下载和安装。 1&…

MyBatis学习笔记——2

MyBatis学习笔记——2 一、MyBatis核心配置文件详解1.1、environment&#xff08;环境&#xff09;1.2、transactionManager&#xff08;事务管理器&#xff09;1.3、dataSource&#xff08;数据源&#xff09;1.4、properties1.5、mapper 二、在WEB中应用MyBatis&#xff08;使…

轮播图添加删除

轮播图页面和对话框搭建 页面简单布局 <template><div id"banner"><el-space direction"vertical" :size"20" style"width: 100%"><h1>轮播图管理</h1><div style"text-align: right"&g…

商城-学习整理-基础-分布式组件(三)

目录 一、前言二、Spring Cloud&Spring Cloud Alibaba1、Spring Cloud 与Spring Cloud Alibaba简介2、为什么使用Spring Cloud Alibaba3、版本选择4、项目中的依赖 三、Spring Cloud Alibaba-Nacos作为注册中心1、Nacos1&#xff09;、下载 nacos-server2&#xff09;、启动…

42. 接雨水

42.接雨水 这是一个简单的动态规划问题&#xff0c;虽然leetcode将它归结为困难。 但是我感觉它难度应该达不到&#xff0c;可能归结为中等比较合适0x1 题目 给定 n 个非负整数表示每个宽度为 1 的柱子的高度图&#xff0c;计算按此排列的柱子&#xff0c;下雨之后能接多少雨…

python flask 返回中文乱码

使用flask返回数据中带有中文的时候会显示成乱码(ascii)&#xff0c; 中文报文&#xff1a; ABAP中的三大财务报表是&#xff1a;* **资产负债表** * **收入证明** * **现金流量表**这些报表全面概述了公司的财务状况和业绩。* **资产负债表**显示公司在特定时间点的资产、负…

【Python数据分析】Python基础知识篇

&#x1f389;欢迎来到Python专栏~Python基础知识篇 ☆* o(≧▽≦)o *☆嗨~我是小夏与酒&#x1f379; ✨博客主页&#xff1a;小夏与酒的博客 &#x1f388;该系列文章专栏&#xff1a;Python学习专栏 文章作者技术和水平有限&#xff0c;如果文中出现错误&#xff0c;希望大…

LeetCode[1508]子数组和排序后的区间和

难度&#xff1a;Medium 题目&#xff1a; 给你一个数组 nums &#xff0c;它包含 n 个正整数。你需要计算所有非空连续子数组的和&#xff0c;并将它们按升序排序&#xff0c;得到一个新的包含 n * (n 1) / 2 个数字的数组。 请你返回在新数组中下标为 left 到 right &#…

将请求参数数据推送至RabbitMQ队列中并且捕捉消息没有到达交换机的异常

1&#xff1a;自定义mq信息类&#xff08;我的交换这些信息都从nacos上直接取的&#xff0c;怎么从nacos取配置信息看上篇文章&#xff09;&#xff1a; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor;impo…

GB2312编码

说明 GB2312代码表用两个字节表示一个图形字符&#xff0c;两个字节中前面的字节为第一字节&#xff0c;后面的字节为第二字节。 区位码&#xff1a; GB2312代码表分成94个区&#xff0c;范围为1-94&#xff0c;区的编号由第一字节表示&#xff1b;每个区有94位&#xff0c;范…

AE 3D粒子插件trapcode particular 新版本

Trapcode Particular for Mac是目前AE系列的插件中最火爆最流行的一款三维粒子插件&#xff0c;是属于Red Giant Trapcode Suite&#xff08;红巨人粒子特效套装插件&#xff09;中的一款粒子插件。该软件提供了多达一百余种粒子效果供用户使用&#xff0c;可以产生各种各样的自…

【mac系统】mac系统调整妙控鼠标速度

当下环境&#xff1a; mac系统版本&#xff0c;其他系统应该也可以&#xff0c;大家可以自行试下&#xff1a; 鼠标 mac妙控鼠标&#xff0c;型号A1657 问题描述&#xff1a; 通过mac系统自带的鼠标速度调节按钮&#xff0c;调到最大后还是感觉移动速度哦过慢 问题解决&…

【Docker】什么是Docker呢?本文带你洞悉

&#x1f680;欢迎来到本文&#x1f680; &#x1f349;个人简介&#xff1a;陈童学哦&#xff0c;目前专攻C/C、Python、Java等方向&#xff0c;一个正在慢慢前行的普通人。 &#x1f3c0;系列专栏&#xff1a;陈童学的日记 &#x1f4a1;其他专栏&#xff1a;CSTL、蓝桥杯&am…

学习day49

computed和watch的区别 1.computed能完成的功能&#xff0c;watch都可以实现 2.watch能完成的&#xff0c;computed不一定能实现&#xff0c;例如&#xff1a;watch可以进行异步操作 两个重要的小原则 1.所被Vue管理的函数&#xff0c;最好写成普通函数&#xff0c;这样this的指…

90道渗透测试面试题(附答案)

2023年已经快过去一半了&#xff0c;不知道小伙伴们有没有找到自己心仪的工作呀。最近后台收到不少小伙伴说要我整理一些渗透测试的面试题&#xff0c;今天它来了&#xff01;觉得对你有帮助的话记得点个赞再走哦~ 1、什么是渗透测试&#xff1f; 渗透测试是一种评估计算机系统…

【MATLAB】 二维绘图,三维绘图的方法与函数

目录 MATLAB的4种二维图 1.线图 2.条形图 3.极坐标图 4.散点图 三维图和子图 1.三维曲面图 2.子图 MATLAB的4种二维图 1.线图 plot函数用来创建x和y值的简单线图 x 0:0.05:30; %从0到30&#xff0c;每隔0.05取一次值 y sin(x); plot(x,y) %若(x,y,LineWidth,2) 可…