套接字编程(四)TCP通信(万字详论)

news2024/12/27 14:38:50

TCP协议:面向连接的、可靠稳定的、基于字节流传输的通信协议

        面向连接:通信之前首先要建立连接,确保双方具有收发数据的能力

        可靠稳定的:通过大量的控制机制,保证数据能够安全有序且完整的到达对端

        基于字节流:采用数据流方式,没有传输大小限制,传输比较灵活的一种方式

UDP协议:无连接的、不可靠的、基于数据报的通信协议

        无连接:收发数据之前不需要建立连接,只要知道对方地址,就可以进行传输

        不可靠:只管数据能够发送出去,不管能否到达对端

        基于数据报:有最大传输大小限制,且有最大交付传输限制的一种方式

对于数据传输时实时性大于安全性的选择UDP通信(视频、音频传输)

防火墙:默认设置下,将几乎所有的向内传输都进行了拦截

关闭它:sudo systemctl stop firewalld(停用防火墙)

彻底关闭它:sudo systemctl  diable firewalld(禁用防火墙,以后重启都不会开启)

目录

一、Tcp服务端流程

二、TCP客户端流程

三、TCP接口介绍

四、Tcp接口操作的类的封装实现

五、实现TCP通信

使用多进程实现服务端:

使用多线程实现服务端


一、Tcp服务端流程

1、创建套接字        在内核中创建一个socket结构体

2、为套接字绑定地址信息

3、开始监听

        Tcp是一个有状态的通信,它会根据自己不同状态来完成不同的功能

        开始监听就使自己的socket结构体处于一种listen状态

        只有在listen状态下,才能处理来自客户端的请求

4、获取新连接

        从监听套接字对应的新建队列中,取出一个套接字结构,并返回这个套接字描述符

5、收发数据

        因为tcp是面向连接的通信方式,所以后续收发数据不需要在进行传递自己的五元组信息了,因为新建的套接字结构体中已经有完整的五元组信息了。

6、关闭套接字

 以下图来建立TCP通信方式: 客户端五元组信息:192.168.2.3 10000

                                                服务端五元组信息:192.168.2.128 8000

首先,服务端创建套接字,然后将自己的五元组信息进行绑定,得到了右下这个struct socket结构体,该结构体可以称之为门迎结构体(先体会)

这时进行第3步开始监听,将socket结构体中的status状态置为LISTEN,当处于LISTEN状态时服务端才能够接受到来自客户端的请求。

 客户端携带着自己的连接请求以及自己的五元组地址信息来与该服务端进行建立连接,这时候socket会创建一个新的socket结构体(服务员结构体)来与客户端进行通信,这个新的socket上不仅绑定着服务端的地址信息,也绑定着来自客户端的地址信息,并且将自身状态由LISTEN改为ESTABLISHED。

从建立该连接之后客户端就可以与该服务端进行数据通信了。

为什么后续不需要绑定信息了?

        因为新创建的socket服务员结构体中不仅有服务端的五元组信息,还有客户端的五元组信息,新传输过来的数据只需要识别哪个是与自己的匹配的完整的socket信息即可。

再来简单梳理一下这个过程;

        服务端开始监听,客户端发送了一个连接请求,处于监听状态的服务端才会与之建立连接

        1、服务端为这个新的连接请求创建一个套接字        

        2、服务端为这个套接字描述了完整的五元组信息

        往后所有的数据通信都会与新创建的套接字进行通信,一个服务端上有多少个客户端需要建立连接,就需要创建多少个新的套接字结构体(服务员),而最早服务端创建的监听套接字--只负责建立请求处理,不负责数据通信(门迎)

二、TCP客户端流程

1、创建套接字

2、绑定地址信息(不推荐)

3、发送连接请求        比UDP客户端多了一个连接请求

4、收发数据

5、关闭套接字

三、TCP接口介绍

int socket(int domain, int type, int protocol)

        domain——地址域类型(一般ipv4 - AF_INET)

        type:套接字类型 (字节流传输 TCP SOCK_STREAM)

                                     (数据报传输 UDP SOCK_DGRAM)

        protocol——协议类型 (IPPROTO_TCP、IPPROTO_UDP)

int bind(int sockfd, struct sockaddr *addr, socklen_t addrlen)

int listen(int sockfd, int backlog)

        sockfd——创建套接字返回的监听套接字描述符(门迎)

        backlog——同一时间的最大并发连接数(限定同一时间能够有多少个客户端连接)

int connect(int sockfd,struct sockaddr *addr,socklen_t len)

        向服务端发送连接建立请求,这个接口只有客户端会用

        sockfd 套接字描述符

        addr 服务端的地址信息

        addrlen 地址长度

获取新建连接

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

        从内核socket指定的监听套接字对应的已完成连接队列中,

                      取出一个socket,并返回这个socket的描述符

        通过addr参数返回具体连接请求来源与那个客户端

        addr:accept内部进行填充用户端地址,是一个输出参数

        addrlen:地址信息长度,输入输出参数,

                        用于指定想要获取的地址长度,以及实际返回的地址长度

        返回值:成功则返回新建连接的套接字描述符;出错返回-1

注意:accept服务端使用accept与客户端建立连接之后就会返回新建连接的套接字描述符,之后的收发数据中使用的就都是该套接字描述符。

收发数据

ssize_t send(int sockfd,void *data,size_t len,int flag)相对于sendto不用指定地址了

        返回值:成功返回实际发送的数据长度,失败返回-1

ssize_t recv(int sockfd,void *buf,size_t len,int flag)相较于recvfrom,不用获取地址了

        返回值:成功返回实际接收的字节长度,失败返回-1,连接断开则返回0

        tcp是面向连接的,一旦连接断开(对方关闭了连接、或者网络出问题……)将无法继续通信

关闭套接字:int close(int sockfd)

四、Tcp接口操作的类的封装实现

1、头文件


#include<cstdio>
#include<iostream>
#include<string>
#include<sys/socket.h> // 套接字相关接口
#include<netinet/in.h> // 点分十进制与整形转化 
#include<arpa/inet.h> // 网络字节序与主机字节序 
#include<unistd.h>    // close接口

2、 创建套接字

#define MAX_BLOG 10
class tcpSocket
{
  private:
    int _sockfd;    // 内置类型封装socket描述符
  public:
    tcpSocket():_sockfd(-1){}
    ~tcpSocket() { /*Close();*/ }    // 这里进行注释 
        因为服务端接收连接之后就会创建一个新的套接字,在在该套接字内部不断循环执行收发信息
        如果在析构函数中关闭了套接字,那么最终程序运行时就会直接连接中断了
    bool Socket()    // 创建套接字
    {
      _sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
      if (_sockfd < 0) {
        perror("create error");
        return false;
      }
      return true;
    }
}

 3、为套接字绑定地址信息

    // int bind(int sockfd, struct sockaddr* addr, socklen_t addrlen)
    bool Bind(const std::string &ip, uint16_t port)
    {
      // 传入参数ip与port来进行绑定
      struct sockaddr_in addr;    // 定义一个ipv4地址结构的套接字
      addr.sin_family = AF_INET;    // 初始化sin_family协议簇为AF_INET ipv4地址域
      addr.sin_port = htons(port);    // 这里是主机字节序向网络字节序转化 使用htons(二字节)

      addr.sin_addr.s_addr = inet_addr(ip.c_str());    
      // 初始化sin_addr中的s_addr注意ip的字符形态转化

      socklen_t len = sizeof(struct sockaddr_in);// 地址长度,直接sizeof(对应的地址结构)
      int ret = bind(_sockfd, (sockaddr*)&addr, len);// bind进行绑定,注意强转使用
      if(ret < 0){
        perror("bind error");
        return false;
      }
      return true;
    }

 4、服务端开始监听状态

    #define MAX_BLOG 10    
    // int listen(int sockfd, int backlog)
    bool Listen(int backlog = MAX_BLOG)    // 传入一个同一时间最大连接数量
    {                                      // 使用默认参数MAX_BLOG(全缺省函数)
        int ret = listen(_sockfd, backlog);// 进入监听状态
        if(ret < 0){
          perror("listen error");
          return false;
        }
        return true;
    }

 5、当处于监听状态的服务端可以接受到来自客户端的建立连接请求,当接受请求后,创建一个新的套接字,新的套接字结构体中里面描述了完整的五元组信息(不仅有服务端的五元组、还有客户端的五元组信息)

    // int accept(int sockfd,struct sockaddr *peer,socklen_t *addrlen)
    
    bool Accept(tcpSocket *sock, std::string *cli_ip = NULL, uint16_t *cli_port = NULL)
    {
        // 注意这里的函数参数,使用sock来接收新的套接字描述符,引入参数ip port来接收
        // 因为服务端使用了accept接口就会创建一个新的套接字,
        // 需要使用上面的三个参数来接收创建的套接字信息
        
        struct sockaddr_in peer;  
        // peer就是新创建的套接字,使用accept把描述信息装载到这个peer中
        socklen_t len = sizeof(struct sockaddr_in);
        int newfd = accept(_sockfd, (struct sockaddr*)&peer, &len);
        if(newfd < 0){    
            perror("accept error");
            return false;
        }
        sock->_sockfd = newfd;
            // 装载结束使用sock接收accept的返回值(即就是我们的新建套接字描述符)
        if(cli_ip) *cli_ip = inet_ntoa(peer.sin_addr);  
            // 使用ip、port接收新建套接字中的ip 与 port
        if(cli_port) *cli_port = ntohs(peer.sin_port);  
            // 因为后续操作根本不需要使用ip或者port了,
            // 这个newfd保存的套接字描述符完全可以进行匹配
        return true;
    }

 6、收发数据;因为上面使用sock接收了accept的返回值(新的套接字描述符——一个具有完整五元组的套接字,后续数据可以直接适配到这个套接字上,所以后续的收发数据就不用传递地址信息了。)

    // 因为不需要绑定信息了,传入接收或者发送的body即可
    bool Send(const std::string &body)
    {
       ssize_t ret = send(_sockfd, body.c_str(), body.size(), 0);
       if(ret < 0)
       {
         perror("send error");
         return false;
       }
       return true;
    }
    bool Recv(std::string *body)
    {
        char buf[1024] = {0};
        ssize_t ret = recv(_sockfd, buf, 1023, 0);
        if(ret < 0)
        {
          perror("revy error");
          return false;
        }
        else if(ret == 0){
          std::cout<<"connect fail"<<std::endl;
        }
        body->assign(buf, ret);// 从buf中取出ret大小的数据放入body中
        return true;
    }

 7、关闭套接字

    bool Close()
    {
      if (_sockfd != -1)
      {
        close(_sockfd);
        _sockfd = -1;
      }
      return true;
    }

五、实现TCP通信

客户端:

#include<assert.h>
int main(int argc, char *argv[])
{
  if(argc != 3)
  {
      printf("./client 192.168.233.128 9000\n");
      return -1;
  }
  std::string ip = argv[1];    // 接收ip
  uint16_t port = std::stoi(argv[2]);    //接收port
  tcpSocket sock;    // 1、创建套接字
  assert(sock.Connect(ip, port)); //2、进行请求连接
  while(1)
  {
      std::string data;
      std::cout<<"client:";
      fflush(stdout);
      std::cin>>data;
      assert(sock.Send(data));    // 3、发送数据
      
      data.clear();
      assert(sock.Recv(&data));    // 4、接收数据
      std::cout<<"serve say:"<<data<<std::endl;
      
  }
  sock.Close();    // 5、关闭套接字
  return 0;
}

服务端

#include<assert.h>

int main(int argc, char *argv[])
{
  if(argc != 3)
  {
    printf("./serve 192.168.233.128 9000\n");
    return -1;
  }
  std::string ip = argv[1];
  uint16_t port = std::stoi(argv[2]);

  tcpSocket doorman_sock;// 定义一个门迎socket
  assert(doorman_sock.Socket());    // 1、创建套接字
  assert(doorman_sock.Bind(ip, port));    // 2、进行地址信息绑定
  assert(doorman_sock.Listen());    // 3、开始监听
  while(1)
  {
    tcpSocket waiter_sock;// 定义一个服务员socket
    std::string cli_ip;// 定义一个ip用来接收Accept中绑定的客户端五元组信息
    uint16_t cli_port; // 和上面ip一起来绑定信息
    // 4、建立连接
    int ret = doorman_sock.Accept(&waiter_sock, &cli_ip, &cli_port); 
    if(ret < 0)
    {
      // 没有接收到所以没有对该服务员进行初始化,也就不需要进行销毁
      continue;
    }

    // 5.收发数据
    std::string data;
    ret = waiter_sock.Recv(&data);
    if(ret < 0)
    {
      waiter_sock.Close();// 因为接收这边出问题了,所以把已经初始化的服务员结构体给销毁
      continue;
    }
    std::cout<<cli_ip<<" "<<cli_port<<" client say:"<<data<<std::endl;
    data.clear();
    std::cout<<"sever say:";
    fflush(stdout);
    std::cin>>data;
    waiter_sock.Send(data);
    if(ret < 0)
    {
      waiter_sock.Close();// 因为接收这边出问题了,所以把初始化的服务员结构体给销毁
      continue;
    }
    doorman_sock.Close();
    return 0;
  }
}

上述的TCP通信只能实现一个客户端和服务端进行通信,如果要实现另外一个客户端的连接就必须得把当前客户端退出,显然存在着不合理性。

单执行流无法完成TCP多个进程之间的通信,必须使用多进程或多线程来完成

使用多进程实现服务端:

在Accept之后让子进程来完成数据收发的工作,每次有客户端连接后,服务端就新建一个新的套接字结构体来创建子进程,让这个子进程去做后面的事儿。在子进程内部实现当前客户端与服务端的通信,当另一个客户端发送链接请求后,就再创建一个新的套接字来完成新的通信工作

 main函数内部

#include<signal.h>
int main(int argc, char *argv[])
{
  if(argc != 3)
  {
    printf("./serve 192.168.233.128 9000\n");
    return -1;
  }
  std::string ip = argv[1];
  uint16_t port = std::stoi(argv[2]);
  signal(SIGCHLD, SIG_IGN);
  tcpSocket doorman_sock;// 定义一个门迎socket
  assert(doorman_sock.Socket()); // 1、创建套接字
  assert(doorman_sock.Bind(ip, port));  // 2、绑定地址信息
  assert(doorman_sock.Listen());  // 开始监听
  while(1)
  {
    tcpSocket waiter_sock;// 定义一个服务员socket
    std::string cli_ip;// 定义一个ip用来接收Accept中绑定的客户端五元组信息
    uint16_t cli_port; // 和上面ip一起来绑定信息
    bool ret = doorman_sock.Accept(&waiter_sock, &cli_ip, &cli_port);
    if(ret == false)
    {
      // 没有接收到所以没有对该服务员进行初始化,也就不需要进行销毁
      continue;
    }
    std::cout<<"newclient:"<<cli_ip<<":"<<cli_port<<std::endl;
    create_worker(waiter_sock);    // 在最后调用该函数,进行子进程的创建
  }
  doorman_sock.Close();
  return 0;
}

 create_worker内部(实现子进程创建及收发数据功能)

void create_worker(tcpSocket new_sock)
{
  pid_t pid = fork();
  if(pid < 0)    // 创建失败 关闭套接字return返回
  {
    new_sock.Close();
    perror("fork error");
    return;
  }
  else if(pid > 0)
  {//父进程退出
    new_sock.Close();    // 如果为父进程那么直接进行退出即可
    return;
  }
  while(1)
  {
    std::string buf;    // 以下就为收发数据
    ssize_t ret = new_sock.Recv(&buf);
    if(ret == 0)    // 接收数据返回值为0时表示连接断开(因为网络或者其他因素)
    {
      printf("connect error\n");
      new_sock.Close();
      break;
    }else if(ret == -1)
    {
      perror("recv error");
      break;
    }
    std::cout<<"client say:"<<buf<<std::endl;
    buf.clear();
    std::cout<<"sever say:";
    fflush(stdout);
    std::cin>>buf;
    ret = new_sock.Send(buf);
    if(ret < 0){
      perror("send error");
      new_sock.Close();
      break;
    }
  }
  exit(-1);
}

使用多线程实现服务端

相较于多进程版本,流程上没有多大区别,也是获得一个新建连接之后创建一个线程出来

线程之间共用同一个文件描述符,因此对这个通信套接字描述符的操作,只能由负责这个描述符操作的线程进行关闭,其他线程不能关闭

main函数和上述基本一致

int main(int argc, char *argv[])
{
  if(argc != 3)
  {
    printf("./serve 192.168.233.128 9000\n");
    return -1;
  }
  std::string ip = argv[1];
  uint16_t port = std::stoi(argv[2]);
  tcpSocket doorman_sock;// 定义一个门迎socket
  assert(doorman_sock.Socket()); // 1、创建套接字
  assert(doorman_sock.Bind(ip, port));  // 2、绑定地址信息
  assert(doorman_sock.Listen());  // 开始监听
  while(1)
  {
    tcpSocket waiter_sock;// 定义一个服务员socket
    std::string cli_ip;// 定义一个ip用来接收Accept中绑定的客户端五元组信息
    uint16_t cli_port; // 和上面ip一起来绑定信息
    bool ret = doorman_sock.Accept(&waiter_sock, &cli_ip, &cli_port);
    if(ret == false)
    {
      // 没有接收到所以没有对该服务员进行初始化,也就不需要进行销毁
      continue;
    }
    std::cout<<"newclient:"<<cli_ip<<":"<<cli_port<<std::endl;

    create_worker(waiter_sock);
  }
  doorman_sock.Close();
  return 0;
}

 create_worker中调用线程创建接口


void create_worker(tcpSocket sock)
{
  pthread_t tid;                                // 这里的arg采用一个值来硬传
  int ret = pthread_create(&tid, NULL, pthread_entry, (void*)sock.get());
  if(ret != 0)
  {
    sock.Close();
    return;
  }
  // 这里不能进行线程等待,等待就会发生阻塞,因此使用分离属性(退出后自动回收资源)
  pthread_detach(tid);
  return;
}

 线程入口函数执行收发数据功能

void *pthread_entry(void *arg)
{
  long fd = (long)arg;    // 注意使用long来接收这个值
  tcpSocket new_sock;
  new_sock.set(fd);

  while(1)
  {
    std::string buf;
    ssize_t ret = new_sock.Recv(&buf);
    if(ret == 0)
    {
      printf("connect error\n");
      new_sock.Close();
      break;
    }else if(ret == -1)
    {
      perror("recv error");
      break;
    }
    std::cout<<"client say:"<<buf<<std::endl;
    buf.clear();
    std::cout<<"sever say:";
    fflush(stdout);
    std::cin>>buf;
    ret = new_sock.Send(buf);
    if(ret < 0){
      perror("send error");
      new_sock.Close();
      break;
    }
  }
  return NULL;
}

 使用指针来处理多线程创建时的传参问题:arg

void *pthread_entry(void *arg)
{
  tcpSocket *new_sock = (tcpSocket*) arg;
  while(1)
  {
    std::string buf;
    ssize_t ret = new_sock->Recv(&buf);
    if(ret == 0)
    {
      printf("connect error\n");
      new_sock->Close();
      break;
    }else if(ret == -1)
    {
      perror("recv error");
      break;
    }
    std::cout<<"client say:"<<buf<<std::endl;
    buf.clear();
    std::cout<<"sever say:";
    fflush(stdout);
    std::cin>>buf;
    ret = new_sock->Send(buf);
    if(ret < 0){
      perror("send error");
      new_sock->Close();
      break;
    }
  }
  return NULL;
}

void create_worker(tcpSocket *sock)
{
  pthread_t tid;
  int ret = pthread_create(&tid, NULL, pthread_entry, (void*)sock);
  if(ret != 0)
  {
    sock->Close();
    return;
  }
  // 这里不能进行线程等待,等待就会发生阻塞,因此使用分离属性(退出后自动回收资源)
  pthread_detach(tid);
  return;
}

int main(int argc, char *argv[])
{
  if(argc != 3)
  {
    printf("./serve 192.168.233.128 9000\n");
    return -1;
  }
  std::string ip = argv[1];
  uint16_t port = std::stoi(argv[2]);
  tcpSocket doorman_sock;// 定义一个门迎socket
  assert(doorman_sock.Socket()); // 1、创建套接字
  assert(doorman_sock.Bind(ip, port));  // 2、绑定地址信息
  assert(doorman_sock.Listen());  // 开始监听
  while(1)
  {
    tcpSocket *waiter_sock = new tcpSocket;// 定义一个服务员socket
    std::string cli_ip;// 定义一个ip用来接收Accept中绑定的客户端五元组信息
    uint16_t cli_port; // 和上面ip一起来绑定信息
    bool ret = doorman_sock.Accept(waiter_sock, &cli_ip, &cli_port);
    if(ret == false)
    {
      // 没有接收到所以没有对该服务员进行初始化,也就不需要进行销毁
      continue;
    }
    std::cout<<"newclient:"<<cli_ip<<":"<<cli_port<<std::endl;

    create_worker(waiter_sock);
  }
  doorman_sock.Close();
  return 0;
}

 总结一下载TCP通信部分的特殊情况问题

问题一:

        连接断开:TCP是面向连接的通信,一旦断开连接就无法通信

                在recv函数接收数据的时候,返回0,代表的不仅仅是没有接收导数据,

                        更多的是为了表示连接断开了,所以在代码中if()判断之后打印connect error

                当send函数发送数据的时候,程序直接异常(SIGPIPE)退出,

                        比如登录一个qq因为我没联网,就一点开就退出,一点开就退出,这可不合理

                        因此网络通信中不想让程序因为连接断开而导致发送数据的时候异常退出,

                        使用SIGPIPE来进行处理

#include<signal.h>  
signal(SIGPIPE, SIG_IGN);

模拟实现:首先我正常运行服务端、客户端,接着我关闭掉我的服务端,然后进行输入数据的时候数据发送出去了,但是收到的回复是个空的,接着在进行输入数据,这时发生崩溃

① 为什么第一次输入的时候还可以正常输入呢?

        因为数据是发送到缓冲区中去了,等到一次收发结束了到下一次进行收发数据的时候才能检测到已经断开连接了。

② 为什么第二次输入的时候直接崩溃退出了

        这是因为触发了异常,通信连接发生中断,触发了13号信号SIGPIPE,(通信失效),所以程序直接退出了,可是正常的逻辑:一个程序不能因为连接不上网络就直接退出了,所以使用上面的signal(SIGPIEPE,SIG_IGN)来屏蔽掉这个异常信号

 处理后:就算服务端退出了,客户端也不会主动退出了

 

问题二:

 网络程序关闭之后,无法立即重新启动,会bind绑定地址错误,绑定失败,地址已经被使用

 如图:先建立起服务端,接着执行CTRL+C给他一个中断命令,然后我再重新建立服务端就会发生报错

大概过了一俩分钟后,这个地址端口又能绑定了

 这是正常的,因为一个程序主动关闭了连接,这个连接并不会立即被释放(对应的地址和端口依然被占用),而是要等待一段时间,这时候要么换一个端口绑定,要么就等一会

然而这项操作放在客户端却不会发生(因为不进行绑定嘛)

 netstat命令学习

        使用netstat,查看主机上所有网络连接状态的命令

                因为一个网络通信程序运行起来以后,如果没有达到预期的目标,首先就要看看程序对应的网络连接是否正常

        a  查看所有信息

        n  不要以服务名称显示端口或地址(22号端口被识别为ssh、237.0.0.1会被识别为localhost)

        p    显示网络连接信息的时候,顺便显示连接对应的进程ID和名称

        t        只显示tcp套接字信息

        u        只显示udp套接字信息

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

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

相关文章

【Vue3源码】第一章 effect和reactive

文章目录【Vue3源码】第一章 effect和reactive前言1、实现effect函数2、封装track函数&#xff08;依赖收集&#xff09;3、封装reactive函数4、封装trigger函数&#xff08;依赖触发&#xff09;5、单元测试【Vue3源码】第一章 effect和reactive 前言 今天就正式开始Vue3源码…

驱动 | Linux | NVMe 不完全总结

本文主要参考这里 1’ 2 的解析和 linux 源码 3。 此处推荐一个可以便捷查看 linux 源码的网站 bootlin 4。 更新&#xff1a;2022 / 02 / 11 驱动 | Linux | NVMe 不完全总结NVMe 的前世今生从系统角度看 NVMe 驱动NVMe CommandPCI 总线从架构角度看 NVMe 驱动NVMe 驱动的文件…

vue+nodejs考研资料分享系统vscode - Visual Studio Code

前端技术&#xff1a;nodejsvueelementui,视图层其实质就是vue页面&#xff0c;通过编写vue页面从而展示在浏览器中&#xff0c;编写完成的vue页面要能够和控制器类进行交互&#xff0c;从而使得用户在点击网页进行操作时能够正常。 Express 框架于Node运行环境的Web框架, 目 …

计算机网络5:数据在两台计算机之间是怎样传输的?

数据在两台计算机之间的传输总的来说包括了封装和解封两个过程 封装&#xff08;5层协议&#xff09; 以传送一张图片为例 **应用层&#xff1a;**将jpg格式的图片数据转化成计算机可以识别的0101的二进制的比特流 **传输层&#xff1a;**将应用层传输下来的数据进行分段&…

pandas数据分析35——多个数据框实现笛卡尔积

什么是笛卡尔积。就是遍历所有组合的可能性。 比如第一个盒子有[1,2,3]三个号码球&#xff0c;第二个盒子有[4,5]两个号码球。那么从每个盒子里面分别拿一个球共有3*2两种可能性&#xff0c;其集合就是{[1,4],[2,4],[3,4],[1,5],[2,5],[3,5]},这个就是笛卡尔积。 三个盒子也是…

GhostNet v2(NeurIPS 2022 Spotlight)原理与代码解析

paper&#xff1a;GhostNetV2: Enhance Cheap Operation with Long-Range Attentioncode&#xff1a;https://github.com/huawei-noah/Efficient-AI-Backbones/tree/master/ghostnetv2_pytorch背景在智能手机和可穿戴设备上部署神经网络时&#xff0c;不仅要考虑模型的性能&…

批量分析快递单号派件时效技巧

规定&#xff0c;如果出现派件延误商家没有及时处理&#xff0c;那么平台会对商家进行采取措施等等&#xff0c;同时商家每天都去跟踪自己发出单号物流状况&#xff0c;一个一个去查呢&#xff0c;不大可能&#xff0c;他们量是比较大&#xff0c;一个一个手动去官方查询工作量…

从元宇宙被认识的第一天开始,就注定了逃不脱区块链所经历的命运

从元宇宙被人们认识的第一天开始&#xff0c;它就注定了依然逃不脱它的先辈——区块链所经历的命运。如果对这样一种宿命进行一个总结的话&#xff0c;我更加愿意将其归结为以资本为主导的野蛮生长的发展模式。这种发展模式并不仅仅只是在区块链和元宇宙的身上出现&#xff0c;…

2016-ICLR-Order Matters- Sequence to sequence for sets

2016-ICLR-Order Matters- Sequence to sequence for sets Paper: [https://arxiv.org/pdf/1511.06391.pdf](https://arxiv.org/pdf/1511.06391.pdf) Code: 顺序重要性&#xff1a;集合的顺序到序列 摘要 许多需要从观察序列映射或映射到观察序列的复杂任务现在可以使用序列…

C++类基础(十一)

运算符重载&#xff08;二&#xff09; ● 对称运算符通常定义为非成员函数以支持首个操作数的类型转换 struct Str {int val 0;Str(int input): val(input){}auto operator(Str x){std::cout << "auto operator(Str x)\n";return Str(val x.val);} }; int …

git 常用命令之 git log

大家好&#xff0c;我是 17。 git log dev 查看 dev 分支的 log git log -p 每次提交所引入的差异 git log --stat 每次提交的简略统计信息 git log --name-status 比 --stat 更简洁&#xff0c;更常用。git log -2 最近两次提交 git log --since2.weeks 最近两周内的提交 git…

Linux通配符、转义符讲解

目录 通配符 通过通配符定义匹配条件 转义符 将所有的逻辑操作符都转换成字符 通配符 通过通配符定义匹配条件 * 任意字符都可以通配&#xff08;也可以匹配空值&#xff09; &#xff1f; 匹配单个字符 [a-z] 匹配单个的小写英文字母 [A-Z] 匹配单个的大写英文…

jQuery 基础知识(五)

前面已经介绍HTML、CSS和javaScript&#xff0c;这三个前端语言&#xff0c;俗称前端三剑客。下面来学习一个javaScript简化并封装好的库——jQuery库。01 - AJAX AJAX 是一种在无需重新加载整个网页的情况下&#xff0c;能够更新部分网页的技术。 AJAX的介绍 ajax 是 Asynchr…

freesurfer如何将组模板投影到个体空间——如投影 Schaefer2018 到个体空间

freesurfer如何将组模板投影到个体空间——如投影 Schaefer2018 到个体空间 freesurfer如何将组模板投影到个体空间? freesurfer如何将组模板投影到个体空间——如投影 Schaefer2018 到个体空间freesurfer的整理流程freesurfer的安装freesurfer对结构像分割流程及批处理代码fr…

git 常用命令之 git checkout

大家好&#xff0c;我是 17。 git checkout 是 git 中最重要最常用的命令之一&#xff0c;本文为大家详细解说一下。 恢复工作区 checkout 的用途之一是恢复工作区。 git checkout . checkout . 表示恢复工作区的所有更改,未跟踪的文件不会有变化。 恢复工作区的所有文件风…

硬件工程师入门基础知识(一)基础元器件认识(二)

硬件工程师入门基础知识 &#xff08;一&#xff09;基础元器件认识&#xff08;二&#xff09; tips&#xff1a;学习资料和数据来自《硬件工程师炼成之路》、百度百科、网上资料。 1.二极管 2.三极管 3.MOS管 4.IGBT 5.晶振 1.二极管 肖特基二极管和硅二极管的比较&#…

架构方法论

0.缘起最近在和同事以及相关领域的人沟通时&#xff0c;大家都在强调架构、架构图&#xff0c;于是兴起了一片关于架构的方法论介绍。本文对内容的组织按照顶层设计思路&#xff0c;先对架构本身进行剖析&#xff1a;什么是架构&#xff1f;为什么架构很重要&#xff1f;这些是…

大家心心念念的RocketMQ5.x入门手册来喽

1、前言 为了更好的拥抱云原生&#xff0c;RocketMQ5.x架构进行了大的重构&#xff0c;提出了存储与计算分离的设计架构&#xff0c;架构设计图如下所示&#xff1a; RocketMQ5.x提供了一套非常建议的消息发送、消费API&#xff0c;并统一放在Apache顶级开源项目rocketmq-clie…

T07 奥运排序问题

描述 按要求&#xff0c;给国家进行排名。 输入描述&#xff1a; 有多组数据。 第一行给出国家数N&#xff0c;要求排名的国家数M&#xff0c;国家号从0到N-1。 第二行开始的N行给定国家或地区的奥运金牌数&#xff0c;奖牌数&#xff0c;人口数&#xff08;百万&#xff09…

【c/c++】c语言的自增操作在不同编译器的差别

示例代码 代码如下&#xff1a; #include <stdio.h>#define product(x) ((x)*(x))int main(void) {int i 3, j, k;j product(i); // (i) * (i)k product(i); // (i) * (i)printf("%d %d\n", j, k); }执行结果 在Ubuntu18.04下通过GCC编译和执行的结果…