​网络socket编程(二)——面向流的TCP编程及测试(SocketTool)、Wireshark软件使用

news2024/12/16 21:28:36

目录

一、书接上回(select()函数使用注意事项)

二、面向流(TCP)的socket编程

2.1 TCP服务端编程和测试

2.1.1 TCP服务器原理流程图

2.1.2 TCP服务端编程实战

2.1.3 测试

2.2 TCP客户端编程和测试

三、Wireshark抓包软件的使用

3.1 Wireshark是什么

3.2 Wireshark抓包界面

3.3 Wireshark过滤器设置

3.3.1 抓包过滤器

3.3.2 显示过滤器

3.4 Wireshark过滤器表达式规则

3.4.1 抓包过滤器语法和示例

3.4.2 显示过滤器设置规则

四、Wireshark抓包实战演练

五、Linux下的网络抓包


一、书接上回(select()函数使用注意事项)

       在网络socket编程(一)中使用select()函数设置UDP接收数据超时时,发现了两个问题:

       ①设定一个超时时间,循环执行select()函数时第一次可以等待设置的时间,第二次及以后就会瞬间返回,根本不会等待,而且显示超时。程序是这样的:

int main()
{
    int ret;
    struct timeval timeout = {          //设置阻塞等待时间为3s
        .tv_sec = 3,
    };

    while(1)
    {
        FD_ZERO(&sockset);      //初始化文件描述符集合sockset为空
        FD_SET(sock_fd,&sockset);       //添加UDP文件描述符
        ret = select(sock_fd+1,&sockset,NULL,NULL,&timeout);//阻塞直到文件描述符有数据可读才会返回,timeout设置阻塞时间
        if(ret<0)       //返回-1表示有错误发生并设置errno
        {
            perror("select err");
            exit(1);
        }
        else if(ret == 0)       //返回0表示在任何文件描述符成为就绪态之前select()调用已经超时
        {
            printf("waitting for server ack timeout\n");
        }
        else            //返回正值表示处于就绪态的文件描述符的个数,可读了
        {
            ;
        }
    }    
    return 0;
}

       通过打印返回值发现select()在第二次执行以后返回的就一直是0,表示超时;打印timeout中的秒数发现第二次以后就变为0了,相当于不等待,这就是造成此现象的原因了。为什么定义初始化的变量值会在执行一次select()后改变?

       网上求助各位大佬看到这样一个解释:man手册查看相关信息说select()函数会更新超时参数以指示剩余多少时间,即timeout是上一次调用后剩余的时间。如果上次是超时退出的,那么下一次时间就为0。

       所以解决办法就是每次执行select()函数前重置超时时间,代码如下:

while(1)
{ 
     FD_ZERO(&sockset);      //初始化文件描述符集合sockset为空
     FD_SET(sock_fd,&sockset);       //添加UDP文件描述符
     timeout.tv_sec = 3;             //必须每次都要重新赋值,否则下次时间为0不等待就返回
     timeout.tv_usec = 0;            //不能少,否则一直阻塞
     ret = select(sock_fd+1,&sockset,NULL,NULL,&timeout);//阻塞直到文件描述符有数据可读才会返回,timeout设置阻塞时间
}

       ②一直阻塞,程序是这样的:

FD_ZERO(&sockset);      //初始化文件描述符集合sockset为空
FD_SET(sock_fd,&sockset);       //添加UDP文件描述符
timeout.tv_sec = 3;             //必须每次都要重新赋值,否则下次时间为0不等待就返回
ret = select(sock_fd+1,&sockset,NULL,NULL,&timeout);//阻塞直到文件描述符有数据可读才会返回,timeout设置阻塞时间

       此现象网上没有查到相关解释,本人也是测试时尝试对timeout的usec进行赋值,发现就正常实现每次执行select()阻塞3秒。 因此编程时需注意每次循环重置超时时间时对tv_sec和tv_usec成员都要进行赋值操作。

二、面向流(TCP)的socket编程

       TCP(Transmission Control Protocol,传输控制协议)是一种面向连接的,可靠的,基于字节流传输的通信协议。TCP具有端口号的概念,用来标识同一个地址上的不同应用。

2.1 TCP服务端编程和测试

2.1.1 TCP服务器原理流程图

       面向流的TCP服务器简单流程图如下图所示。服务器启动后创建服务器Socket,进行相应设置后始终调用accept(2)等待客户端连入。客户端正常连入后,创建一个子进程作为业务进程对特定客户端进行服务,父进程始终作为监听进程等待下一个客户端的连入。

图1 TCP服务器流程图

       其中为了防止僵尸进程出现,服务器还需要有处理子进程退出的功能,简便起见,程序范例将直接安装一个信号处理程序来处理SIGCHLD信号,此过程因为是完全异步的,并未体现在流程图上。

2.1.2 TCP服务端编程实战

       编写服务器应用程序的流程如下:

       ①调用 socket()函数打开套接字,得到套接字描述符;

       ②调用 bind()函数将套接字与IP地址、端口号进行绑定;

       ③调用listen()函数让服务器进程进入监听状态;

       ④调用accept()函数获取客户端的连接请求并建立连接;

       ⑤调用read/recv、write/send与客户端进行通信;

       ⑥调用close()关闭套接字。

       根据上面步骤编写TCP服务器应用程序,代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>


#define SERVER_PORT 8888    //端口号不能发生冲突,不常用的端口号通常大于5000
#define SERVER_IP   "192.168.2.136"         //服务器IP地址

int main()
{
    struct sockaddr_in server_addr = {0};
    struct sockaddr_in client_addr = {0};
    char ip_str[20] = {0};
    int sock_fd, conn_sock;
    int addrlen = sizeof(client_addr);
    char recvbuf[20];
    int ret,i,send_num,recv_num;
    char send_buf[20]  = "recv from server";
    struct timeval timeout;
    fd_set sockset;                     //定义文件描述符集合

    
    sock_fd = socket(AF_INET, SOCK_STREAM, 0);       //打开套接字,得到套接字描述符
    if (sock_fd < 0) 
    {
        perror("socket error");
        exit(1);
    }
    //将套接字与指定端口号进行绑定
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = inet_addr(SERVER_IP);
    server_addr.sin_port = htons(SERVER_PORT);
    //绑定套接字
    if(bind(sock_fd,(struct sockaddr *)&server_addr,sizeof(struct sockaddr_in))<0)//第二个参数需要强转,成功返回0失败返回-1
    {
		perror("bind error");
        close(sock_fd);
		exit(1);
	} 
    if(listen(sock_fd, 5))          //服务器进入监听状态,等待用户发起连接请求
    {
        perror("listen error");
        close(sock_fd);
		exit(1);
    }

    conn_sock = accept(sock_fd, (struct sockaddr *)&client_addr, &addrlen);     //阻塞等待客户端连接
    if (conn_sock < 0) 
    {
        perror("accept error");
        close(sock_fd);
        exit(1);
    }
    printf("有客户端接入:");
    inet_ntop(AF_INET, &client_addr.sin_addr.s_addr, ip_str, sizeof(ip_str));//IP地址二进制格式转换成点分十进制字符串
    printf("客户端主机IP地址为 %s,", ip_str);
    printf("客户端进程的端口号为 %d\n", client_addr.sin_port);

    // while(1)            //循环接收客户端发送过来的数据并回送
    // {
    //     memset(recvbuf, 0x0, sizeof(recvbuf));      //接收缓冲区清零
    //     ret = recv(conn_sock, recvbuf, sizeof(recvbuf), 0);     //阻塞读数据
    //     if(ret < 0) 
    //     {
    //         perror("recv error");
    //         close(conn_sock);
    //         break;
    //     }
    //     printf("recv success from client: %s\n", recvbuf);//将读取到的数据以字符串形式打印出来
    //     send_num = send(conn_sock,send_buf,sizeof(send_buf),0);     //回送客户端
    //     if(send_num < 0)
    //     {
    //         perror("send error");
    //         exit(1);
    //     } 
    //     if (strncmp("exit", recvbuf, 4) == 0)       //如果读取到"exit"则关闭套接字退出程序
    //     {
    //         printf("server exit...\n");
    //         close(conn_sock);
    //         break;
    //     }
    // }

    for(i=0;i<10;i++)       //循环10次发送数据给客户端并等待客户端回复,调用select()函数等待3s
    {
        send_num = send(conn_sock,send_buf,sizeof(send_buf),0);     //回送客户端
        if(send_num < 0)
        {
            perror("send error");
            exit(1);
        }
        else
        {
            printf("server send data to client success\n");
            FD_ZERO(&sockset);      //初始化文件描述符集合sockset为空
            FD_SET(conn_sock,&sockset);       //添加UDP文件描述符
            timeout.tv_sec = 3;             //必须每次都要重新赋值,否则下次时间为0不等待就返回
            timeout.tv_usec = 0;            //不能少,否则一直阻塞
            ret = select(conn_sock+1,&sockset,NULL,NULL,&timeout);//阻塞直到文件描述符有数据可读才会返回,timeout设置阻塞时间
            if(ret<0)       //返回-1表示有错误发生并设置errno
            {
                perror("select err");
                exit(1);
            }
            else if(ret == 0)       //返回0表示在任何文件描述符成为就绪态之前select()调用已经超时
            {
                printf("waitting for server ack timeout\n");
            }
            else            //返回正值表示处于就绪态的文件描述符的个数,可读了
            {
                printf("ret is %d,sock_fd have data to be read:\n",ret);
                if(FD_ISSET(conn_sock,&sockset))          //判断sock_fd文件描述符是否是集合中的成员
                {
                    memset(recvbuf,0,sizeof(recvbuf));
                    recv_num = recv(conn_sock,recvbuf,sizeof(recvbuf),0);
                    if(recv_num <0)
                    {
                        perror("recvfrom");
                        exit(1);
                    } 
                    else
                    {
                        printf("recvfrom success:%s\n",recvbuf);
                    }
                }
            }
        } 
    }

    close(sock_fd);     //关闭套接字
    return 0;
}

       以上我实现了一个简单的TCP服务器应用程序,64-87行实现的功能是当客户端连接到服务器后,67行recv阻塞读取客户端发送的数据,打印出来并回送数据给客户端,直到收到客户端发来的exit字符串则退出程序;89-133行实现的功能则是当客户端连接到服务器后,服务器循环10次发送数据给客户端并等待客户端回复,调用select()函数等待3s,超时进行下一次发送数据。注意后续无论是读还是写数据都是针对accept接收客户端连接返回的新的文件描述符。

2.1.3 测试

       程序交叉编译后在开发板上运行作TCP服务端,PC机上运行SocketTool工具新建TCP客户端,PC机和开发板通过网线直连并设置在同一号段。客户端设置要连接的服务端IP和端口如下图所示,对应程序中填充的sockaddr_in结构。

图2 TCP客户端设置

       首先测试接收客户端数据并回送功能,开发板运行程序,然后在SocketTool客户端点击“连接”,开发板控制台终端就会打印有客户机连接,随后在SocketTool客户端发送数据,可以在控制台上看到数据接收成功,完整的测试过程如下:

TCP服务端socket编程测试结果

       最后测试服务端率先发送数据到客户端并调用select函数等待客户端回送数据。在SocketTool窗口接收到来自开发板服务端数据,若在3s内回送数据则服务端能够接收,否则超时服务端不再等待。完整的测试过程如下:

TCP服务端socket编程测试结果2

2.2 TCP客户端编程和测试

       对于客户机程序,处理流程见下图。启动后立即创建Socket,并且直接调用connect(2)连接服务器,省去bind(2)调用,系统将会将刚才创建的Socket隐式绑定到一个随机端口上。connect(2)后直接发送数据到服务器,发送完毕有直接读取服务器回发的数据并打印接收到的数据后结束。

图3 TCP客户端流程图

       具体实现代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>


#define SERVER_PORT 8888                //服务器的端口号
#define SERVER_IP "192.168.2.100"       //服务器的 IP 地址

int main()
{
    struct sockaddr_in server_addr = {0};
    char buf[20];
    int sock_fd;
    int ret,i,recv_num;
    fd_set sockset;                     //定义文件描述符集合
    struct timeval timeout;

    sock_fd = socket(AF_INET, SOCK_STREAM, 0);      //打开套接字,得到套接字描述符
    if (sock_fd < 0) 
    {
        perror("socket error");
        exit(1);
    }
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(SERVER_PORT); //端口号
    server_addr.sin_addr.s_addr = inet_addr(SERVER_IP);//也可以这样填充IP地址
    // inet_pton(AF_INET, SERVER_IP, &server_addr.sin_addr);//IP地址,将字符串转换为二进制,第三个参数必须是struct in_addr对象
    ret = connect(sock_fd, (struct sockaddr *)&server_addr, sizeof(server_addr));  //调用connect(阻塞)连接远端服务器
    if (ret < 0) 
    {
        perror("connect error");
        close(sock_fd);
        exit(1);
    }
    printf("服务器连接成功...\n\n");
    
    for(i=0;i<10;i++)      //向服务器发送数据 
    { 
        memset(buf, 0, sizeof(buf));            //清理缓冲区
        strcpy(buf,"hello,yrr!");
        ret = send(sock_fd, buf, strlen(buf), 0);        //将输入的数据发送给服务器
        if(ret < 0)
        {
            perror("send error");
            break;
        }
        else
        {
            printf("send success:%s ,waitting for server ack\n",buf);
            FD_ZERO(&sockset);      //初始化文件描述符集合sockset为空
            FD_SET(sock_fd,&sockset);       //添加UDP文件描述符
            timeout.tv_sec = 3;             //必须每次都要重新赋值,否则下次时间为0不等待就返回
            timeout.tv_usec = 0;            //不能少,否则一直阻塞
            ret = select(sock_fd+1,&sockset,NULL,NULL,&timeout);//阻塞直到文件描述符有数据可读才会返回,timeout设置阻塞时间
            if(ret<0)       //返回-1表示有错误发生并设置errno
            {
                perror("select err");
                exit(1);
            }
            else if(ret == 0)       //返回0表示在任何文件描述符成为就绪态之前select()调用已经超时
            {
                printf("waitting for server ack timeout\n");
            }
            else            //返回正值表示处于就绪态的文件描述符的个数,可读了
            {
                printf("ret is %d,sock_fd have data to be read:\n",ret);
                if(FD_ISSET(sock_fd,&sockset))          //判断sock_fd文件描述符是否是集合中的成员
                {
                    memset(buf,0,sizeof(buf));
                    recv_num = read(sock_fd,buf,sizeof(buf));
                    if(recv_num <0)
                    {
                        perror("read");
                        exit(1);
                    } 
                    else
                    {
                        printf("read success from client:%s\n",buf);
                    }
                }
            }
        }
    }
    close(sock_fd);

    return 0;
}

      程序交叉编译后在开发板上运行作TCP客户端,PC机上运行TCP_tester工具新建TCP服务端,设置端口为8888,PC机和开发板通过网线直连并设置在同一号段。点击“开始侦听”,开发板上运行程序就可以连接到服务器,然后就可以实现收发数据了。测试的完整过程如下:

TCP客户端socket编程测试结果

三、Wireshark抓包软件的使用

3.1 Wireshark是什么

       Wireshark是非常流行的网络封包分析软件,简称小鲨鱼,功能十分强大。可以截取各种网络封包,显示网络封包的详细信息。Wireshark是开源软件,可以放心使用。可以运行在Windows和Mac OS上。对应的,linux下的抓包工具是tcpdump。使用Wireshark的人必须了解网络协议,否则就看不懂Wireshark了。

       只要涉及到网络通信,尤其是调试过程中遇见网络不通或是数据异常就需要借助Wireshark工具抓包分析了,WireShark是非常流行的网络封包分析工具,可以截取各种网络数据包,并显示数据包详细信息。常用于开发测试过程中各种问题定位。

3.2 Wireshark抓包界面

        Wireshark软件打开后主界面如下图所示,选择对应的网卡,右键会出现Start Capture(开始捕获),点击即可进行捕获该网络信息,开始抓取网络包;也可点击上方工具栏“鲨鱼”形按键快速开始捕获。

图4 Wireshark初始主界面

       双击“WLAN”对无限网卡上的网络数据进行抓包,进入抓包界面,如下图所示:

图5 抓包界面

       主要是以下几个模块:

       显示过滤器:用于设置过滤条件进行数据包列表过滤。

     数据包列表:显示捕获到的数据包,每个数据包包含编号(No)、时间戳(Time)、源地址(Source)、目标地址(Destination)、协议(Protocol)、长度(Length)、数据包信息(Info)。不通协议的数据包使用了不同的颜色区分显示。协议颜色标识定位在菜单栏视图→着色规则。

       数据包详细信息:在数据包列表中选择指定数据包,在数据包详细信息中会显示该数据包的所有详细内容。数据包详细信息面板是最重要的,用来查看协议中的每一个字段。不同的协议数据信息列表是不一样的,UDP显示的各行信息是如下图所示这样的:

图6 UDP数据详细信息列表

       而TCP如下图,主要区别在于传输层协议不同:

图7 TCP数据详细信息列表

       ①Frame:物理层的数据帧概况,具体信息如下图所示:

图8 Frame物理层数据帧情况

       ②Ethernet II:数据链路层以太网帧头部信息,具体信息如下图所示:

图9 Ethernet II数据链路层以太网帧头部信息

       ③Internet Protocol Version 4:互联网层IP包头部信息,具体信息如下图所示:

图10 Internet Protocol Version 4互联网层IP包头部信息

       ④User Datagram Protocol / Transmission Control Protocol:传输层数据段头部信息,分为TCP协议和UDP协议,如下图所示:

图11 传输层TCP头部信息

       ⑤data:应用层数据报文信息,对于其他协议有不同的名称,例如HTTP协议是HypertextTransfer Protocol。

       数据包字节区:报文原始内容,界面如下图:

图12 数据包字节区报文原始内容

3.3 Wireshark过滤器设置

       使用Wireshark可能会得到大量的冗余数据包列表,以至于很难找到自己需要抓取的数据包部分。Wireshark工具中自带了两种类型的过滤器,抓包过滤器和显示过滤器,一定要区分两者概念。

3.3.1 抓包过滤器

       捕获过滤器的菜单栏路径为捕获→捕获过滤器。用于在抓取数据包前设置

图13 Wireshark捕获过滤器界面

       可点击“+”添加新的抓包过滤条件,默认是捕获所有符合上述条件的网络数据包,可以添加过滤条件,如ip host 192.168.2.136表示只捕获主机IP为192.168.2.136的数据包。

3.3.2 显示过滤器

       显示过滤器是用于在抓取数据包后设置过滤条件进行过滤数据包。通常是在抓取数据包时设置条件相对宽泛或者没有设置导致抓取的数据包内容较多时使用显示过滤器设置条件过滤以方便分析。显示过滤器就在主界面上方,如下图:

图14 显示过滤器界面

        以直接通过无线网卡捕获所有数据包为例,未设置抓包过滤规则,数据包列表中含有大量的无效数据。这时就可以通过设置显示过滤器条件进行提取分析信息。例如输入ip.addr == 192.168.2.136进行过滤就可以滤除掉大量我们不想看到的数据信息。

3.4 Wireshark过滤器表达式规则

3.4.1 抓包过滤器语法和示例

     (1)协议过滤,比较简单,直接在抓包过滤框中直接输入协议名即可,如下所示:

       ①tcp,只显示TCP协议的数据包列表

       ②http,只查看HTTP协议的数据包列表

       ③icmp,只显示ICMP协议的数据包列表

     (2)IP过滤

       ①host 192.168.1.104

       ②src host 192.168.1.104

       ③dst host 192.168.1.104

     (3)端口过滤

       ①port 80

       ②src port 80

       ③dst port 80

     (4)逻辑运算符与&&、或|| 、非!

       ①src host 192.168.1.104 && dst port 80:抓取主机地址为192.168.1.80、目的端口为80的数据包

       ②host 192.168.1.104 || host 192.168.1.102:抓取主机为192.168.1.104或者192.168.1.102的数据包

       ③! broadcast:不抓取广播数据包

3.4.2 显示过滤器设置规则

      (1)比较操作符有:==等于、!= 不等于、>大于、<小于、>=大于等于、<=小于等于。

     (2)协议过滤:比较简单,直接在Filter框中直接输入协议名即可。注意协议名称需要输入小写。tcp,只显示TCP协议的数据包列表;http,只查看HTTP协议的数据包列表;icmp,只显示ICMP协议的数据包列表。

     (3)ip过滤:

       ip.src ==222.134.133.110:显示源地址为222.134.133.110的数据包列表;

       ip.dst == 222.134.133.110: 显示目标地址为222.134.133.110的数据包列表;

       ip.addr == 222.134.133.110:显示源IP地址或目标IP地址为222.134.133.110的数据包列表。

     (4)端口过滤:

       tcp.port ==80:显示源主机或者目的主机端口为80的数据包列表。

       tcp.srcport == 80:只显示TCP协议的源主机端口为80的数据包列表。

       tcp.dstport == 80:只显示TCP协议的目的主机端口为80的数据包列表。

   (5)逻辑运算符为 and/or/not:过滤多个条件组合时,使用and/or。比如获取IP地址为192.168.2.136的ICMP数据包表达式为ip.addr == 192.168.2.136 and icmp。

四、Wireshark抓包实战演练

       以上面的TCP客户端测试作为实战背景,在PC机上使用Wireshark进行抓包。开发板上运行TCP客户端发送程序,PC机上TCP_tester工具作为服务端接收数据并回送。对PC机以太网进行抓包,设置显示过滤条件为ip.addr == 192.168.2.136。

       首先开发板上运行客户端程序,服务端还没开始侦听,如下图所示可以看到开发板一直在发送请求建立连接报文,也就是第一次握手数据包,协议为TCP,标志位为[SYN](表示请求建立连接),序列号Seq=0(从0开始表示还没有发送数据),Ack=0(表示已经收到包的数量,0表示当前没有接收到数据)。

图15 TCP第一次握手报文

       服务端点击“开始侦听”,TCP建立连接,可以看到三次握手报文,如下图所示:

图16 TCP三次握手报文

       编号为7的数据包就是二次握手报文,标志位为[SYN,ACK]表示同意建立连接,确认序号Ack+1=1,seq=0表示还没有发送数据。编号为8的数据包就是三次握手报文,标志位为[ACK]表示已经收到,Seq=1表示当前已发送1个数据,Ack=1表示当前端成功收到的数据位数。

       随后就可以进行数据交互了,点击一个客户端发送的数据包,可以看到发送的具体内容,如下图所示:

图17 捕获数据包内容

       过滤的数据包列表中可以看到只有ip为192.168.2.136的网络包,还可以在Frame信息中看到数据包发送的具体时刻。

五、Linux下的网络抓包

       tcpdump是Linux系统下的一个强大的命令,可以将网络中传送的数据包完全截获下来提供分析。它支持针对网络层、协议、主机、网络或端口的过滤,并提供and、or、not等逻辑语句来帮助你去掉无用的信息。

       tcpdump命令可以搭配很多种参数,本文不一一列举,网上有很多可供查阅的资料。下面介绍一些常用的命令示例初步了解tcpdump的使用。

       ①抓取地址包含是192.168.2.136的包,并将结果保存到test.cap文件中的命令是:

tcpdump host 192.168.2.136
tcpdump host 192.168.2.136 -w result.cap

       ②抓取指定协议格式的数据包,协议格式可以是udp、icmp、arp、ip中的任何一种,例如以下命令:

tcpdump udp  -i eth0 -vnn

       ③抓取本机端口是22的数据包:

tcpdump port 22 
tcpdump port 22 -w test.cap

       如果需要详细查看报文情况,用tcpdump port 30080 -w file.cap命令,运行后等待将捕获的数据包详细信息写入文件中,此时并不打印出来,ctrl + C结束抓包后可用Wireshark软件打开分析。下面演示在运行Linux操作系统的开发板上使用tcpdump命令进行网络抓包。

       PC机用网线连接开发板eth1网口,开发板上运行tcpdump -i eth1命令指定对eth1网卡抓包(如果不指定网卡,默认tcpdump只会监视第一个网络接口,一般是eth0)。在电脑上ping开发板,可以看到控制台终端打印捕获的网络包信息,如下图所示:

图18 tcpdump抓包打印信息

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

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

相关文章

fastlio2 给 interactive-slam 保存每帧的点云和每帧的里程计为单独的文件做后端回环优化和手动回环优化

为了给 interactive-slam 提供数据做后端回环优化和手动回环优化,需要保存每帧的点云和每帧的里程计为单独的文件,并且需要保存的名字为ros时间戳。 效果很好,比我自己写的手动回环模块好用 // This is an advanced implementation of the algorithm described in the // fo…

Jmeter各组件超详细介绍

1、JMeter和Loadrunner的区别&#xff1f; 2、JMeter如何开发脚本的&#xff1f;强化脚本的技术&#xff1f; 代理服务器录制脚本&#xff0c;Fiddler录制脚本&#xff0c;Badboy录制脚本&#xff0c;根据API&#xff0c;手写脚本&#xff0c;根据抓包&#xff0c;手写脚本。 …

Twitter Api查询用户粉丝列表

如果大家为了获取实现方式代码的话可能要让大家失望了&#xff0c;这边文章主要是为了节省大家开发时间&#xff0c;少点坑。https://api.twitter.com/2/users/:id/followers &#xff0c;这个接口很熟悉吧&#xff0c;他是推特提供的获取用户关注者&#xff08;粉丝&#xff0…

目标跟踪——行人车辆数据集

一、重要性及意义 首先&#xff0c;目标跟踪对于个人和组织的目标实现至关重要。无论是个人职业发展、企业业务增长还是政府的社会发展&#xff0c;目标跟踪都能够帮助我们明确目标&#xff0c;并将其分解为可行的步骤和时间表。这有助于我们保持动力和专注&#xff0c;提高效…

CNAS软件测试公司有什么好处?如何选择靠谱的软件测试公司?

CNAS认可是中国合格评定国家认可委员会的英文缩写&#xff0c;由国家认证认可监督管理委员会批准设立并授权的国家认可机构&#xff0c;统一负责对认证机构、实验室和检验机构等相关机构的认可工作。 在软件测试行业&#xff0c;CNAS认可具有重要意义。它标志着一个软件测试公…

Java并发编程基础面试题详细总结

1. 什么是线程和进程? 1.1 何为进程? 进程是程序的一次执行过程&#xff0c;是系统运行程序的基本单位&#xff0c;因此进程是动态的。系统运行一个程序即是一个进程从创建&#xff0c;运行到消亡的过程。 在 Java 中&#xff0c;当我们启动 main 函数时其实就是启动了一个…

Failed to resolve import “Home/components/HomeNew.vue“. Does the file exist?

错误信息 [plugin:vite:import-analysis] Failed to resolve import "/apis/home.js" from "src/views/Home/components/HomeNew.vue". Does the file exist? 错误原因 路径错误 解决方法

[C#]使用OpencvSharp去除面积较小的连通域

【C介绍】 关于opencv实现有比较好的算法&#xff0c;可以参考这个博客OpenCV去除面积较小的连通域_c#opencv 筛选小面积区域-CSDN博客 但是没有对应opencvsharp实现同类算法&#xff0c;为了照顾懂C#编程同学们&#xff0c;因此将 去除面积较小的连通域算法转成C#代码。 方…

Open CASCADE学习|刚体( TopoDS_Shape)按某种轨迹运动,停在指定位置上

今天实现如下功能&#xff1a;刚体做做螺旋运动&#xff0c;轨迹已知&#xff0c;求刚体在每个位置上的所占据的空间&#xff0c;就是把刚体从初始位置变换到该位置。 这里的刚体是一个砂轮截面&#xff0c;螺旋运动轨迹由B样条曲线拟合&#xff0c;通过Frenet标架确定运动轨迹…

Spring Boot集成AJ-Captcha实现滑动验证码功能

1.AJ-Captcha介绍 行为验证码 采用嵌入式集成方式&#xff0c;接入方便&#xff0c;安全&#xff0c;高效。抛弃了传统字符型验证码展示-填写字符-比对答案的流程&#xff0c;采用验证码展示-采集用户行为-分析用户行为流程&#xff0c;用户只需要产生指定的行为轨迹&#xff0…

WPS二次开发系列:如何获取应用签名SHA256值

在申请WPS SDK授权版时候需要开发者提供应用包名和签名&#xff0c;应用包名好说&#xff0c;那如何生成符合WPS要求的应用签名&#xff08;SHA256)呢&#xff0c;经笔者亲测&#xff0c;有如下两种方式可以实现获取第三方应用签名值&#xff08;SHA256&#xff09; 1. 方法一&…

springboot对接minio的webhook全过程

前言 近日需要将minio的apache2.0版本给用起来&#xff0c;顺便要完善一下原有的文件上传管理系统&#xff0c;其中很重要的一点是&#xff0c;在原有客户端直传的基础上&#xff0c;再添加 minio 的上传回调给服务端做后续处理。 本文重点在于&#xff0c;介绍整个minio与spr…

SpringCloud学习(1)-consul

consul下载安装及使用 1.consul简介 Consul是一种开源的、分布式的服务发现和配置管理工具&#xff0c;能够帮助开发人员构建和管理现代化的分布式系统。它提供了一套完整的功能&#xff0c;包括服务注册与发现、健康检查、KV存储、多数据中心支持等&#xff0c;可以帮助开发人…

Jenkins--任务详解

一、任务类型 Jenkins的主要功能的实现是由执行任务去完成的&#xff0c;常用的任务类型主要有以下三种&#xff1a; 自由风格任务(Free Style Project): 这是Jenkins中最常用的任务类型&#xff0c;允许你自定义各种构建步骤和配置选项&#xff0c;如源码管理、构建触发器、…

vue3+echarts:echarts地图打点显示的样式

colorStops是打点的颜色和呼吸灯、label为show是打点是否显示数据、rich里cnNum是自定义的过滤模板用来改写显示数据的样式 series: [{type: "effectScatter",coordinateSystem: "geo",rippleEffect: {brushType: "stroke",},showEffectOn: &quo…

Redis的值有5种数据结构,不同数据结构的使用场景是什么?

文章目录 字符串缓存计数共享Session限速 哈希缓存 列表消息队列文章列表栈队列有限集合 集合标签抽奖社交需求 有序集合排行榜系统 字符串 缓存 &#xff08;1&#xff09;使用原生字符类型缓存 优点&#xff1a;简单直观&#xff0c;每个属性都支持更新操作 缺点&#xff1…

大话设计模式之状态模式

状态模式是一种行为设计模式&#xff0c;它允许对象在其内部状态发生变化时改变其行为。在状态模式中&#xff0c;对象将其行为委托给当前状态对象&#xff0c;从而在不同的状态下执行不同的行为&#xff0c;而不必在对象自身的代码中包含大量的条件语句。 通常&#xff0c;状…

WE博客代码系统

WE博客代码系统 说明文档 运行前附加数据库.mdf&#xff08;或sql生成数据库&#xff09; 主要技术&#xff1a; 基于asp.net mvc架构和sql server数据库&#xff0c;并采用EF实体模型开发。 三层架构并采用EF实体模型开发 功能模块&#xff1a; WE博客代码系统 WE博客代码系…

使用SpringBoot实现的登录注册后端功能

1、系统演示视频&#xff08;演示视频&#xff09; 2、需要交流和学习请联系

java网络编程——网络编程概述及UDP/TCP通信编程的实现

前言&#xff1a; 学习到通信了&#xff0c;整理下相关知识点。打好基础&#xff0c;daydayup!!! 网络编程 网络编程指可以让设备中的程序与网络上其他设备中的程序进行数据交互。 基本的通信架构 基本的通信架构有两种形式&#xff1a;CS架构&#xff08;Client客户端/Server服…