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

目录

一、书接上回(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.mfbz.cn/a/514585.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

vue 数据埋点

最近菜鸟做项目&#xff0c;需要做简单的数据埋点&#xff0c;不是企业级的&#xff0c;反正看渡一的视频&#xff0c;企业级特别复杂&#xff0c;包括但不限于&#xff1a;错误收集、点击地方、用户行为…… 菜鸟的需求就是简单收集一下用户的ip、地址、每个界面的访问时间&a…

doccano标注工具|为机器学习建模做数据标注

目录 一、标记流程 二、配置环境 2.1 安装 2.2 运行doccano 三、案例 3.1 创建项目 3.2 上传数据 3.3 定义标签 3.4 添加成员 3.5 开始标注 3.6 导出数据 3.7 导出数据 doccano doccano是开源的数据…

在ssh 工具 Linux screen会话中使用鼠标进行上下滚动

经过几次发现 除xshell外&#xff0c; WindTerm finalshell MobaXterm 都是进入会话后&#xff0c;发现其界面无法滚动屏幕向上查看 如果想要在Linux screen会话中使用鼠标进行上下滚动。必须首先进入该screen的回滚(scrollback模式)才能进行上下滚动 第一步&#xff…

小白理智进入IT行业

职业测试 &#x1f514; 0基础请先按照以下步骤做一遍&#xff0c;试试自己的毅力 1️⃣ 请在b站搜索王佩丰vba&#xff0c;然后试着看一下 2️⃣ 看完视频&#xff0c;有两种结果 &#x1f62d; 视频看到一半就终止&#xff0c;代码只会复制粘贴。 &#x1f389; 看完全部视…

MHA高可用-解决MySQL主从复制的单点问题

目录 一、MHA的介绍 1&#xff0e;什么是 MHA 2&#xff0e;MHA 的组成 2.1 MHA Node&#xff08;数据节点&#xff09; 2.2 MHA Manager&#xff08;管理节点&#xff09; 3&#xff0e;MHA 的特点 4. MHA工作原理总结如下&#xff1a; 二、搭建 MySQL MHA 实验环境 …

mongoDB 优化(2)索引

执行计划 语法&#xff1a; db.collection_xxx_t.find({"param":"xxxxxxx"}).explain(executionStats) 感觉这篇文章写得很好&#xff0c;可以参考 MongoDB——索引&#xff08;单索引&#xff0c;复合索引&#xff0c;索引创建、使用&#xff09;_mongo …

软考高级架构师:CISC (复杂指令集计算机) 和 RISC (精简指令集计算机)概念和例题

作者&#xff1a;明明如月学长&#xff0c; CSDN 博客专家&#xff0c;大厂高级 Java 工程师&#xff0c;《性能优化方法论》作者、《解锁大厂思维&#xff1a;剖析《阿里巴巴Java开发手册》》、《再学经典&#xff1a;《Effective Java》独家解析》专栏作者。 热门文章推荐&am…

开源博客项目Blog .NET Core源码学习(13:App.Hosting项目结构分析-1)

开源博客项目Blog的App.Hosting项目为MVC架构的&#xff0c;主要定义或保存博客网站前台内容显示页面及后台数据管理页面相关的控制器类、页面、js/css/images文件&#xff0c;页面使用基于layui的Razor页面&#xff08;最早学习本项目就是想学习layui的用法&#xff0c;不过最…

flink1.18源码本地调试环境

01 源码本地调试环境搭建 1. 从github拉取源码创建本地项⽬ https://github.com/apache/flink.git 可以拉取github上官⽅代码 https://github.com/apache/flink.git GitHub - apache/flink: Apache Flink 2. 配置编译环境 ctrlaltshifts &#xff08;或菜单&#xff09;打…

UE5启用SteamOS流程

一、安装OnlineSubsystemSteam插件 1、在UE里安装OnlineSubsystemSteam 2、设置默认开始地图 3、设置DefaultEngine.ini文件&#xff1a; 打开项目根目录/Config/DefaultEngine.ini文件 打开官网的配置说明 复制并粘贴到该文件中 4、设置运行模式 5、测试 确保Steam平台已…

Ansible批量操作(上传文件、删除文件指定文件内容、执行sh文件等)

官方网站 https://www.ansible.com/ 一、Ansible 简介 1、Ansible是新出现的自动化运维工具&#xff0c;完全基于Python开发&#xff0c;集合了众多运维工具&#xff08;puppet、chef、func、fabric&#xff09;的优点&#xff0c;实现了批量系统配置、批量程序部署、批量运行…

【详解】Windows系统安装Nginx及简单使用

【详解】Windows系统安装Nginx及简单使用 一、Nginx是什么&#xff1f; “Nginx 是一款轻量级的 HTTP 服务器&#xff0c;采用事件驱动的异步非阻塞处理方式框架&#xff0c;这让其具有极好的 IO 性能&#xff0c;时常用于服务端的反向代理和负载均衡。”Nginx 是一款 http 服…

RocketMQ是什么?

文章目录 一、RocketMQ是什么&#xff1f;二、RocketMQ 应用场景三、RocketMQ 优缺点1.优点2、缺点 一、RocketMQ是什么&#xff1f; RocketMQ 是一款纯 java、分布式、队列模型的消息中间件&#xff0c;支持事务消息、顺序消息、批量消息、定时消息、消息回溯等。 二、Rocke…

软件杯 深度学习YOLO抽烟行为检测 - python opencv

文章目录 1 前言1 课题背景2 实现效果3 Yolov5算法3.1 简介3.2 相关技术 4 数据集处理及实验5 部分核心代码6 最后 1 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 基于深度学习YOLO抽烟行为检测 该项目较为新颖&#xff0c;适合作为竞赛课…

BGP-(as-path-filter)

BGP-as-path-filter&#xff0c;缺省 as-path-filter&#xff0c;正则表达式&#xff0c;as-path过滤器&#xff0c;对于BGP的as-path属性实际上可以看成是一个包含空格的字符串。 特点&#xff1a;1、通过对BGP路由的as-path属性进行匹配达到对BGP路由的过滤。 2、在route-…

OpenHarmony实战:轻量级系统之移植验证

OpenHarmony芯片移植完成后&#xff0c;需要开展OpenHarmony兼容性测试以及芯片SDK功能性测试。除可获得测试认证之外&#xff0c;还可以在开发阶段提前发现缺陷&#xff0c;大幅提高代码质量。 OpenHarmony兼容性测试 OpenHarmony兼容性测试是XTS&#xff08;OpenHarmony生态…

传统模型用腻了?GCN图卷积神经网络一键实现西储大学轴承故障诊断!发文新思路!

​ 声明&#xff1a;文章是从本人公众号中复制而来&#xff0c;因此&#xff0c;想最新最快了解各类智能优化算法及其改进的朋友&#xff0c;可关注我的公众号&#xff1a;强盛机器学习&#xff0c;不定期会有很多免费代码分享~ 目录 数据介绍与故障诊断讲解 1.数据…

[lesson04]布尔类型和引用

布尔类型和引用 布尔类型 C中的布尔类型 C在C语言的基本类型系统上增加了boolC中的bool可取的值只有true和false理论上bool只占用一个字节 注意&#xff1a; true代表真值&#xff0c;编译器内部用1来表示 false代表非真值&#xff0c;编译器内部用0来表示 bool类型只有true(…

RabbitMQ3.x之六_RabbitMQ使用场景

RabbitMQ3.x之六_RabbitMQ使用场景 文章目录 RabbitMQ3.x之六_RabbitMQ使用场景1. 为什么选择 RabbitMQ&#xff1f;1. 可互操作2. 灵活3. 可靠 2. 常见用户案例1. 服务解耦2. 远程过程调用3. 流处理4. 物联网 1. 为什么选择 RabbitMQ&#xff1f; RabbitMQ 是一个可靠且成熟的…

IDEA 中能提高开发效率的插件

目录 前言 插件 Rainbow Brackets AceJump POJO to JSON Json Helper MybatisX Maven Helper PlantUML Integration TONYYI Lingma 前言 IDEA 里又很多好用的插件可以帮助我们提升开发效率&#xff0c;这里罗列下自己开发过程中常用的插件&#xff0c;善于利用插件&…
最新文章