【Hello Network】网络编程套接字(二)

news2024/11/15 8:33:10

作者:@小萌新
专栏:@网络
作者简介:大二学生 希望能和大家一起进步
本篇博客简介:简单介绍网络的基础概念

网络编程套接字(二)

  • 简单的TCP网络程序
    • 服务端创建套接字
    • 服务端绑定
    • 服务器监听
    • 服务端获取连接
    • 服务端处理请求
    • 客户端创建套接字
    • 客户端发起请求
    • 客户端发起请求
    • 服务器测试

简单的TCP网络程序

服务端创建套接字

我们使用一个类来封装服务端 当我们定义一个服务器对象之后马上就进行初始化 初始化TCP服务器第一时间就要创建套接字

我们在使用TCP服务的时候用socket函数创建套接字 参数设置如下

int socket(int domain, int type, int protocol);
  • 协议家族选择AF_INET 因为我们要进行的是网络通信
  • 创建套接字时所需的服务类型应该是SOCK_STREAM 因为我们编写的是TCP服务器 SOCK_STREAM提供的就是一个有序的、可靠的、全双工的、基于连接的流式服务
  • 协议类型默认设置为0即可 它会自动推导服务

如果创建套接字后获得的文件描述符是小于0的 说明套接字创建失败 此时也就没必要进行后续操作了 直接终止程序即可

class TcpSever    
{    
  private:    
    int _sockfd;    
  public:    
    void Init()    
    {                                                                                                                          
      _sockfd = socket(AF_INET, SOCK_STREAM , 0);    
      if (_sockfd < 0)    
      {    
        cout << "socket error" << endl;    
        exit(2);    
      }    
    }    
    ~TcpSever()    
    {    
      if (_sockfd > 0)    
      {    
         close(_sockfd);    
      }    
    }    
}; 

这里需要注意的是:

  • 我们创建TCP套接字和UDP套接字的做法是一样的 只不过创建TCP是流式服务 而UDP是数据报服务
  • 当我们析构服务器时 可以将服务器对应的文件描述符关闭

服务端绑定

套接字创建完毕之后我们实际上只是在系统层面上打开了一个文件 该文件还没有和网络关联起来 因此我们创建之后还需要使用bind函数绑定

绑定步骤如下

  • 定义一个struct sockaddr_in结构体,将服务器网络相关的属性信息填充到该结构体当中,比如协议家族、IP地址、端口号等。
  • 填充服务器网络相关的属性信息时,协议家族对应就是AF_INET,端口号就是当前TCP服务器程序的端口号。在设置端口号时,需要调用htons函数将端口号由主机序列转为网络序列。
  • 在设置服务器的IP地址时,我们可以设置为本地环回127.0.0.1,表示本地通信。也可以设置为公网IP地址,表示网络通信。
  • 如果使用的是云服务器,那么在设置服务器的IP地址时,不需要显示绑定IP地址,直接将IP地址设置为INADDR_ANY即可,此时服务器就可以从本地任何一张网卡当中读取数据。此外,由于INADDR_ANY本质就是0,因此在设置时不需要进行网络字节序的转换。
  • 填充完服务器网络相关的属性信息后,需要调用bind函数进行绑定。绑定实际就是将文件与网络关联起来,如果绑定失败也没必要进行后续操作了,直接终止程序即可。

由于TCP服务器初始化时需要服务器的端口号,因此在服务器类当中需要引入端口号,当实例化服务器对象时就需要给传入一个端口号。而由于我当前使用的是云服务器,因此在绑定TCP服务器的IP地址时不需要绑定公网IP地址,直接绑定INADDR_ANY即可,因此我这里没有在服务器类当中引入IP地址。

class TcpSever    
{    
  private:    
    int _sockfd;    
    int _port;    
  public:    
    void Init()    
    {    
      _sockfd = socket(AF_INET, SOCK_STREAM , 0);    
      if (_sockfd < 0)    
      {    
        cout << "socket error" << endl;    
        exit(2);    
      }    
    
      struct sockaddr_in local;    
      memset(&local , 0 , sizeof(local));    
      local.sin_family = AF_INET;    
      local.sin_port = htons(_port);    
      local.sin_addr.s_addr = INADDR_ANY;    
      if(bind(_sockfd , (struct sockaddr*)&local , sizeof(sockaddr)) < 0)    
      {    
        cout << "bind error" << endl;    
        exit(3);    
      }    
    }    
  public:    
    TcpSever(int port)    
      :_sockfd(-1),    
      _port(port)    
    {}    
    ~TcpSever()    
    {                                                                                                                                                                             
      if (_sockfd > 0)    
      {    
         close(_sockfd);    
      }    
    }    
};   

在这之后我们的服务端绑定便完成了 此时我们会发现TCP和UDP的创建套接字和绑定步骤没有任何的区别

我们真正有区别的是下一步 服务器监听

服务器监听

因为TCP服务器是面向连接的 客户端在正式向TCP服务器发送数据之前需要建立连接

因此TCP服务器需要随时注意是否有客户端的连接请求 此时我们需要将状态设置为监听状态

listen函数

设置套接字为监听状态的函数叫做listen 该函数的函数原型如下:

  int listen(int sockfd, int backlog);

返回值说明:

  • 监听成功返回0,监听失败返回-1,同时错误码会被设置。

参数说明:

  • sockfd:需要设置为监听状态的套接字对应的文件描述符。
  • backlog:全连接队列的最大长度。如果有多个客户端同时发来连接请求,此时未被服务器处理的连接就会放入连接队列,该参数代表的就是这个全连接队列的最大长度,一般不要设置太大,设置为5或10即可。

服务器监听

我们在创建完套接字和绑定之后 需要再进一步将状态设置为监听状态 监听后续是否有新的连接 如果监听失败就意味着TCP无法接受服务器发送的请求了 此时服务器也没有了启动的意义 直接退出即可

    void Init()    
    {    
      _sockfd = socket(AF_INET, SOCK_STREAM , 0);    
      if (_sockfd < 0)    
      {    
        cout << "socket error" << endl;    
        exit(2);    
      }    
    
      struct sockaddr_in local;    
      memset(&local , 0 , sizeof(local));    
      local.sin_family = AF_INET;    
      local.sin_port = htons(_port);    
      local.sin_addr.s_addr = INADDR_ANY;    
      if(bind(_sockfd , (struct sockaddr*)&local , sizeof(sockaddr)) < 0)    
      {    
        cout << "bind error" << endl;    
        exit(3);    
      }    
    
      if(listen(_sockfd , 5) < 0)    
      {    
        cout << "listen error" << endl;    
        exit(4);                                                                                                                                                                  
      }    
    } 

我们在初始化TCP服务器的时候只有在创建套接字完毕绑定成功

服务端获取连接

TCP服务器初始化后就可以开始运行了,但TCP服务器在与客户端进行网络通信之前,服务器需要先获取到客户端的连接请求。

accept函数

获取连接的函数叫做accept,该函数的函数原型如下:

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

返回值说明:

  • 获取连接成功返回接收到的套接字的文件描述符,获取连接失败返回-1,同时错误码会被设置。

参数说明:

  • sockfd:特定的监听套接字,表示从该监听套接字中获取连接。
  • addr:对端网络相关的属性信息,包括协议家族、IP地址、端口号等。
  • addrlen:调用时传入期望读取的addr结构体的长度,返回时代表实际读取到的addr结构体的长度,这是一个输入输出型参数。

accept函数返回的套接字是什么?

调用accept函数获取连接时,是从监听套接字当中获取的。如果accept函数获取连接成功,此时会返回接收到的套接字对应的文件描述符。

监听套接字与accept函数返回的套接字的作用:

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

服务端获取连接

服务端在获取连接时需要注意:

  • accept函数获取连接时可能会失败,但TCP服务器不会因为获取某个连接失败而退出,因此服务端获取连接失败后应该继续获取连接。
  • 如果要将获取到的连接对应客户端的IP地址和端口号信息进行输出,需要调用inet_ntoa函数将整数IP转换成字符串IP,调用ntohs函数将端口号由网络序列转换成主机序列。
  • inet_ntoa函数在底层实际做了两个工作,一是将网络序列转换成主机序列,二是将主机序列的整数IP转换成字符串风格的点分十进制的IP。
    void Start()    
    {    
      for(;;)    
      {    
        struct sockaddr_in peer;    
        memset(&peer , 0 , sizeof(peer));    
        socklen_t len = sizeof(peer);    
        int sock = accept(_sockfd , (struct sockaddr*)&peer , &len);    
        if (sock < 0)    
        {    
          cout << "accept error" << endl;    
          continue; // do not stop server     
        }    
    
        string client_ip = inet_ntoa(peer.sin_addr);    
        int client_port = ntohs(peer.sin_port);    
        cout << "get a new link " << sock <<  "new port is " << client_port <<endl ;    
                                                                                                                                                                                  
      }    
    } 

服务端接受连接测试

我们现在做个测试 看看当前服务器能够接受请求

我们在服务器运行的时候传入一个端口号作为我们的服务端口 服务端初始化之后启动

编译代码后 我们使用8082端口号初始化服务器

在这里插入图片描述

服务端运行之后我们可以通过netstat命令查看服务

在这里插入图片描述
它绑定的端口就是8082 而由于服务器绑定的是INADDR_ANY 因此该服务器的本地IP地址是0.0.0.0 这就意味着该TCP服务器可以读取本地任何一张网卡里面的数据

此时最重要的是服务器状态处于listen状态

虽然我们现在还没有编写客户端相关的代码 但是我们现在已经能登录这个服务器了

我们可以使用telnet指令来登录当前服务器 因为itelntt指令底层就是使用tcp实现的

在这里插入图片描述

我们发现此时分配的文件描述符是4 这是因为在运行一个C++程序的时候默认会打开0 1 2 文件输入流 文件输出流 文件错误流

而3号文件描述符在初始化时分配给了监视套接字 因此当一个客户端发起连接请求的时候 为该客户端提供服务的文件套接字就是4

服务端处理请求

现在TCP服务器已经能够获取连接请求了 下面当然就是要对获取到的连接进行处理

但此时为客户端提供服务的不是监听套接字 因为监听套接字获取到一个连接后会继续获取下一个请求连接 为对应客户端提供服务的套接字实际是accept函数返回的套接字 下面就将其称为“服务套接字”

为了让通信双方都能看到对应的现象 我们这里就实现一个简单的回声TCP服务器 服务端在为客户端提供服务时就简单的将客户端发来的数据进行输出 并且将客户端发来的数据重新发回给客户端即可

当客户端拿到服务端的响应数据后再将该数据进行打印输出 此时就能确保服务端和客户端能够正常通信了

read函数

TCP服务器读取数据的函数叫做read,该函数的函数原型如下:

ssize_t read(int fd, void *buf, size_t count);

返回值说明:

  • 如果返回值大于0,则表示本次实际读取到的字节个数。
  • 如果返回值等于0,则表示对端已经把连接关闭了。
  • 如果返回值小于0,则表示读取时遇到了错误。

参数说明:

  • fd:特定的文件描述符,表示从该文件描述符中读取数据。
  • buf:数据的存储位置,表示将读取到的数据存储到该位置。
  • count:数据的个数,表示从该文件描述符中读取数据的字节数。

read返回值为0表示对端连接关闭

这实际和本地进程间通信中的管道通信是类似的,当使用管道进行通信时,可能会出现如下情况:

  • 写端进程不写,读端进程一直读,此时读端进程就会被挂起,因为此时数据没有就绪。
  • 读端进程不读,写端进程一直写,此时当管道被写满后写端进程就会被挂起,因为此时空间没有就绪。
  • 写端进程将数据写完后将写端关闭,此时当读端进程将管道当中的数据读完后就会读到0。
  • 读端进程将读端关闭,此时写端进程就会被操作系统杀掉,因为此时写端进程写入的数据不会被读取。

这里的写端就对应客户端,如果客户端将连接关闭了,那么此时服务端将套接字当中的信息读完后就会读取到0,因此如果服务端调用read函数后得到的返回值为0,此时服务端就不必再为该客户端提供服务了。

write函数

TCP服务器写入数据的函数叫做write,该函数的函数原型如下:

ssize_t write(int fd, const void *buf, size_t count);

返回值说明:

  • 写入成功返回实际写入的字节数,写入失败返回-1,同时错误码会被设置。

参数说明:

  • fd:特定的文件描述符,表示将数据写入该文件描述符对应的套接字。
  • buf:需要写入的数据。
  • count:需要写入数据的字节个数。

当服务端调用read函数收到客户端的数据后,就可以再调用write函数将该数据再响应给客户端。

服务端处理请求

需要注意的是,服务端读取数据是服务套接字中读取的,而写入数据的时候也是写入进服务套接字的。也就是说这里为客户端提供服务的套接字,既可以读取数据也可以写入数据,这就是TCP全双工的通信的体现。

在从服务套接字中读取客户端发来的数据时,如果调用read函数后得到的返回值为0,或者读取出错了,此时就应该直接将服务套接字对应的文件描述符关闭。因为文件描述符本质就是数组的下标,因此文件描述符的资源是有限的,如果我们一直占用,那么可用的文件描述符就会越来越少,因此服务完客户端后要及时关闭对应的文件描述符,否则会导致文件描述符泄漏。

      void Service(int sock)    
      {    
        char buff[1024];    
        while(true)    
        {    
          ssize_t size = read(sock , buff , sizeof(buff)-1);    
          if (size > 0)                                                                                               
          {    
            buff[size] = 0;    
            write(sock , buff , size);    
          }    
          else if (size == 0) // 对端关闭    
          {    
            close(sock);    
            cout << "read cloes " << endl;    
            break;    
          }    
          else    
          {    
            cout << "error : " << errno  << endl;    
            break;    
          }    
        }    
      }  

客户端创建套接字

同样的 我们也可以将客户端封装成一个类 当我们需要一个客户端的时候将其进行实例化

而我们客户端只需要创建套接字就可以 至于绑定和监听则不需要

为什么客户端不需要绑定和监听

  • 客户端不需要绑定 因为客户端的port可以是随机的 服务端需要固定是因为客户端需要一个确定的port才能找到服务端
  • 客户端不需要监听 因为客户端不需要接受除了服务端之外任何人的连接

此外 客户端必须要知道它要连接的服务端的IP地址和端口号 因此客户端除了要有自己的套接字之外 还需要知道服务端的IP地址和端口号 这样客户端才能够通过套接字向指定服务器进行通信

  class TcpClient    
  {    
    private:    
      int _sockfd;    
      int _sever_port;    
      string _sever_ip;    
    public:    
      TcpClient(string ip , int port)    
        :_sever_ip(ip),    
        _sever_port(port),    
        _sockfd(-1)    
      {}    
      ~TcpClient()    
      {    
        if(_sockfd >= 0)    
        {    
          close(_sockfd);    
        }    
      }    
    public:    
      void ClientInit()    
      {    
        _sockfd = socket(AF_INET , SOCK_STREAM , 0);    
        if (_sockfd < 0)    
        {    
          cout << "ClientInit error" << endl;    
          exit(1);                                                                                                                       
        }    
      }    
  }; 

客户端发起请求

由于我们的客户端不需要绑定监听 所以当创建完毕之后就可以开始处理请求了 我们使用connect函数来处理请求

connect函数

该函数原型如下

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

返回值说明:

  • 如果连接成功则返回0 如果失败则返回-1 错误码被设置

参数说明:

  • sockfd:特定的套接字,表示通过该套接字发起连接请求。
  • addr:对端网络相关的属性信息,包括协议家族、IP地址、端口号等。
  • addrlen:传入的addr结构体的长度。

客户端连接服务器

这里需要特别注意的一点是 客户端只是不自己绑定端口号和ip 并不是不需要端口号和ip

因为通信双方都必须要有IP地址和端口号 否则无法唯一标识通信双方

也就是说 如果connect函数调用成功了 客户端本地会随机给该客户端绑定一个端口号发送给对端服务器

但是我们进行连接的时候传入的并不是客户端的IP和PORT 而是服务器的 因为我们要连接的是服务器 在连接的时候客户端的IP和PORT会自动传给服务器

      void ClientStart()    
      {    
        struct sockaddr_in peer;    
        memset(&peer , 0 , sizeof(peer));    
      
        peer.sin_family = AF_INET;    
        peer.sin_port = htons(_sever_port);    
        peer.sin_addr.s_addr = inet_addr(_sever_ip.c_str());    
      
        if (connect(_sockfd , (struct sockaddr*)&peer , sizeof(peer)) == 0)    
        {    
          cout << "connect success" << endl;    
          // request                                                                                                                     
        }    
        else    
        {    
          cout << "connect fail" << endl;    
          exit(2);    
        }    
      }  

客户端发起请求

由于我们实现的是一个简单的回声服务器 因此当客户端连接到服务端之后就可以向服务端发送数据了 这里我们可以将客户端发送的数据利用write函数发送给服务端

当我们的客户端发送数据给服务端之后 由于服务端读取到数据后还会进行回显 因此客户端在发送数据后还需要调用read函数读取服务端的响应数据 然后将该响应数据进行打印以确定双方通信无误

      void Request()
      {
        string msg;
        char buff[1024];
  
        while(true)
        {
          cout << "please enter#" << endl;
          fflush(nullptr);
          getline(cin , msg);
          write(_sockfd , msg.c_str() , msg.size());                                                                                     
  
          ssize_t size = read(_sockfd , buff , sizeof(buff) -1);
          if(size > 0)
          {
            buff[size] = 0; // '\0'
            cout << "sever echo: " << buff << endl ; 
          }
          else if (size == 0)
          {
            cout << "sever exit " << endl ;
            break;
          }
          else 
          {
            cout << "sever error" << endl;
            break;
          }
        }
  
        close(_sockfd);
      }

在运行客户端程序时我们就需要携带上服务端对应的IP地址和端口号 然后我们就可以通过服务端的IP地址和端口号构造出一个客户端对象 对客户端进行初始后启动客户端即可

服务器测试

我们首先启动服务器

在这里插入图片描述

之后使用netstat来查看网络状态

在这里插入图片描述

之后我们再运行客户端

在这里插入图片描述

我们发现简单的回声服务器就制作完毕了

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

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

相关文章

Qt Quick Qml-Rectangle案例

Qt Quick - Qml 1.Rectangle //组件 IShadow.qml import QtQuick import QtQuick.ControlsItem {id:rootanchors.fill: parentanchors.margins: -4property color color: "#999999"property int radius: 4Rectangle{width: root.widthheight: root.heightanchors.ce…

原型图都可以用什么软件做?分享这9款给你

设计师在进行原型设计师时&#xff0c;会使用原型图软件&#xff0c;从产生想法到向开发人员提交项目。无论是构建基本线框还是功能齐全的原型&#xff0c;原型图软件都可以为你节省大量的时间和精力。 如果你是这个领域的新手或者想更新你的原型图软件包&#xff0c;请快速看…

iOS App的生命周期

App的生命周期 App从启动到退出的过程中&#xff0c;iOS应用程序不断从系统接收各种事件&#xff0c;如&#xff1a;用户点击了屏幕、用户点击了Home键&#xff0c;并对这些事件进行响应。接受事件是UIApplication对象的工作&#xff0c;但是&#xff0c;响应事件就需要由程序…

Dubbo详解

一、基础知识 1、 RPC RPC【Remote Procedure Call】是指远程过程调用&#xff0c;是一种进程间通信方式&#xff0c;他是一种技术的思想&#xff0c;而不是规范。它允许程序调用另一个地址空间(通常是共享网络的另一台机器上)的过程或函数&#xff0c; 而不用程序员显式编码…

【unity细节】—(Can‘t add script)脚本文件无法拖拽到对象的问题

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! 本文由 秩沅 原创 收录于专栏&#xff1a;unity细节和bug ⭐关于脚本文件无法拖拽到对象的问题⭐ 文章目录 ⭐关于脚本文件无法拖拽到对象的…

分治算法(Divide and Conquer)

本文已收录于专栏 《算法合集》 一、简单释义 1、分治算法 字面上的解释是“分而治之”&#xff0c;就是把一个复杂的问题拆分成两个或更多的相同或相似的子问题&#xff0c;再把子问题分成更小的子问题……直到最后子问题可以简单的直接求解&#xff0c;原问题的解即子问题的…

什么是FAQ页面?如何设计一个优秀的FAQ页面?

随着互联网技术的迅猛发展&#xff0c;越来越多的企业开始将在线客户支持服务作为一种重要的业务方式&#xff0c;以提供更好的服务体验。然而&#xff0c;在线客户支持服务除了提供实时的沟通方式外&#xff0c;一个功能齐全、易于使用的FAQ页面也是必不可少的&#xff0c;这可…

Go 语言进阶与依赖管理

作者&#xff1a;非妃是公主 专栏&#xff1a;《Golang》 博客主页&#xff1a;https://blog.csdn.net/myf_666 个性签&#xff1a;顺境不惰&#xff0c;逆境不馁&#xff0c;以心制境&#xff0c;万事可成。——曾国藩 文章目录 一、语言进阶1. 并发和并行2. 协程(Goroutine…

软件or硬件?硬件的前途到底在哪里?

一、硬件明明比软件更难&#xff0c;国内的硬件技术也不如软件&#xff0c;为什么硬件工程师待遇还不如软件&#xff1f; 1、不需要太高层次的硬件设计&#xff0c;比如大部分小家电企业&#xff0c;简单的电子产品企业&#xff0c;单片机简单外围设计就够了&#xff0c;单片机…

java网络编程——NIO架构

目录 1.什么是NIO 2.NIO结构 3.基于NIO下的聊天系统实现 4.Netty 1.什么是NIO NIO&#xff1a;java non-blocking IO&#xff0c;同步非阻塞IO。 BIO是阻塞IO&#xff0c;即每一个事件都需要分配一个进程给他&#xff0c;如果客户端没有连接上&#xff0c;则一直阻塞等待…

Java基础--->并发部分(1)

文章目录 线程基本概念线程的创建方式线程调度-------常用的方法线程的生命周期和状态并发编程的根本原因Java内存模型(JMM)多线程核心的根本问题volatile关键字保障原子性synchronized和ReentrantLock的区别 线程基本概念 ​ 进程是程序的一次执行过程&#xff0c;是系统运行程…

【Linux】1.4 基本权限

文章目录 一、shell 命令以及运行原理二、Linux 权限的概念三、Linux 权限管理01.文件类型和访问权限&#xff08;事物属性&#xff09;02.文件访问的分类&#xff08;人&#xff09;①用户分类②角色划分 03.文件权限值的表现方法04.文件访问权限的相关设置方法&#xff08;a.…

STC8H8K64U单片机-看门狗配置与讲解

1、看门狗寄存器讲解 &#xff08;bit7&#xff09;WDT_FLAG&#xff1a;看门狗溢出标志&#xff0c;看门狗发生溢出时&#xff0c;硬件自动将此位置1&#xff0c;需要软件清零 &#xff08;bit5&#xff09;EN_WDT&#xff1a;看门狗使能位 0&#x…

被优化了怎么办?他苦学仨月拿到11koffer

网上有个段子叫做“生活就是起起落落落落落落”。人生在世&#xff0c;本就不易&#xff0c;再加上最近大环境影响&#xff0c;各行各业都在内卷&#xff0c;身为芸芸众生的一员&#xff0c;我们也难免受到影响&#xff0c;面临福利裁剪、降薪、甚至被优化的风险。 大环境我们…

面了20家大厂,发现这样介绍项目经验,显得项目很牛...

我在刚刚开始面试的时候&#xff0c;也遇到了这个问题&#xff0c;也是我第一个思考的问题&#xff0c;如何介绍自己的项目&#xff0c;既可以比较全面的让面试官了解这个项目&#xff0c;同时&#xff0c;也不会让面试官觉得废话太多。经过这么多的面试&#xff0c;我发现&…

JavaWeb+JSP+路径问题+跳转(HTML|Servlet|JSP)|这一篇就够了(超详细)

&#x1f648;作者简介&#xff1a;练习时长两年半的Java up主 &#x1f649;个人主页&#xff1a;老茶icon &#x1f64a; ps:点赞&#x1f44d;是免费的&#xff0c;却可以让写博客的作者开兴好久好久&#x1f60e; &#x1f4da;系列专栏&#xff1a;Java全栈&#xff0c;计…

ChatGPT真能取代程序员吗,看看它怎么解释SQL注入漏洞的问题

本文首发自「慕课网」&#xff0c;想了解更多IT干货内容&#xff0c;程序员圈内热闻&#xff0c;欢迎关注"慕课网"&#xff01; 作者&#xff1a;Beerus|慕课网讲师 背景 本周在《Web安全渗透测试》课程的QQ群中&#xff0c;有同学提问了一个关于网上一个关于SQL注入…

大淘宝技术斩获NTIRE 2023视频质量评价比赛冠军(内含夺冠方案)

近日&#xff0c;CVPR NTIRE 2023 Quality Assessment of Video Enhancement Challenge比赛结果公布&#xff0c;来自大淘宝音视频技术团队的同学组成「TB-VQA」队伍&#xff0c;从37支队伍中脱颖而出&#xff0c;拿下该比赛&#xff08;唯一赛道&#xff09;冠军。此次夺冠是团…

Mysql replace into与on duplicate key update区别

1、replace into REPLACE INTO 首先判断数据是否存在&#xff1b;如果不存在&#xff0c;则插入&#xff1b;如果已存在则更新&#xff08;先删除再插入 根据主键或唯一索引判断记录是否已存在&#xff0c;所以插入数据的表必须要有主键或者唯一索引&#xff01;否则的话&…

Java 实现访问Redis哨兵(六)

目录 一、哨兵和复制 1.1 哨兵(sentinal) 1.Redis哨兵主要功能 2.Redis哨兵的高可用 1.2 Redis复制(Replication) 1.数据复制原理(执行步骤) 1.3 Redis 主从复制、哨兵和集群这三个有什么区别 二、Java访问哨兵实现 一、哨兵和复制 谈到Redis服务器的高可用&#xff0c…