SOCKET编程(4):SOCKET实战

news2024/11/18 12:41:09

SOCKET实战

Writen()、Readn()函数

send()函数存在需要发送的字符数len小于函数返回的已发送的字符数(ssize_t)的问题

recv()函数存在需要接收的字符数len小于函数返回的已接收的字符数(ssize_t)的问题

解决上述问题通过Writen()Readn()函数实现

//buff是数据存储地址,n是待读取长度
bool Readn(const int sockfd, char *buffer, const size_t n)
{
  int nLeft, nread, idx;

  nLeft = n;
  idx = 0;

  while (nLeft > 0)
  {
    if ((nread = recv(sockfd, buffer + idx, nLeft, 0)) <= 0)
      return false;

    idx += nread;
    nLeft -= nread;
  }

  return true;
}

bool Writen(const int sockfd, const char *buffer, const size_t n)
{
  int nLeft, idx, nwritten;

  nLeft = n;
  idx = 0;
  
  while (nLeft > 0)
  {
    if ((nwritten = send(sockfd, buffer + idx, nLeft, 0)) <= 0)
      return false;

    nLeft -= nwritten;
    idx += nwritten;
  }

  return true;
}

TcpWrite()、TcpRead()函数

在这里插入图片描述

💡 解决TCP报文分包和粘包的问题

bool TcpRead(const int sockfd, char *buffer, int *ibuflen, const int itimeout)
{
  if (sockfd == -1)
    return false;

  //连接超时
  if (itimeout > 0)
  {
    fd_set tmpfd;

    FD_ZERO(&tmpfd);
    FD_SET(sockfd, &tmpfd);

    struct timeval timeout;
    timeout.tv_sec = itimeout;
    timeout.tv_usec = 0;

    int i;
    if ((i = select(sockfd + 1, &tmpfd, 0, 0, &timeout)) <= 0)
      return false;
  }

  (*ibuflen) = 0; //既然重新赋值为0,那么是否有参数传入的必要

  if (Readn(sockfd, (char *)ibuflen, 4) == false)
    return false;

  (*ibuflen) = ntohl(*ibuflen); //网络字节序转换为主机字节序

  if (Readn(sockfd, buffer, (*ibuflen)) == false)
    return false;

  return true;
}

bool TcpWrite(const int sockfd, const char *buffer, const int ibuflen)
{
  if (sockfd == -1)
    return false;

  fd_set tmpfd;

  FD_ZERO(&tmpfd);
  FD_SET(sockfd, &tmpfd);

  struct timeval timeout;
  timeout.tv_sec = 5;
  timeout.tv_usec = 0;

  if (select(sockfd + 1, 0, &tmpfd, 0, &timeout) <= 0)
    return false;

  int ilen = 0;

  // ibuf==0默认为字符数组,否则要给定大小
  if (ibuflen == 0)
    ilen = strlen(buffer);
  else
    ilen = ibuflen;

  int ilenn = htonl(ilen); //主机字节序转换为网络字节序

  char strTBuffer[ilen + 4];
  memset(strTBuffer, 0, sizeof(strTBuffer));
  memcpy(strTBuffer, &ilenn, 4);  //用memcpy能够完成任何数据类型的拷贝
  memcpy(strTBuffer + 4, buffer, ilen);

  if (Writen(sockfd, strTBuffer, ilen + 4) == false)
    return false;

  return true;
}

多进程网络服务端框架

socket服务端封装为CTcpServer类

// socket通信的服务端类
class CTcpServer
{
private:
  int m_socklen;                    // 结构体struct sockaddr_in的大小。
  struct sockaddr_in m_clientaddr;  // 客户端的地址信息。
  struct sockaddr_in m_servaddr;    // 服务端的地址信息。
public:
  int  m_listenfd;   // 服务端用于监听的socket。
  int  m_connfd;     // 客户端连接上来的socket。
  bool m_btimeout;   // 调用Read和Write方法时,失败的原因是否是超时:true-超时,false-未超时。
  int  m_buflen;     // 调用Read方法后,接收到的报文的大小,单位:字节。

  CTcpServer();  // 构造函数。

  // 服务端初始化。
  // port:指定服务端用于监听的端口。
  // 返回值:true-成功;false-失败,一般情况下,只要port设置正确,没有被占用,初始化都会成功。
  bool InitServer(const unsigned int port); 

  // 阻塞等待客户端的连接请求。
  // 返回值:true-有新的客户端已连接上来,false-失败,Accept被中断,如果Accept失败,可以重新Accept。
  bool Accept();

  // 获取客户端的ip地址。
  // 返回值:客户端的ip地址,如"192.168.1.100"。
  char *GetIP();

  // 接收客户端发送过来的数据。
  // buffer:接收数据缓冲区的地址,数据的长度存放在m_buflen成员变量中。
  // itimeout:等待数据的超时时间,单位:秒,缺省值是0-无限等待。
  // 返回值:true-成功;false-失败,失败有两种情况:1)等待超时,成员变量m_btimeout的值被设置为true;2)socket连接已不可用。
  bool Read(char *buffer,const int itimeout=0);

  // 向客户端发送数据。
  // buffer:待发送数据缓冲区的地址。
  // ibuflen:待发送数据的大小,单位:字节,缺省值为0,如果发送的是ascii字符串,ibuflen取0,如果是二进制流数据,ibuflen为二进制数据块的大小。
  // 返回值:true-成功;false-失败,如果失败,表示socket连接已不可用。
  bool Write(const char *buffer,const int ibuflen=0);

  // 关闭监听的socket,即m_listenfd,常用于多进程服务程序的子进程代码中。
  void CloseListen();

  // 关闭客户端的socket,即m_connfd,常用于多进程服务程序的父进程代码中。
  void CloseClient();

  ~CTcpServer();  // 析构函数自动关闭socket,释放资源。
};

socket客户端封装为CTcpClient类

// socket通信的客户端类
class CTcpClient
{
public:
  int  m_sockfd;    // 客户端的socket.
  char m_ip[21];    // 服务端的ip地址。
  int  m_port;      // 与服务端通信的端口。
  bool m_btimeout;  // 调用Read和Write方法时,失败的原因是否是超时:true-超时,false-未超时。
  int  m_buflen;    // 调用Read方法后,接收到的报文的大小,单位:字节。

  CTcpClient();  // 构造函数。

  // 向服务端发起连接请求。
  // ip:服务端的ip地址。
  // port:服务端监听的端口。
  // 返回值:true-成功;false-失败。
  bool ConnectToServer(const char *ip,const int port);

  // 接收服务端发送过来的数据。
  // buffer:接收数据缓冲区的地址,数据的长度存放在m_buflen成员变量中。
  // itimeout:等待数据的超时时间,单位:秒,缺省值是0-无限等待。
  // 返回值:true-成功;false-失败,失败有两种情况:1)等待超时,成员变量m_btimeout的值被设置为true;2)socket连接已不可用。
  bool Read(char *buffer,const int itimeout=0);

  // 向服务端发送数据。
  // buffer:待发送数据缓冲区的地址。
  // ibuflen:待发送数据的大小,单位:字节,缺省值为0,如果发送的是ascii字符串,ibuflen取0,如果是二进制流数据,ibuflen为二进制数据块的大小。
  // 返回值:true-成功;false-失败,如果失败,表示socket连接已不可用。
  bool Write(const char *buffer,const int ibuflen=0);

  // 断开与服务端的连接
  void Close();

  ~CTcpClient();  // 析构函数自动关闭socket,释放资源。
};

mpserver.cpp

  • mpserver.cpp

    /*
     *  程序名:mpserver.cpp,此程序演示采用freecplus框架的CTcpServer类实现socket通信多进程的服务端。
     *  作者:C语言技术网(www.freecplus.net) 日期:20190525
    */
    #include "_freecplus.h"
     
    CLogFile logfile;       // 服务程序的运行日志。
    CTcpServer TcpServer;   // 创建服务端对象。
    
    // 程序退出时调用的函数
    void FathEXIT(int sig);   // 父进程退出函数。
    void ChldEXIT(int sig);   // 子进程退出函数。
    
    int main(int argc,char *argv[])
    {
      if (argc!=3)
      {
        printf("Using:./mpserver port logfile\nExample:./mpserver 5005 /tmp/mpserver.log\n\n"); return -1;
      }
    
      // 关闭全部的信号
      for (int ii=0;ii<100;ii++) signal(ii,SIG_IGN);
    
      // 打开日志文件。
      if (logfile.Open(argv[2],"a+")==false) { printf("logfile.Open(%s) failed.\n",argv[2]); return -1;}
    
      // 设置信号,在shell状态下可用 "kill + 进程号" 正常终止些进程
      // 但请不要用 "kill -9 +进程号" 强行终止
      signal(SIGINT,FathEXIT); signal(SIGTERM,FathEXIT);
     
      if (TcpServer.InitServer(atoi(argv[1]))==false) // 初始化TcpServer的通信端口。
      {
        logfile.Write("TcpServer.InitServer(%s) failed.\n",argv[1]); FathEXIT(-1);
      }
     
      while (true)
      {
        if (TcpServer.Accept()==false)   // 等待客户端连接。
        {
          logfile.Write("TcpServer.Accept() failed.\n"); continue;
        }
    
        if (fork()>0) { TcpServer.CloseClient(); continue; } // 父进程返回到循环首部。
    
        // 子进程重新设置退出信号。
        signal(SIGINT,ChldEXIT); signal(SIGTERM,ChldEXIT);
    
        TcpServer.CloseListen();
     
        // 以下是子进程,负责与客户端通信。
        logfile.Write("客户端(%s)已连接。\n",TcpServer.GetIP());
     
        char strbuffer[1024];  // 存放数据的缓冲区。
     
        while (true)
        {
          memset(strbuffer,0,sizeof(strbuffer));
          if (TcpServer.Read(strbuffer,50)==false) break; // 接收客户端发过来的请求报文。
          logfile.Write("接收:%s\n",strbuffer);
     
          strcat(strbuffer,"ok");      // 在客户端的报文后加上"ok"。
          logfile.Write("发送:%s\n",strbuffer);
          if (TcpServer.Write(strbuffer)==false) break;     // 向客户端回应报文。
        }
     
        logfile.Write("客户端已断开。\n");    // 程序直接退出,析构函数会释放资源。
    
        ChldEXIT(-1);  // 通信完成后,子进程退出。
      }
    }
    
    // 父进程退出时调用的函数
    void FathEXIT(int sig)
    {
      if (sig > 0)
      {
        signal(sig,SIG_IGN); signal(SIGINT,SIG_IGN); signal(SIGTERM,SIG_IGN);
        logfile.Write("catching the signal(%d).\n",sig);
      }
    
      kill(0,15);  // 通知其它的子进程退出。
    
      logfile.Write("父进程退出。\n");
    
      // 编写善后代码(释放资源、提交或回滚事务)
      TcpServer.CloseClient();
    
      exit(0);
    }
    
    // 子进程退出时调用的函数
    void ChldEXIT(int sig)
    {
      if (sig > 0)
      {
        signal(sig,SIG_IGN); signal(SIGINT,SIG_IGN); signal(SIGTERM,SIG_IGN);
      }
    
      logfile.Write("子进程退出。\n");
    
      // 编写善后代码(释放资源、提交或回滚事务)
      TcpServer.CloseClient();
    
      exit(0);
    }
    
  • 利用fork()函数创建子进程,父进程负责监听工作,子进程负责与客户端的连接

  • 父进程关闭connectfd,子进程关闭listenfd,避免占用系统资源

if (TcpServer.Accept() == false) // 等待客户端连接。
{
    logfile.Write("TcpServer.Accept() failed.\n");
    continue;
}

if (fork() > 0)
{
    TcpServer.CloseClient();
    continue;
} // 父进程返回到循环首部。

// 子进程重新设置退出信号。
signal(SIGINT, ChldEXIT);
signal(SIGTERM, ChldEXIT);

TcpServer.CloseListen();

💡 考虑用sigaction()函数代替signal()函数进行优化

避免僵尸进程

在这里插入图片描述

  1. 忽略子进程的退出信号
  2. wait()函数阻塞式等待,影响主进程工作,可利用waitpid()函数实现无阻塞等待
// 关闭全部的信号
for (int ii = 0; ii < 100; ii++)
{
    signal(ii, SIG_IGN);
}

💡 关闭全部信号意味着同时忽略子进程的退出信号

多进程服务程序的退出和资源释放

  • 实际开发中在终端按Ctrl+C退出是不专业的
  • 信号来通知和实现进程的退出
  • 一般而言父进程退出则子进程也退出,而子进程单独退出则不受影响
// 父进程退出时调用的函数
void FathEXIT(int sig)
{
  if (sig > 0)
  {
    signal(sig,SIG_IGN); signal(SIGINT,SIG_IGN); signal(SIGTERM,SIG_IGN);
    logfile.Write("catching the signal(%d).\n",sig);
  }

  kill(0,15);  // 通知其它的子进程退出。

  logfile.Write("父进程退出。\n");

  // 编写善后代码(释放资源、提交或回滚事务)
  TcpServer.CloseClient();

  exit(0);
}

// 子进程退出时调用的函数
void ChldEXIT(int sig)
{
  if (sig > 0)
  {
    signal(sig,SIG_IGN); signal(SIGINT,SIG_IGN); signal(SIGTERM,SIG_IGN);
  }

  logfile.Write("子进程退出。\n");

  // 编写善后代码(释放资源、提交或回滚事务)
  TcpServer.CloseClient();

  exit(0);
}

TCP短连接与长连接

client与server建立连接进行通信,通信完成后释放连接,建立连接时需要3次握手,释放连接需要4次挥手,连接的建立和释放都需要时间,server还有创建新进程或线程的开销

短连接:

client/server间只进行一次或连续多次通信,通信完成后马上断开了。管理起来比较简单,不需要额外的控制手段

长连接:

client/serverl间需要多次通信,通信的频率和次数不确定,所以client和server需要保持这个连接

增加心跳机制

心跳机制是针对长连接而言的

如果client与server采用长连接,在连接空闲时,client每若干秒向server发送一个心跳报文,server也回复一个心跳报文,确认连接继续生效中
如果server在约定的时间内没有收到client的任何报文,则认为客户端已掉线,就主动断开连接,释放资源

在这里插入图片描述

💡 心跳报文建议在60秒以内,不要超过120秒
可以将心跳报文的发送放在子线程

多线程网络服务端框架

mtserver_biz.cpp

  • mtserver_biz.cpp

    /*
     *  程序名:mtserver_biz.cpp,此程序演示采用freecplus框架的CTcpServer类实现socket通信多线程的服务端。
     *  作者:C语言技术网(www.freecplus.net) 日期:20190525
    */
    #include "_freecplus.h"
    
    void *pthmain(void *arg);  // 线程主函数。
    
    vector<long> vpthid;  // 存放线程id的容器。
    
    void mainexit(int sig);   // 信号2和15的处理函数。
    
    void pthmainexit(void *arg); // 线程清理函数。
     
    CLogFile logfile;       // 服务程序的运行日志。
    CTcpServer TcpServer;   // 创建服务端对象。
    
    // 处理业务的主函数。
    bool _main(const char *strrecvbuffer,char *strsendbuffer);
    
    // 心跳报文。
    bool biz000(const char *strrecvbuffer,char *strsendbuffer);
    
    // 身份验证业务处理函数。
    bool biz001(const char *strrecvbuffer,char *strsendbuffer);
    
    // 查询余客业务处理函数。
    bool biz002(const char *strrecvbuffer,char *strsendbuffer);
    
    int main(int argc,char *argv[])
    {
      if (argc!=3)
      {
        printf("Using:./mtserver_biz port logfile\nExample:./mtserver_biz 5005 /tmp/mtserver_biz.log\n\n"); return -1;
      }
    
      // 关闭全部的信号
      for (int ii=0;ii<100;ii++) signal(ii,SIG_IGN);
    
      // 打开日志文件。
      if (logfile.Open(argv[2],"a+")==false) { printf("logfile.Open(%s) failed.\n",argv[2]); return -1;}
    
      // 设置信号,在shell状态下可用 "kill + 进程号" 正常终止些进程
      // 但请不要用 "kill -9 +进程号" 强行终止
      signal(SIGINT,mainexit); signal(SIGTERM,mainexit);
     
      if (TcpServer.InitServer(atoi(argv[1]))==false) // 初始化TcpServer的通信端口。
      {
        logfile.Write("TcpServer.InitServer(%s) failed.\n",argv[1]); return -1;
      }
     
      while (true)
      {
        if (TcpServer.Accept()==false)   // 等待客户端连接。
        {
          logfile.Write("TcpServer.Accept() failed.\n"); continue;
        }
    
        logfile.Write("客户端(%s)已连接。\n",TcpServer.GetIP());
    
        pthread_t pthid;
        if (pthread_create(&pthid,NULL,pthmain,(void *)(long)TcpServer.m_connfd)!=0)
        { logfile.Write("pthread_create failed.\n"); return -1; }
    
        vpthid.push_back(pthid); // 把线程id保存到vpthid容器中。
      }
    
      return 0;
    }
    
    void *pthmain(void *arg)
    {
      pthread_cleanup_push(pthmainexit,arg);  // 设置线程清理函数。
    
      pthread_detach(pthread_self());  // 分离线程。
    
      pthread_setcanceltype(PTHREAD_CANCEL_DISABLE,NULL);  // 设置取消方式为立即取消。
    
      int sockfd=(int)(long)arg;  // 与客户端的socket连接。
    
      int  ibuflen=0;
      char strrecvbuffer[1024],strsendbuffer[1024];  // 存放数据的缓冲区。
     
      while (true)
      {
        memset(strrecvbuffer,0,sizeof(strrecvbuffer));
        memset(strsendbuffer,0,sizeof(strsendbuffer));
    
        if (TcpRead(sockfd,strrecvbuffer,&ibuflen,50)==false) break; // 接收客户端发过来的请求报文。
        logfile.Write("接收:%s\n",strrecvbuffer);
     
        // 处理业务的主函数。
        if (_main(strrecvbuffer,strsendbuffer)==false) break;
    
        logfile.Write("发送:%s\n",strsendbuffer);
    
        if (TcpWrite(sockfd,strsendbuffer)==false) break;     // 向客户端回应报文。
      }
    
      pthread_cleanup_pop(1);
    
      pthread_exit(0);
    }
    
    // 信号2和15的处理函数。
    void mainexit(int sig)
    {
      logfile.Write("mainexit begin.\n");
    
      // 关闭监听的socket。
      TcpServer.CloseListen();
    
      // 取消全部的线程。
      for (int ii=0;ii<vpthid.size();ii++)
      {
        logfile.Write("cancel %ld\n",vpthid[ii]);
        pthread_cancel(vpthid[ii]);
      }
    
      logfile.Write("mainexit end.\n");
    
      exit(0);
    }
    
    // 线程清理函数。
    void pthmainexit(void *arg)
    {
      logfile.Write("pthmainexit begin.\n");
    
      // 关闭与客户端的socket。
      close((int)(long)arg);
    
      // 从vpthid中删除本线程的id。
      for (int ii=0;ii<vpthid.size();ii++)
      {
        if (vpthid[ii]==pthread_self())
        {
          vpthid.erase(vpthid.begin()+ii);
        }
      }
    
     logfile.Write("pthmainexit end.\n");
    }
    
    bool _main(const char *strrecvbuffer,char *strsendbuffer)  // 处理业务的主函数。
    {
      int ibizcode=-1;
      GetXMLBuffer(strrecvbuffer,"bizcode",&ibizcode);
    
      switch (ibizcode)
      {
        case 0:  // 心跳
          biz000(strrecvbuffer,strsendbuffer); break;
        case 1:  // 身份验证。
          biz001(strrecvbuffer,strsendbuffer); break;
        case 2:  // 查询余额。
          biz002(strrecvbuffer,strsendbuffer); break;
    
        default:
          logfile.Write("非法报文:%s\n",strrecvbuffer); return false;
      }
    
      return true;
    }
    
    // 身份验证业务处理函数。
    bool biz001(const char *strrecvbuffer,char *strsendbuffer)
    {
      char username[51],password[51];
      memset(username,0,sizeof(username));
      memset(password,0,sizeof(password));
    
      GetXMLBuffer(strrecvbuffer,"username",username,50);
      GetXMLBuffer(strrecvbuffer,"password",password,50);
    
      if ( (strcmp(username,"wucz")==0) && (strcmp(password,"p@ssw0rd")==0) )
        sprintf(strsendbuffer,"<retcode>0</retcode><message>成功。</message>");
      else
        sprintf(strsendbuffer,"<retcode>-1</retcode><message>用户名或密码不正确。</message>");
    
      return true;
    }
    
    // 查询余额业务处理函数。
    bool biz002(const char *strrecvbuffer,char *strsendbuffer)
    {
      char cardid[51];
      memset(cardid,0,sizeof(cardid));
    
      GetXMLBuffer(strrecvbuffer,"cardid",cardid,50);
    
      if (strcmp(cardid,"62620000000001")==0)
        sprintf(strsendbuffer,"<retcode>0</retcode><message>成功。</message><ye>100.50</ye>");
      else
        sprintf(strsendbuffer,"<retcode>-1</retcode><message>卡号不存在。</message>");
    
      return true;
    }
    
    // 心跳报文
    bool biz000(const char *strrecvbuffer,char *strsendbuffer)
    {
      sprintf(strsendbuffer,"<retcode>0</retcode><message>成功。</message>");
    
      return true;
    }
    

💡 考虑用thread代替pthread实现多线程

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

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

相关文章

矩阵的压缩存储介绍

引入 概述 特殊矩阵的压缩 对称矩阵 三角矩阵 上三角矩阵&#xff1a;上三角区的元素不同&#xff0c;下三角区的元素相同。 存储不同元素的上三角区(计算前i-1行的所有元素之和(j-i1)[i行的列数]-1[下标由0开始],即以下标为0开始存储的下标)一个相同元素(下三角区) 下三角矩…

Verlog-串口发送-FPGA

Verlog-串口发送-FPGA 引言&#xff1a; ​ 随着电子技术的不断进步&#xff0c;串口通信已成为嵌入式系统和计算机外设中一种广泛使用的异步通信方式。串口通信因其简单性、可靠性以及对硬件资源的低要求&#xff0c;在数据传输领域扮演着重要角色。在FPGA&#xff08;现场可编…

ICode国际青少年编程竞赛- Python-4级训练场-复杂嵌套for循环

ICode国际青少年编程竞赛- Python-4级训练场-复杂嵌套for循环 1、 for i in range(4):Dev.step(i6)for j in range(3):Dev.turnLeft()Dev.step(2)2、 for i in range(4):Dev.step(i3)for j in range(4):Dev.step(2)Dev.turnRight()Dev.step(-i-3)Dev.turnRight()3、 for i …

ICode国际青少年编程竞赛- Python-4级训练场-嵌套for循环练习

ICode国际青少年编程竞赛- Python-4级训练场-嵌套for循环练习 1、 for i in range(3):Spaceship.step(4)for j in range(4):Dev.step(2)Dev.turnRight()Spaceship.turnLeft()Spaceship.step(4)Spaceship.turnRight()2、 for i in range(4):Spaceship.step(6)for j in range(3):…

SpringBoot实现图片验证码

引入依赖 <dependency><groupId>com.github.whvcse</groupId><artifactId>easy-captcha</artifactId><version>1.6.2</version> </dependency>代码实现 package com.qiangesoft.captcha.controller;import com.wf.captcha.*…

【LeetCode刷题记录】简单篇-108-将有序数组转换为二叉搜索树

【题目描述】 给你一个整数数组 nums &#xff0c;其中元素已经按 升序 排列&#xff0c;请你将其转换为一棵 平衡 二叉搜索树。 【测试用例】 示例1&#xff1a; 输入&#xff1a;nums [-10,-3,0,5,9] 输出&#xff1a;[0,-3,9,-10,null,5] 解释&#xff1a;[0,-10,5,null,…

面向对象进阶——内部类

1、初始内部类 什么是内部类&#xff1f; 类的五大成员&#xff1a; 属性、方法、构造方法、代码块、内部类 在一个类的里面&#xff0c;再定义一个类。 举例&#xff1a;在A类大的内部定义B类&#xff0c;B类就被称为内部类 public class Outer{ 外部类 public …

计数问题C++

题目&#xff1a; 思路&#xff1a; 1~n之间进行循环遍历&#xff0c;如果i不等于0继续循环&#xff0c;然后求出i的个位数与十位数&#xff0c;如果个位数为要查找的特定数字&#xff0c;计时器就1. 代码&#xff1a; #include<iostream> using namespace std; int n,x…

80.网络游戏逆向分析与漏洞攻防-移动系统分析-利用数据包尝试穿墙

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 如果看不懂、不知道现在做的什么&#xff0c;那就跟着做完看效果&#xff0c;代码看不懂是正常的&#xff0c;只要会抄就行&#xff0c;抄着抄着就能懂了 内容…

蛋糕店做配送小程序的作用是什么

蛋糕烘焙除了生日需要&#xff0c;对喜吃之人来说往往复购率较高&#xff0c;除线下实体店经营外&#xff0c;更多的商家选择线上多种方式获客转化、持续提高生意营收&#xff0c;而除了进驻第三方平台外&#xff0c;构建品牌私域自营店铺也同样重要。 运用【雨科】平台搭建蛋…

【软考高项】四十二、八大绩效域知识点

一、干系人绩效域 预期目标 建立高效的工作关系 检查&#xff1a;干系人参与的连续性 干系人认同项目目标 检查&#xff1a; 变更频率 支持项目的干系人提高了满意度&#xff0c;从中受益 检查&#xff1a;干系人行为、干系人满意度、干系人相…

Linux基础之进程

目录 一、进程的基本概念 1.1 什么是进程 1.2 PCB的概念 1.3 进程的查看 1.3.1 查看进程方式一 1.3.2 查看进程的方式二 1.4 父进程与子进程 一、进程的基本概念 1.1 什么是进程 进程是什么&#xff1f; 课本概念&#xff1a;程序的一个执行实例&#xff0c;正在执行的…

基于StatefulSet控制器在Kubernetes上部署MySQL一主多从

一、前提--StatefuSet特性 1.1 有状态的节点控制器 -- StatefulSet 及其网络状态 容器的解决方案是针对无状态应用场景的最佳实践&#xff0c;但对于有状态应用来说&#xff0c;就并非如此了。Kubernetes 用 StatefulSet 解决了有状态应用编排的问题&#xff0c;本文我们就来…

设计模式-创建型-原型模式-prototype

工作经验类 public class WorkExperience implements Cloneable {private String workDate;private String company;public void setWorkDate(String workDate) {this.workDate workDate;}public void setCompany(String company) {this.company company;}Overridepublic Ob…

【35分钟掌握金融风控策略21】贷前额度策略

目录 贷前策略审批流程和统一额度管理 贷前策略审批流程 统一额度管理 预授信策略 贷前策略审批流程和统一额度管理 贷前包含了多个风控场景&#xff0c;这些风控场景的策略在执行时是否存在先后顺序呢&#xff1f;在贷前&#xff0c;除上述主要的风控场景&#xff0c;还有…

2024年4月12日饿了么春招实习试题【第三题】-题目+题解+在线评测,2024.4.12,饿了么机试【Kruskal 算法, 最小生成树】

2024年4月12日饿了么春招实习试题【第三题】-题目题解在线评测&#xff0c;2024.4.12&#xff0c;饿了么机试 &#x1f3e9;题目一描述&#xff1a;样例1样例2解题思路一&#xff1a;[Kruskal 算法](https://baike.baidu.com/item/%E5%85%8B%E9%B2%81%E6%96%AF%E5%8D%A1%E5%B0%…

【第19章】spring-mvc之全局异常处理

文章目录 前言一、全局异常处理1. 前端2. 后端 二、常见错误页1.增加界面2.web.xml3.异常处理4.效果 总结 前言 例如&#xff1a;随着人工智能的不断发展&#xff0c;机器学习这门技术也越来越重要&#xff0c;很多人都开启了学习机器学习&#xff0c;本文就介绍了机器学习的基…

决策树的学习(Decision Tree)

1.对于决策树的概念&#xff1a; **本质上&#xff1a;**决策树就是模拟树的结构基于 if-else的多层判断 2.目的&#xff1a; 对实例进行分类的树形结构&#xff0c;通过多层判断&#xff0c;将所提供的数据归纳为一种分类规则。 3.优点&#xff1a; 1.计算量小&#xff0c;…

vs2019 STL库里 判断函数类型的模板 is_function_v 与 is_const_v

&#xff08;1&#xff09;源代码如下&#xff1a; 经简单代码测试后&#xff0c;得出 vs2019 的 c 编译器 和 其 STL 库的观点与设计&#xff1a;is_const_v 用来判断类型 T 内是否含有 const 修饰符&#xff0c;含有 const 则返回真。但若 T 是含有 const 的引用类型&#xf…

loongarch64 electron打包deb改成符合统信测试通过的deb

需要做软件适配统信系统的自主认证。 我之前是在 麒麟 龙芯 loongarch64 电脑上使用 electron 打包的 deb包&#xff1a;麒麟龙芯loongarch64 electron 打包deb包_electron麒麟系统打包的-CSDN博客 安装在统信电脑 处理器&#xff1a;Loongson-3A60000-HV 2.5GHz 可以使用&…