​网络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.hqwc.cn/news/590006.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Flutter 应用数据持久化指南

1. 介绍 1.1 什么是数据持久化&#xff1f; 数据持久化是指将应用程序中的数据保存在持久存储介质&#xff08;如硬盘、数据库等&#xff09;中的过程。在计算机科学领域&#xff0c;持久化数据是指数据在程序退出或系统关机后仍然存在的能力。这种持久性使得数据可以在不同的…

报告分享:2024 年小米汽车产业链分析及新品上市全景洞察报告

本文主要介绍了小米汽车在市场中的布局和优势&#xff0c;以及其面临的劣势与挑战。小米汽车凭借品牌、技术和成本三大核心优势&#xff0c;展现出强大的市场竞争力和品牌影响力&#xff0c;为消费者带来全新的汽车生活体验。文章还分析了小米汽车与小米生态链的协同效应&#…

CTF下加载CTFtraining题库以管理员身份导入 [HCTF 2018]WarmUp,之后以参赛者身份完成解题全过程

-------------------搭建CTFd------------------------------ 给大家介绍一个本地搭建比较好用的CTF比赛平台&#xff1a;CTFD。 CTFd是一个Capture The Flag框架&#xff0c;侧重于易用性和可定制性。它提供了运行CTF所需的一切&#xff0c;并且可以使用插件和主题轻松进行自…

如何在Java中,使用jackson实现json缩进美化

导入的maven依赖 <!--json--> <dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId><version>2.10.0</version> </dependency>示例代码 json要是String类型 public…

解决el-table设置固定高度后,展示不同列时表格高度变小bug

解决el-table设置固定高度后&#xff0c;展示不同列时表格高度变小bug 1、需求分析2、解决方案 1、需求分析 在el-table使用过程中&#xff0c;选择多个参数展示更多列时会出现高度变小问题究其原因可知是el-table列动态发生变化后&#xff0c;el-table__body-wrapper的高度变…

【NLP笔记】LLM应用之AI Agent LangChain实战

文章目录 AI Agent概述LangChain实战构建prompt模版LLM调用调用HuggingFace开源大模型&#xff08;在线&#xff09;调用HuggingFace开源大模型&#xff08;本地&#xff09;调用文心一言 ChainsSingle ChainSequential ChainSimple Sequential ChainComplex Sequential Chain …

Apache Pulsar源码解析之Lookup机制

引言 在学习Pulsar一段时间后&#xff0c;相信大家也或多或少听说Lookup这个词&#xff0c;今天就一起来深入剖析下Pulsar是怎么设计的它吧 Lookup是什么 在客户端跟服务端建立TCP连接前有些信息需要提前获取&#xff0c;这个获取方式就是Lookup机制。所获取的信息有以下几种…

java 解析c语言程序xml数据文件

GetMapping("/getHostMenuButtons")Operation(summary "创建试卷分类")PermitAll // PreAuthorize("ss.hasPermission(jd:index:query)")public JSONObject getJd() {//1、读取文件并转换为Document文档对象Document doc null;try {doc ne…

Spring Boot 学习(2)——HelloWorld

HelloWorld&#xff01;全宇宙码农的第一个&#xff08;行&#xff09;程序&#xff08;代码&#xff09;。 1、创建项目 打开idea&#xff0c;新建一个maven项目。 1&#xff09;选择项目sdk&#xff08;本例是1.8&#xff09; 2&#xff09;输入GroupId&#xff08;co…

Android JNI 调用第三方SO

最近一个项目使用了Go 编译了一个so库&#xff0c;但是这个so里面还需要使用第三方so库pdfium, 首先在Android工程把2个so库都放好 在jni中只能使用dlopen方式&#xff0c;其他的使用函数指针的方式来调用&#xff0c;和windows dll类似&#xff0c;不然虽然编译过了但是会崩溃…

日志服务 HarmonyOS NEXT 日志采集最佳实践

作者&#xff1a;高玉龙&#xff08;元泊&#xff09; 背景信息 随着数字化新时代的全面展开以及 5G 与物联网&#xff08;IoT&#xff09;技术的迅速普及&#xff0c;操作系统正面临前所未有的变革需求。在这个背景下&#xff0c;华为公司自主研发的鸿蒙操作系统&#xff08…

29-控制流(下):iam-apiserver服务核心功能实现讲解

我们再来看下 iam-apiserver 中的核心功能实现。 这些关键代码设计分为 3 类&#xff0c;分别是应用框架相关的特性、编程规范相关的特性和其他特性。 应用框架相关的特性 应用框架相关的特性包括三个&#xff0c;分别是优雅关停、健康检查和插件化加载中间件。 优雅关停 …