如何使用ebpf统计某个端口的流量

news2024/12/28 5:17:32

前言

        上篇文章我们已经初步使用kprobe来探测内核函数了, 这篇文章就在上篇文章的基础上做个修改, 通过kprobe探测内核函数tcp_sendmsg来统计tcp服务端的发送流量. 废话不多说, 直接上正文. 

环境

        tcp服务端运行在ubuntu22, 监听端口为6230, 其内核为5.19.0-26-generic, ebpf程序同样运行在ubuntu22.

        tcp客户端运行在centos7, 其内核为3.10.0-1160.el7.x86_64. 

代码

        ebpf代码同样分为两个文件, 一个是内核态代码, 探测内核函数, 并把相关信息写入map, 在本文中是tcp服务端发送的流量大小(Byte). 一个是用户态代码, 定时读取map并打印流量大小(Byte).

        for_flow_kern.c代码如下:

// for_flow_kern.c

#include <linux/skbuff.h>
#include <linux/netdevice.h>
#include <linux/version.h>
#include <uapi/linux/bpf.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
#include <bpf/bpf_core_read.h>
#include <net/sock.h>
#include "trace_common.h"

#define MAX_ENTRIES 64

struct {
	__uint(type, BPF_MAP_TYPE_HASH);
	__type(key, u64);
	__type(value, u32);
	__uint(max_entries, MAX_ENTRIES);
} pid_2_port_map SEC(".maps");

struct {
	__uint(type, BPF_MAP_TYPE_HASH);
	__type(key, u64);
	__type(value, u64);
	__uint(max_entries, MAX_ENTRIES);
} pid_2_flow_map SEC(".maps");

SEC("kprobe/tcp_sendmsg")
int trace_sys_send(struct pt_regs *ctx)
{
	int ret = 0;
	u16 family = 0;
	struct sock *sock = (struct sock *)PT_REGS_PARM1_CORE(ctx);

	if ((ret = bpf_probe_read_kernel(&family, sizeof(family), &sock->sk_family)))
	{
		return 0;
	}
	if (family != AF_INET)
	{
		return 0;
	}

	u16 port_tmp = 0;
	if ((ret = bpf_probe_read_kernel(&port_tmp, sizeof(port_tmp), &sock->sk_num)))
	{
		return 0;
	}

	if (port_tmp == 6230)
	{
		u64 pid = bpf_get_current_pid_tgid();
		u32 port = port_tmp;
		bpf_map_update_elem(&pid_2_port_map, (const void *)&pid, &port, BPF_ANY);
	}
	return 0;
}

SEC("kretprobe/tcp_sendmsg")
int trace_sys_send_ret(struct pt_regs *ctx)
{
	int ret = 0;
	u64 pid = bpf_get_current_pid_tgid();

	// 获取pid
	u32 *value_ptr = bpf_map_lookup_elem(&pid_2_port_map, &pid);
	if (!value_ptr)
	{
		return 0;
	}

	// 获取tcp_sendmsg返回值
	int size = PT_REGS_RC(ctx);
	if (size > 0)
	{
		// 查找flow
		u64 *flow_ptr = bpf_map_lookup_elem(&pid_2_flow_map, &pid);
		u64 sum = flow_ptr == NULL ? (0 + size) : (*flow_ptr + size); 
		bpf_map_update_elem(&pid_2_flow_map, &pid, &sum, BPF_ANY);
	}
	return 0;
}

char _license[] SEC("license") = "GPL";
u32 _version SEC("version") = LINUX_VERSION_CODE;

        for_flow_user.c代码如下:

// for_flow_user.c

#define _GNU_SOURCE
#include <sched.h>
#include <stdio.h>
#include <sys/types.h>
#include <asm/unistd.h>
#include <unistd.h>
#include <assert.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <signal.h>
#include <string.h>
#include <time.h>
#include <arpa/inet.h>
#include <errno.h>

#include <bpf/bpf.h>
#include <bpf/libbpf.h>

int main(int argc, char **argv)
{
        struct bpf_object *obj;
        int i = 0;
        int mfd1 = 0;
        struct bpf_link *link1 = NULL;
	struct bpf_program *prog1;
        int mfd2 = 0;
        struct bpf_link *link2 = NULL;
	struct bpf_program *prog2;
	char filename[256];
        snprintf(filename, sizeof(filename), "%s_kern.o", argv[0]);

        // obj
	obj = bpf_object__open_file(filename, NULL);
	if (libbpf_get_error(obj)) 
        {
		fprintf(stderr, "ERROR: opening BPF object file failed\n");
		return -1;
	}
	if (bpf_object__load(obj)) 
        {
		fprintf(stderr, "ERROR: loading BPF object file failed\n");
		goto END;
	}


        // ------------- //
        prog1 = bpf_object__find_program_by_name(obj, "trace_sys_send");
	if (!prog1) 
        {
		printf("finding a prog1 in obj file failed\n");
		goto END;
	}
        prog2 = bpf_object__find_program_by_name(obj, "trace_sys_send_ret");
	if (!prog2) 
        {
		printf("finding a prog2 in obj file failed\n");
		goto END;
	}

        // ------------- //
        mfd1 = bpf_object__find_map_fd_by_name(obj, "pid_2_port_map");
        if (mfd1 < 0)
        {
		fprintf(stderr, "ERROR: finding a map mfd1 in obj file failed\n");
		goto END;    
        }
        mfd2 = bpf_object__find_map_fd_by_name(obj, "pid_2_flow_map");
        if (mfd2 < 0)
        {
		fprintf(stderr, "ERROR: finding a map mfd2 in obj file failed\n");
		goto END;    
        }

        // ------------- //
	link1 = bpf_program__attach(prog1);
	if (libbpf_get_error(link1)) 
        {
		fprintf(stderr, "ERROR: bpf_program__attach link1 failed\n");
		link1 = NULL;
		goto END;
	}
        link2 = bpf_program__attach(prog2);
	if (libbpf_get_error(link2)) 
        {
		fprintf(stderr, "ERROR: bpf_program__attach link2 failed\n");
		link2 = NULL;
		goto END;
	}

        for (i = 0; i < 1000; i++)
        {
                unsigned long long key = 0;
                unsigned long long next_key = 0;
                int j = 0;
                while (bpf_map_get_next_key(mfd2, &key, &next_key) == 0) 
                {
                        unsigned int value = 0;
                        bpf_map_lookup_elem(mfd1, &next_key, &value);
                        fprintf(stdout, "pid%d: %llu, flow: %u\n", ++j, next_key, value);
                        key = next_key;
                }

                key = 0;
                next_key = 0;
                j = 0;
                while (bpf_map_get_next_key(mfd2, &key, &next_key) == 0) 
                {
                        unsigned long long value = 0;
                        bpf_map_lookup_elem(mfd2, &next_key, &value);
                        fprintf(stdout, "pid%d: %llu, flow: %llu\n", ++j, next_key, value);
                        key = next_key;
                }
                printf("-----------------------\n");

                sleep(2);
        }
        
END:
        bpf_link__destroy(link1);
        bpf_link__destroy(link2);
        bpf_object__close(obj);
	return 0;
}

        把for_flow_kern.c和for_flow_user.c两个文件加入Makefile后编译.

        在这里提供一个自己编写的tcp客户端和服务端测试程序, 可能会有bug, 但是应付这个测试是没问题的. 客户端和服务端互相通信, 客户端每次写1024字节, 每次读512字节. 服务端每次读1024字节, 每次写512字节. 代码如下:

// tcp_test.c

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <pthread.h>
#include <errno.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <signal.h>
#include <sys/ioctl.h>
#include <linux/tcp.h>
#include <arpa/inet.h>
#include <sys/epoll.h>
#include <unistd.h>
#include <fcntl.h>

#define EPOLL_SIZE 1024
#define PARALLEL_MAX 16
#define READ_SIZE 1024
#define WRITE_SIZE 512

#define SET_NONBLOCK(fd) ({fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK);})

#define EPOLL_ET_CTL(node, op, event) \
({\
        struct epoll_event ev = {0x00}; \
        ev.events = (event) | EPOLLET; \
        ev.data.ptr = (node); \
        epoll_ctl((node)->ep_fd, (op), (node)->fd, &ev); \
})

#define EPOLL_ET_DEL(node) \
({\
        epoll_ctl((node)->ep_fd, EPOLL_CTL_DEL, (node)->fd, NULL); \
})

/*
* 命令行参数
*/
typedef struct config
{
        int mode;
        char *addr;
        unsigned short int port;
        int parallel;

        struct sockaddr_in addr_in;
        socklen_t  addr_in_len;
}config_t;

/*
* epoll树的节点
*/
typedef struct ep_node
{
        struct sockaddr_in addr_in;
        int ep_fd;
        int sk_fd;
        int fd;
        long long r_sum;
        long long w_sum;
}ep_node_t;

typedef struct ep_instance
{
        config_t *conf;
        int position;                   // 0代表主干线程                                    
        int ep_fd;                      // epoll树节点
        union 
        {
                int sk_fd;              // 主干epoll维护的socket
                int r_pipe;             // 分支epoll维护的读pipe, 与w_pipes对应
        };
        int w_pipes[PARALLEL_MAX];      // 主干线程维护的各子线程pipe的写端
        long long count;
}ep_instance_t;

typedef struct fd_and_addr
{
        int fd;
        struct sockaddr_in addr_in;
}fd_and_addr_t;

static int tcp_socket(const config_t *conf)
{
        int sfd = socket(AF_INET, SOCK_STREAM, 0);
        if (sfd == -1)
        {
                printf("socket failed, err msg: %s\n", strerror(errno));
                return -1;
        }

        int val1 = 1;
        if (setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR | (conf->mode == 0 ? SO_REUSEPORT : 0), (void *)&val1, sizeof(val1)) == -1)
        {
                printf("setsockopt failed, err msg: %s\n", strerror(errno));
                goto FAILED;               
        }

        if (conf->mode == 0)
        {
                if (bind(sfd, (struct sockaddr*)&conf->addr_in, conf->addr_in_len) == -1)
                {
                        printf("bind failed, err msg: %s\n", strerror(errno));
                        goto FAILED;               
                }
                if (listen(sfd, 1024))
                {
                        printf("bind failed, err msg: %s\n", strerror(errno));
                        goto FAILED;                              
                }                       
        }
        else
        {
                if (connect(sfd, (struct sockaddr*)&conf->addr_in, conf->addr_in_len))
                {
                        printf("connect failed, err msg: %s\n", strerror(errno));
                        goto FAILED;                        
                }                
        }

        int val2 = 1;
        if (setsockopt(sfd, IPPROTO_TCP, TCP_NODELAY, (void *)&val2, sizeof(val2)) == -1)
        {
                printf("setsockopt failed, err msg: %s\n", strerror(errno));
                goto FAILED;               
        }

        SET_NONBLOCK(sfd);
        return sfd;

FAILED:
        close(sfd);
        return -1;
}

ep_node_t *accept_cb(ep_instance_t *ins, ep_node_t *node)
{
        ep_node_t *new_node = (ep_node_t *)malloc(sizeof(ep_node_t));
        if (!new_node)
        {       
                return NULL;
        }

        fd_and_addr_t addr;
        if (ins->position == 0)
        {
                socklen_t remote_len = sizeof(addr.addr_in);
                addr.fd = accept(ins->sk_fd, (struct sockaddr *)&addr.addr_in, &remote_len);
                if (addr.fd == -1)
                {
                        goto FREE;
                }

                int index = ins->count++ % ins->conf->parallel;
                if (index != 0)
                {
                        write(((int *)(ins->w_pipes))[index], (void *)&addr, sizeof(addr));
                        return NULL;
                }
        }
        else
        {
                int ret = read(ins->r_pipe, &addr, sizeof(addr));
                if (ret != sizeof(addr))
                {
                        goto CLOSE;
                }
        }

        SET_NONBLOCK(addr.fd);
        new_node->addr_in = addr.addr_in;
        new_node->ep_fd = ins->ep_fd;
        new_node->sk_fd = ins->position == 0 ? ins->sk_fd : ins->r_pipe;
        new_node->fd = addr.fd;
        return new_node;

CLOSE:
        close(addr.fd);
FREE:
        free(new_node);
        new_node = NULL;
        return new_node;
}

static int server_read_cb(ep_node_t *node)
{
        unsigned char read_data[READ_SIZE];
        memset(read_data, 0x00, READ_SIZE);
        int ret = read(node->fd, read_data, READ_SIZE);
        if (ret > 0)
        {
                node->r_sum += ret;
                EPOLL_ET_CTL(node, EPOLL_CTL_MOD, EPOLLOUT);
        }
        else if (ret <= 0)
        {
                close(node->fd);
                EPOLL_ET_DEL(node);
                free(node);
                node = NULL;
        }
        return ret;
}

static int server_write_cb(ep_node_t *node)
{
        unsigned char write_data[WRITE_SIZE];
        memset(write_data, 0x30, WRITE_SIZE);
        int ret = write(node->fd, write_data, WRITE_SIZE);
        if (ret >= 0)
        {
                node->w_sum += ret;
                EPOLL_ET_CTL(node, EPOLL_CTL_MOD, EPOLLIN);
        }
        else
        {
                printf("write finished, read size: %lld, write size: %lld\n", node->r_sum, node->w_sum);
                close(node->fd);
                EPOLL_ET_DEL(node);
                free(node);
                node = NULL;
        }
        return 0;
}

void *tcp_server_process(void *arg)
{
        ep_instance_t *ins = (ep_instance_t *)arg;
        if (!ins)
        {
                return NULL;
        }

        ins->ep_fd = epoll_create(EPOLL_SIZE);
        if (ins->ep_fd == -1)
        {
                return NULL;                
        }

        int sk_fd = ins->position == 0 ? ins->sk_fd : ins->r_pipe;
        ep_node_t sk_fd_node = {
                .ep_fd = ins->ep_fd, 
                .sk_fd = sk_fd, 
                .fd = sk_fd, 
        };
        if (EPOLL_ET_CTL(&sk_fd_node, EPOLL_CTL_ADD, EPOLLIN))
        {
                return NULL;
        }

        struct epoll_event active_events[EPOLL_SIZE + 1];
        memset(&active_events, 0x00, sizeof(active_events));
        int i = 0;
        for(;;)
        {
                int active = epoll_wait(ins->ep_fd, active_events, EPOLL_SIZE + 1, -1);
                for (i = 0; i < active; i++)
                {
                        ep_node_t *node = (ep_node_t *)active_events[i].data.ptr;

                        // 新连接
                        if (node->fd == node->sk_fd)
                        {
                                ep_node_t *new_node = accept_cb(ins, node);
                                if (new_node)
                                {
                                        if (EPOLL_ET_CTL(new_node, EPOLL_CTL_ADD, EPOLLIN))
                                        {
                                                close(new_node->fd);
                                                free(new_node);
                                                new_node = NULL;
                                        }
                                        else
                                        {
                                                printf("pos: %d, fd: %d, remote ip: %s, remote port: %d\n", ins->position, new_node->fd, 
                                                                        inet_ntoa(new_node->addr_in.sin_addr), ntohs(new_node->addr_in.sin_port));
                                        }
                                }
                                EPOLL_ET_CTL(node, EPOLL_CTL_MOD, EPOLLIN);
                        }
                        // 读事件
                        else if (active_events[i].events & EPOLLIN)
                        {
                                unsigned char read_data[READ_SIZE];
                                memset(read_data, 0x00, READ_SIZE);
                                int ret = read(node->fd, read_data, READ_SIZE);
                                if (ret <= 0)
                                {
                                        printf("peer closed or read failed and then close peer, read size: %lld, write size: %lld\n", node->r_sum, node->w_sum);
                                        close(node->fd);
                                        EPOLL_ET_DEL(node);
                                        free(node);
                                        node = NULL;
                                        continue;
                                }
                                node->r_sum += ret;
                                EPOLL_ET_CTL(node, EPOLL_CTL_MOD, EPOLLOUT);
                        }
                        // 写事件
                        else if (active_events[i].events & EPOLLOUT)
                        {
                                unsigned char write_data[WRITE_SIZE];
                                memset(write_data, 0x39, WRITE_SIZE);
                                int ret = write(node->fd, write_data, WRITE_SIZE);
                                if (ret < 0)
                                {
                                        printf("write failed and then close peer, read size: %lld, write size: %lld\n", node->r_sum, node->w_sum);
                                        close(node->fd);
                                        EPOLL_ET_DEL(node);
                                        free(node);
                                        node = NULL;
                                        continue;
                                }
                                node->w_sum += ret;
                                EPOLL_ET_CTL(node, EPOLL_CTL_MOD, EPOLLIN);
                        }
                }
        }

        return NULL;
}

int tcp_server(config_t *conf)
{
        int i = 0, tmp_sk_fd = 0;
        if ((tmp_sk_fd = tcp_socket(conf)) < 0)
        {
                return -1;
        }

        int tmp_pipes_fd[PARALLEL_MAX][2];
        memset(tmp_pipes_fd, 0x00, sizeof(tmp_pipes_fd));

        ep_instance_t tmp_ins_arr[PARALLEL_MAX];
        memset(tmp_ins_arr, 0x00, sizeof(tmp_ins_arr));

        int tmp_w_pipes[PARALLEL_MAX];
        memset(tmp_w_pipes, 0x00, sizeof(tmp_w_pipes));

        pthread_t pids[PARALLEL_MAX];
        memset(pids, 0x00, sizeof(pids));

        for (i = 1; i < conf->parallel; i++)
        {
                pipe(tmp_pipes_fd[i]);
                SET_NONBLOCK(tmp_pipes_fd[i][0]);
                SET_NONBLOCK(tmp_pipes_fd[i][1]);
        
                tmp_ins_arr[i].conf = conf;
                tmp_ins_arr[i].position = i;
                tmp_ins_arr[i].r_pipe = tmp_pipes_fd[i][0];
                tmp_w_pipes[i] = tmp_pipes_fd[i][1];
                pthread_create(&pids[i], NULL, tcp_server_process, (void *)&tmp_ins_arr[i]);
        }
        
        tmp_ins_arr[0].conf = conf;
        tmp_ins_arr[0].position = 0;
        tmp_ins_arr[0].sk_fd = tmp_sk_fd;
        memcpy(tmp_ins_arr[0].w_pipes, tmp_w_pipes, sizeof(tmp_w_pipes));
        tcp_server_process((void *)&tmp_ins_arr[0]);

        for (i = 1; i < conf->parallel; i++)
        {
                pthread_join(pids[i], NULL);
        }
        return 0;
}

void* tcp_client(void *arg)
{
        config_t *conf = (config_t *)arg;

        ep_node_t fd_node;
        memset(&fd_node, 0x00, sizeof(fd_node));

        int ep_fd = epoll_create(EPOLL_SIZE);
        if (ep_fd == -1)
        {
                return NULL;                
        }

        int fd = tcp_socket(conf);
        if (fd < 0)
        {
                return NULL;
        }

        fd_node.ep_fd = ep_fd;
        fd_node.fd = fd;
        if (EPOLL_ET_CTL(&fd_node, EPOLL_CTL_ADD, EPOLLOUT))
        {
                return NULL;
        }

        struct epoll_event active_events[EPOLL_SIZE + 1];
        memset(&active_events, 0x00, sizeof(active_events));
        int i = 0;
        for(;;)
        {
                int active = epoll_wait(ep_fd, active_events, EPOLL_SIZE + 1, -1);
                for (i = 0; i < active; i++)
                {
                        ep_node_t *node = (ep_node_t *)active_events[i].data.ptr;
                        if (active_events[i].events & EPOLLIN)
                        {
                                unsigned char read_data[WRITE_SIZE];
                                memset(read_data, 0x00, WRITE_SIZE);
                                int ret = read(node->fd, read_data, WRITE_SIZE);
                                if (ret <= 0)
                                {
                                        printf("peer closed or read failed and then close peer, read size: %lld, write size: %lld\n", node->r_sum, node->w_sum);
                                        close(node->fd);
                                        EPOLL_ET_DEL(node);
                                        continue;
                                }

                                node->r_sum += ret;
                                if (node->r_sum >= 1024 * 1024 * 512 && node->w_sum >= 1024 * 1024 * 1024)
                                {
                                        printf("rw finished, read size: %lld, write size: %lld\n", node->r_sum, node->w_sum);
                                        close(node->fd);
                                        EPOLL_ET_DEL(node);
                                        return NULL;
                                }

                                EPOLL_ET_CTL(node, EPOLL_CTL_MOD, EPOLLOUT);
                        }
                        else if (active_events[i].events & EPOLLOUT)
                        {
                                unsigned char write_data[READ_SIZE];
                                memset(write_data, 0x39, READ_SIZE);
                                int ret = write(node->fd, write_data, READ_SIZE);
                                if (ret < 0)
                                {
                                        printf("write failed and then close peer, read size: %lld, write size: %lld\n", node->r_sum, node->w_sum);
                                        close(node->fd);
                                        EPOLL_ET_DEL(node);
                                        continue;
                                }

                                node->w_sum += ret;
                                if (node->r_sum >= 1024 * 1024 * 512 && node->w_sum >= 1024 * 1024 * 1024)
                                {
                                        printf("rw finished, read size: %lld, write size: %lld\n", node->r_sum, node->w_sum);
                                        close(node->fd);
                                        EPOLL_ET_DEL(node);
                                        return NULL;
                                }
                                EPOLL_ET_CTL(node, EPOLL_CTL_MOD, EPOLLIN);
                        }
                }
        }

        return 0;
}


void sig_cb(int sig)
{
        printf("capture sig: %d\n", sig);
}

int main(int argc, char *argv[])
{
        if (argc != 5)
        {
                printf("Argc failed\n");
                return -1;
        }
        signal(SIGPIPE, sig_cb);

        config_t conf = {
                .mode = atoi(argv[1]),
                .addr = argv[2],
                .port = atoi(argv[3]),
                .parallel = atoi(argv[4]) > PARALLEL_MAX ? PARALLEL_MAX : atoi(argv[4]),

                .addr_in.sin_family = AF_INET,
                .addr_in.sin_addr.s_addr = inet_addr(argv[2]),
                .addr_in.sin_port = htons(atoi(argv[3])),
                .addr_in_len = sizeof(struct sockaddr_in),
        };

        int i = 0;
        if (conf.mode == 0)
        {
                tcp_server(&conf);
        }
        else
        {
                pthread_t pids[PARALLEL_MAX];
                for (i = 1; i < conf.parallel; i++)
                {
                        pthread_create(&pids[i], NULL, tcp_client, &conf);
                }
                tcp_client(&conf);
                for (i = 1; i < conf.parallel; i++)
                {
                        pthread_join(pids[i], NULL);
                }
                printf("after pthread_join\n");
        }
        return 0;
}

        编译命令: gcc tcp_test.c -o tcp_test -lpthread

测试

        ubuntu22上启动ebpf程序: ./for_flow

        ubuntu22上启动tcp服务端: ./tcp_test 0 192.168.20.11 6230 2 // 0代表服务端, 2代表线程数

        centos7上启动tcp客户端: ./tcp_test 1 192.168.20.11 6230 2 // 1代表客户端, 2代表线程数, 且每个线程一个tcp连接

结果

        结果入下图:

         从结果可以看到, 客户端读取536870912字节(512MB), 服务端写入536870912字节(512MB), tcp测试程序代码里做的判断的也是1024*1024*512字节, 而最后ebpf统计的也是536870912字节(512MB), 结果匹配, ebpf统计tcp服务端写流量成功.

        其实这个代码改一改也可以统计tcp服务端读取的字节数, 只要探测函数改成tcp_recvmsg, 其他的逻辑判断再改一改即可. 但是改造的过程中可能要踩一下坑, 我已经改造成功, 就不放出来了. 如果有同学感兴趣可以尝试下. 如果改造的过程中遇到问题, 也可以交流交流. 

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

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

相关文章

LSA、pLSA、LDA、NMF、BERTopic、Top2Vec进行主题建模

在自然语言处理(NLP)中,主题建模是一种技术,用于从文本数据中发现隐藏的语义主题(或主题)。这是一个无监督机器学习问题,即在没有标签或标签的情况下学习模式。主题建模的应用非常广泛,可用于搜索引擎、情感分析、新闻聚类和摘要生成等许多任务。 在这里将探讨主题建模…

一屏统管 智慧交管Web3D可视化大屏云控系统

交通是城市发展的基础&#xff0c;体现着社会文明程度&#xff0c;彰显着城市治理水平。今天给大家分享一个基于 数维图 的 Sovit3D编辑器 构建轻量化 3D 可视化场景的案例——智慧交管三维可视化系统。多维度呈现城市交通情况&#xff0c;赋能“安全管控、缓堵保畅、出行服务”…

硬件系统工程师宝典(2)-----硬件电路的概要设计启动

今天我们继续来读这本书&#xff0c;硬件系统工程师宝典。作者提到&#xff0c;产品需求分析之后就进入概要设计阶段。在这个阶段&#xff0c;ID&#xff08;Industrial Design&#xff09;工业设计及结构工程师、软件系统开发工程师和硬件系统开发工程师等开始分头工作。 工业…

头条百科词条怎么编辑?送你一份超详细指南

头条百科其实就是之前的互动百科&#xff0c;后面被今日头条收购之后&#xff0c;改为头条百科&#xff0c;也叫快懂百科。 百度百科在百度上的权重很高&#xff0c;而头条百科在今日头条和抖音上的权重很高。 现在我们遇到什么问题或是不知道什么人物、品牌的时候&#xff0…

xxljob 的 阻塞处理策略的逻辑是什么(小白)

目录 1 需求2 单机串行3 丢弃后续调整4 覆盖之前的调整1 需求 每一个任务都有一个阻塞处理策略,我们在创建任务的时候可以自己设置,那么不同的设置,后端的逻辑是什么呢》 xxljob 调度中心项目 调度我们自己的项目,到了我们项目里面,最先到的文件是 就在首次到的这个文件的…

Springboot-数据库操作(Mybatis)-初级入门

一、Mybatis-plus介绍 官方文档&#xff1a;简介 | MyBatis-Plus (baomidou.com) 他只增强了单表查询&#xff0c;没增强多表查询等复杂的查询。 二、配置 引入依赖 <!-- MyBatisPlus依赖--><dependency><groupId>com.baomidou</groupId><a…

UBUNTU 22.04 使用 SUNSHINE 和 MOONLIGHT 进行串流

参考 【ubuntu22.04】sunshine安装使用总结&#xff0c;远程游戏。_哔哩哔哩_bilibili sunshine/README.md at master loki-47-6F-64/sunshine GitHub GitHub - LizardByte/Sunshine: Sunshine is a Gamestream host for Moonlight. Installation - Sunshine documentati…

基于Java+SpringBoot+vue实现图书借阅和销售商城一体化系统

基于JavaSpringBootvue实现图书借阅和销售商城一体化系统 &#x1f680; 博主介绍&#xff1a;5年java开发经验&#xff0c;专注Java开发、定制、远程、文档编写指导等,csdn特邀作者、专注于Java技术领域 &#x1f345; 作者主页 超级帅帅吴 &#x1f345; 欢迎点赞 &#x1f…

2023年江苏专转本志愿填报辅导(22上岸南工程学长辅导手册)

文章目录公告链接一、23年专转本与22年的变化二、专转本志愿填报2.1、填报流程2.2、志愿填报院校顺序选择参考三、专转本考试分值及时间节点四、专转本录取投档原则及办法&#xff08;平行、征求平行志愿、服从志愿、降分录取&#xff09;五、考前冲刺辅导不同大类填报计算机大…

面试官问我TCP三次握手和四次挥手,我真的是

候选者&#xff1a;面试官你好&#xff0c;请问面试可以开始了吗 面试官&#xff1a;嗯&#xff0c;开始吧 面试官&#xff1a;今天来聊聊TCP吧&#xff0c;TCP的各个状态还有印象吗&#xff1f; 候选者&#xff1a;还有些许印象的&#xff0c;要不我就来简单说下TCP的三次握…

【Kotlin】函数 ⑤ ( 匿名函数变量类型推断 | 匿名函数参数类型自动推断 | 匿名函数又称为 Lambda 表达式 )

文章目录一、匿名函数变量类型推断二、匿名函数参数类型自动推断三、Lambda 表达式一、匿名函数变量类型推断 定义变量 时 , 如果将变量值 直接赋值给该变量 , 那么就可以 不用显示声明该变量的类型 ; 下面的代码中 , 定义 name 变量 , 为其 赋值 “Tom” 字符串 String 类型变…

2023年山东食品安全管理员模拟试题及答案

百分百题库提供食品安全管理员考试试题、食品安全管理员考试预测题、食品安全管理员考试真题、食品安全管理员证考试题库等&#xff0c;提供在线做题刷题&#xff0c;在线模拟考试&#xff0c;助你考试轻松过关。 一、单选题 1.餐饮具消毒的目的是: A.去除表面的污垢 B.杀灭…

【个人博客】Hexo个人博客搭建与配置详细教程 + Fluid主题 + Gitee发布

文章目录一、环境准备1.1 安装 NodeJs1.2 安装 Git1.3 安装 Hexo二、Gitee仓库搭建2.1 Gitee账号注册2.2 仓库搭建三、Hexo博客搭建3.1 基础环境搭建3.2 启动 Hexo3.3 更换 Fluid 主题四、自定义配置4.1 全局设置4.1.1 页面顶部大图4.1.2 博客标题4.1.3 导航菜单4.1.4 懒加载4.…

路由器连接实验

使用静态路由实现全网连通 R1 [r1]interface GigabitEthernet 0/0/0 [r1-GigabitEthernet0/0/0]ip address 12.1.1.1 24 [r1]interface GigabitEthernet 0/0/1 [r1-GigabitEthernet0/0/1]ip ad [r1-GigabitEthernet0/0/1]ip address 14.1.1.1 24 [r1]interface LoopBack 0 [r1…

搞懂MyBatis?这篇文章就够了~

哈喽呀~你好呀~欢迎呀~一起来看看这篇宝藏文章吧~ 目录 1.什么是MyBatis 2.配置MyBatis开发环境 3.使用MyBatis框架去操作数据库 3.1 实现MyBatis查询功能. 3.1.1 创建一个接口(该接口一定要加上Mapper注解): 3.1.2 创建上面接口对应的 xml 文件 (此文件会实现接口中的…

易基因|METTL3 通过调节m6A 修饰抑制口腔鳞状细胞癌安罗替尼敏感性| 肿瘤研究

大家好&#xff0c;这里是专注表观组学十余年&#xff0c;领跑多组学科研服务的易基因。2022年9月27日&#xff0c;中山大学附属第一医院口腔颌面外科王安训和何倩婷课题组在《Cancer Cell International》杂志发表了《METTL3 suppresses anlotinib sensitivity by regulating …

Tomcat弱口令+后端getshell漏洞

今天继续给大家介绍渗透测试相关知识&#xff0c;本文主要内容是Tomcat弱口令后端getshell漏洞。 免责声明&#xff1a; 本文所介绍的内容仅做学习交流使用&#xff0c;严禁利用文中技术进行非法行为&#xff0c;否则造成一切严重后果自负&#xff01; 再次强调&#xff1a;严禁…

指针进阶之数组指针和指针数组

文章目录一、指针数组1.概念2.用法&#xff08;1&#xff09;案例一&#xff08;2&#xff09;案例二二、数组指针1.概念&#xff08;1&#xff09;引子&#xff08;2&#xff09;写法&#xff08;3&#xff09;辨析&#xff08;4&#xff09;总结&#xff08;5&#xff09;案例…

ThreeJS—OrbitControls使其控制模型而不是场景

转载核心代码 项目场景&#xff1a; 来公司之前公司有一个地球组件&#xff0c;大概是张这个样子的⬇️&#xff0c;会转有飞线&#xff0c;有城市涟漪&#xff0c;很炫酷。可惜不是我做的。 一个大屏项目上需要额外增加一些需求 转动到某一城市&#xff0c;暂停转动&#…

K8S Pod 基本使用

K8S Pod 基本使用 Pod基本概念 Pods是在Kubernetes集群中创建和管理最小的部署单元&#xff0c;一个Pod内部可以运行一个或多个容器&#xff0c;多个容器之间具共享的存储和网络资源&#xff0c;共享运行上下文。Pod共享运行时上下文是通过linux 命名空间实现&#xff0c;不同…