Linux高并发服务器开发(九)Tcp状态转移和IO多路复用

文章目录

  • 0 包裹函数
  • 1 多进程服务器
    • 流程
    • 代码
  • 2 多线程服务器
  • 3 TCP状态转移
    • 半关闭
    • 心跳包
  • 4 端口复用
  • 5 IO多路复用技术
    • 高并发服务器
  • 6 select
    • 代码
    • 总结
  • 7 POLL
    • API
    • 代码
    • poll相对select的优缺点
  • 8 epoll(重点)
    • API
    • 监听管道
    • 代码
    • EPOLL 高并发服务器
  • 9 Epoll的两种工作方式
    • 边缘触发代码


0 包裹函数

用于创建socket,绑定端口ip和监听时,添加了错误时报错的包裹函数

warp.h

#ifndef __WRAP_H_
#define __WRAP_H_
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <strings.h>void perr_exit(const char *s);
int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr);
int Bind(int fd, const struct sockaddr *sa, socklen_t salen);
int Connect(int fd, const struct sockaddr *sa, socklen_t salen);
int Listen(int fd, int backlog);
int Socket(int family, int type, int protocol);
ssize_t Read(int fd, void *ptr, size_t nbytes);
ssize_t Write(int fd, const void *ptr, size_t nbytes);
int Close(int fd);
ssize_t Readn(int fd, void *vptr, size_t n);
ssize_t Writen(int fd, const void *vptr, size_t n);
ssize_t my_read(int fd, char *ptr);
ssize_t Readline(int fd, void *vptr, size_t maxlen);
int tcp4bind(short port,const char *IP);
#endif

wrap.c

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <strings.h>void perr_exit(const char *s)
{perror(s);exit(-1);
}int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr)
{int n;again:if ((n = accept(fd, sa, salenptr)) < 0) {if ((errno == ECONNABORTED) || (errno == EINTR))goto again;elseperr_exit("accept error");}return n;
}int Bind(int fd, const struct sockaddr *sa, socklen_t salen)
{int n;if ((n = bind(fd, sa, salen)) < 0)perr_exit("bind error");return n;
}int Connect(int fd, const struct sockaddr *sa, socklen_t salen)
{int n;if ((n = connect(fd, sa, salen)) < 0)perr_exit("connect error");return n;
}int Listen(int fd, int backlog)
{int n;if ((n = listen(fd, backlog)) < 0)perr_exit("listen error");return n;
}int Socket(int family, int type, int protocol)
{int n;if ((n = socket(family, type, protocol)) < 0)perr_exit("socket error");return n;
}ssize_t Read(int fd, void *ptr, size_t nbytes)
{ssize_t n;again:if ( (n = read(fd, ptr, nbytes)) == -1) {if (errno == EINTR)goto again;elsereturn -1;}return n;
}ssize_t Write(int fd, const void *ptr, size_t nbytes)
{ssize_t n;again:if ( (n = write(fd, ptr, nbytes)) == -1) {if (errno == EINTR)goto again;elsereturn -1;}return n;
}int Close(int fd)
{int n;if ((n = close(fd)) == -1)perr_exit("close error");return n;
}/*参三: 应该读取的字节数*/
ssize_t Readn(int fd, void *vptr, size_t n)
{size_t  nleft;              //usigned int 剩余未读取的字节数ssize_t nread;              //int 实际读到的字节数char   *ptr;ptr = vptr;nleft = n;while (nleft > 0) {if ((nread = read(fd, ptr, nleft)) < 0) {if (errno == EINTR)nread = 0;elsereturn -1;} else if (nread == 0)break;nleft -= nread;ptr += nread;}return n - nleft;
}ssize_t Writen(int fd, const void *vptr, size_t n)
{size_t nleft;ssize_t nwritten;const char *ptr;ptr = vptr;nleft = n;while (nleft > 0) {if ( (nwritten = write(fd, ptr, nleft)) <= 0) {if (nwritten < 0 && errno == EINTR)nwritten = 0;elsereturn -1;}nleft -= nwritten;ptr += nwritten;}return n;
}static ssize_t my_read(int fd, char *ptr)
{static int read_cnt;static char *read_ptr;static char read_buf[100];if (read_cnt <= 0) {
again:if ( (read_cnt = read(fd, read_buf, sizeof(read_buf))) < 0) {if (errno == EINTR)goto again;return -1;} else if (read_cnt == 0)return 0;read_ptr = read_buf;}read_cnt--;*ptr = *read_ptr++;return 1;
}ssize_t Readline(int fd, void *vptr, size_t maxlen)
{ssize_t n, rc;char    c, *ptr;ptr = vptr;for (n = 1; n < maxlen; n++) {if ( (rc = my_read(fd, &c)) == 1) {*ptr++ = c;if (c  == '\n')break;} else if (rc == 0) {*ptr = 0;return n - 1;} elsereturn -1;}*ptr  = 0;return n;
}int tcp4bind(short port,const char *IP)
{struct sockaddr_in serv_addr;int lfd = Socket(AF_INET,SOCK_STREAM,0);bzero(&serv_addr,sizeof(serv_addr));if(IP == NULL){//如果这样使用 0.0.0.0,任意ip将可以连接serv_addr.sin_addr.s_addr = INADDR_ANY;}else{if(inet_pton(AF_INET,IP,&serv_addr.sin_addr.s_addr) <= 0){perror(IP);//转换失败exit(1);}}serv_addr.sin_family = AF_INET;serv_addr.sin_port   = htons(port);Bind(lfd,(struct sockaddr *)&serv_addr,sizeof(serv_addr));return lfd;
}

1 多进程服务器

流程

因为read 和 write都是阻塞的,所以如果多个客户端进行连接时,会阻塞。
在这里插入图片描述
理念就是连接给一个专用的进程,然后这个进程来分配其他进程进行读写的操作

流程
创建套接字
绑定
监听

while(1)
{
提取连接
fork创建子进程
子进程中 关闭lfd 服务客户端(连接)
父进程关闭 cfd(读写),回收子进程资源

}

代码

#include<sys/socket.h>
#include<stdio.h>
#include<arpa/inet.h>
#include <unistd.h>
#include "wrap.h"
#include<stdlib.h>
#include <signal.h>
#include<sys/wait.h>
#include<sys/types.h>void *free_process(int signum)
{pid_t pid;while(1){pid = waitpid(-1,NULL,WNOHANG);if(pid <= 0)    // 没有要回收的子进程{break;}else{printf("child pid =%d\n",pid);}}}int main()
{// 阻塞信号集,在子进程创建之前// 在创建子进程之后添加信号sigset_t set;sigemptyset(&set);sigaddset(&set, SIGCHLD);sigprocmask(SIG_BLOCK, &set, NULL);// 创建套接字,绑定 链接socketint lfd = tcp4bind(8000,NULL);// 监听Listen(lfd, 128);// 提取// 回射struct sockaddr_in cliaddr;socklen_t len = sizeof(cliaddr);while(1){// 读取socketint cfd = Accept(lfd, (struct sockaddr*)&cliaddr,&len);char ip[16] = "";printf("new client ip= %s port = %d\n",inet_ntop(AF_INET,&cliaddr.sin_addr.s_addr,ip,16),ntohs(cliaddr.sin_port));// fork 创建子进程pid_t pid;pid = fork();if(pid<0){perror("");exit(0);}else if(pid == 0) // 子进程{// 关闭lfdclose(lfd);while(1){char buf[1024];int n = read(cfd,buf,sizeof(buf));if(n < 0){perror("");close(cfd);exit(0);}else if(n == 0) // 对方关闭{printf("client close\n");close(cfd);exit(0);}else{printf("%s", buf);write(cfd,buf,n);// exit(0);}}}else{close(cfd); // 回收// 注册信号回调struct sigaction act;act.sa_flags = 0;act.sa_handler = free_process;sigemptyset(&act.sa_mask);sigaction(SIGCHLD,&act,NULL);sigprocmask(SIG_UNBLOCK, &set, NULL);}}// 回收return 0;
}

2 多线程服务器

#include<sys/socket.h>
#include<stdio.h>
#include<arpa/inet.h>
#include <unistd.h>
#include "wrap.h"
#include<stdlib.h>
#include <signal.h>
#include<sys/wait.h>
#include<sys/types.h>
#include<pthread.h>typedef struct c_info
{int cfd;struct sockaddr_in cliaddr;}CINFO;int main(int argc, char *argv[])
{if(argc < 2){printf("argc < 2???\n ./a.out 8000\n");}// 初始化线程属性pthread_attr_t attr;pthread_attr_init(&attr);pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);short port = atoi(argv[1]);int lfd = tcp4bind(port, NULL);Listen(lfd, 128);struct sockaddr_in cliaddr;socklen_t len = sizeof(cliaddr);CINFO *info;while(1){int cfd = Accept(lfd,(struct sockaddr*)&cliaddr,&len);char ip[16] = "";printf("new client ip = %s port = %d\n", inet_ntop(AF_INET,&cliaddr.sin_addr.s_addr,ip,16),ntohs(cliaddr.sin_port));pthread_t pthid;info = malloc(sizeof(CINFO));info->cfd = cfd;info->cliaddr = cliaddr;pthread_create(&pthid,NULL,client_fun,&info);}return 0;
}void * client_fun(void *arg)
{CINFO *info = (CINFO *)arg;char ip[16] = "";printf("new client ip = %s port = %d\n", inet_ntop(AF_INET,&(info->cliaddr.sin_addr.s_addr),ip,16),ntohs(info->cliaddr.sin_port));while(1){char buf[1024] = "";int count = 0;count = read(info->cfd, buf, sizeof(buf));if(count < 0){perror("");break;}else if(count == 0){printf("client close");break;}else{   printf("%s", buf);write(info->cfd, buf, count);}}close(info->cfd);free(info);}

3 TCP状态转移

在这里插入图片描述
TIME_WAIT -> CLOSE 2MML

半关闭

处于FIN_WAIT2时,处于半关闭状态,此时只能读数据不能收数据

手动半关闭
在这里插入图片描述

心跳包

每隔一段时间服务器向客户端发送一个包,客户端需要在一定时间内返回一个规定好的包,用于测试连接是否还存在,如果对方没有回复,则断开连接

在这里插入图片描述

4 端口复用

端口重新启用
使用 setsockopt设置端口重新使用
放在绑定之前
在这里插入图片描述

5 IO多路复用技术

高并发服务器

1.阻塞等待
一个进程 服务一个客户端
消耗资源

2.非阻塞忙轮询
重复查看 进程是否有需求,是否有新连接

3.多路io
通过监听多个文件描述符,监听文件描述符是否还在读写
内核有三种方式
select:windows使用 select select跨平台
poll: 少用
epoll: linux下使用

内核监听多个文件描述符的属性(读写缓冲区)变化,如果某个文件描述符的读缓冲区变化了,这个时候就是可以读了,将这个事件告知应用层

在这里插入图片描述在这里插入图片描述

6 select

使用select监听文件描述符
在这里插入图片描述
注意:变化的文件描述符,会存放在监听的集合中,未变化的文件描述符会被删除

代码

#include<sys/socket.h>
#include<stdio.h>
#include<arpa/inet.h>
#include <unistd.h>
#include<sys/time.h>
#include<sys/types.h>
#include<sys/select.h>
#include "wrap.h"#define PORT 8888
int main(int argc, char *argv[])
{// 创建套接字,绑定int lfd = tcp4bind(PORT,NULL);// 监听Listen(lfd, 128);int maxfd = lfd;    // 最大的文件描述符fd_set oldset,rset;// 清空集合FD_ZERO(&oldset);FD_ZERO(&rset);// 将lfd加入到oldset集合中FD_SET(lfd, &oldset);// whilewhile(1){rset = oldset;  // 将oldset赋值给需要监听的集合rsetint n = select(maxfd + 1,&rset,NULL,NULL,NULL);if(n<0){perror("");break;}else if(n==0){continue;}else    // n>0  监听到了文件描述符{// lfd变化, 则进行提取if(FD_ISSET(lfd,&rset)){struct sockaddr_in cliaddr;socklen_t len = sizeof(cliaddr);char ip[16] = "";// 提取新的连接int cfd = Accept(lfd, (struct sockaddr*)&cliaddr, & len);printf("new client ip = %s, port = %d\n",inet_ntop(AF_INET, &cliaddr.sin_addr.s_addr,ip,16),ntohs(cliaddr.sin_port));// 将cfd添加到oldset集合中,下次进行监听FD_SET(cfd,&oldset);// 更新maxfdif(cfd > maxfd)maxfd = cfd;// 如果只有lfd变化,continueif(--n == 0)continue;}// cfd  遍历cfd,看lfd之后的文件描述符是否在rset,如果在则cfd变化for(int i = lfd+1;i<=maxfd;i++){// 如果i文件描述符在rset集合中if(FD_ISSET(i,&rset)){char buf[1024] = "";int ret = Read(i,buf,sizeof(buf));if(ret < 0) //出错,将cfd关闭,从oldset删除cfd{perror("");close(i);FD_CLR(i,&oldset);}else if(ret == 0){printf("client close\n");close(i);FD_CLR(i,&oldset);}else{printf("%s\n", buf);Write(i,buf,ret);}}}}}// select监听return 0;
}

总结

优缺点
优点:跨平台
缺点:文件描述符1024的限制 由于FD_SETSIZE的限制
只是返回变化的文件描述符的个数,具体哪个变化需要遍历
每次都需要将需要监听的文件描述集合由应用层拷贝到内核

7 POLL

在这里插入图片描述

API

在这里插入图片描述

代码

//IO多路复用技术poll函数的使用 
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <errno.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <poll.h>
#include "wrap.h"int main()
{int i;int n;int lfd;int cfd;int ret;int nready;int maxfd;char buf[1024];socklen_t len;int sockfd;fd_set tmpfds, rdfds;struct sockaddr_in svraddr, cliaddr;//创建socketlfd = Socket(AF_INET, SOCK_STREAM, 0);//允许端口复用int opt = 1;setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(int));//绑定bindsvraddr.sin_family = AF_INET;svraddr.sin_addr.s_addr = htonl(INADDR_ANY);svraddr.sin_port = htons(8888);ret = Bind(lfd, (struct sockaddr *)&svraddr, sizeof(struct sockaddr_in));//监听listenret = Listen(lfd, 128);struct pollfd client[1024];for(i=0; i<1024; i++){client[i].fd = -1;}		//将监听文件描述符委托给内核监控----监控读事件client[0].fd = lfd;client[0].events = POLLIN;maxfd = 0; //maxfd表示内核监控的范围while(1){nready = poll(client, maxfd+1, -1);if(nready<0){perror("poll error");exit(1);}//有客户端连接请求if(client[0].fd==lfd && (client[0].revents & POLLIN)){cfd = Accept(lfd, NULL, NULL);//寻找client数组中的可用位置for(i=1; i<1024; i++){if(client[i].fd==-1){client[i].fd = cfd;client[i].events = POLLIN;break;}}//若没有可用位置, 则关闭连接if(i==1024){Close(cfd);continue;}if(maxfd<i){maxfd = i;}if(--nready==0){continue;}}//下面是有数据到来的情况for(i=1; i<=maxfd; i++){//若fd为-1, 表示连接已经关闭或者没有连接if(client[i].fd==-1)	{continue;}sockfd = client[i].fd;memset(buf, 0x00, sizeof(buf));n = Read(sockfd, buf, sizeof(buf));if(n<=0){printf("read error or client closed,n==[%d]\n", n);Close(sockfd);client[i].fd = -1; //fd为-1,表示不再让内核监控}else{printf("read over,n==[%d],buf==[%s]\n", n, buf);write(sockfd, buf, n);}if(--nready==0){break;}}}Close(lfd);return 0;
}

poll相对select的优缺点

优点:相对于select没有最大1024文件描述符限制
请求和返回是分离

缺点:
每次都需要将需要监听的文件描述符从应用层拷贝到内核
每次都需要将数组中的元素遍历一遍才知道哪个变化
大量并发、少量活跃率低

8 epoll(重点)

1.创建红黑树
2.将监听的文件描述符上树
3.监听

特点:
没有文件描述符1024的限制
以后每次监听都不需要在此将需要监听的文件描述符拷贝在内核
返回的是已经变化的文件描述符,不需要遍历树

工作原理:
在这里插入图片描述

在这里插入图片描述

API

1.创建红黑树
在这里插入图片描述

2.上树 下树 修改节点

在这里插入图片描述
在这里插入图片描述
3. 监听

在这里插入图片描述

监听管道

在这里插入图片描述

代码

#include<sys/socket.h>
#include<stdio.h>
#include<arpa/inet.h>
#include <unistd.h>
#include<sys/time.h>
#include<sys/types.h>
#include<sys/select.h>
#include "wrap.h"
#include<sys/epoll.h>int main()
{int fd[2];pipe(fd);pid_t pid;pid = fork();if(pid < 0)perror("");else if(pid == 0){close(fd[0]);char buf[5];char ch = 'a';while(1){sleep(3);memset(buf,ch,sizeof(buf));write(fd[1],buf,5);}}else{close(fd[1]);// 创建树int epfd = epoll_create(1);struct epoll_event ev, evs[1];ev.data.fd = fd[0];ev.events = EPOLLIN;epoll_ctl(epfd,EPOLL_CTL_ADD,fd[0],&ev);while(1){int n = epoll_wait(epfd, &evs[1],-1,-1);if(n == 1){   char buf[128] = "";int ret = read(fd[0],buf,sizeof(buf));if(ret <= 0){close(fd[0]);epoll_ctl(epfd,EPOLL_CTL_DEL,fd[0],&ev);break;}else{printf("%s\n",buf);}}}}return 0;}

EPOLL 高并发服务器

#include<sys/socket.h>
#include<stdio.h>
#include<arpa/inet.h>
#include <unistd.h>
#include<sys/time.h>
#include<sys/types.h>
#include<sys/select.h>
#include "wrap.h"
#include<sys/epoll.h>#define PORT 8000
int main()
{// 创建套接字int lfd = tcp4bind(PORT,NULL);// 监听Listen(lfd,128);// 创建树int epfd = epoll_create(1);// 将lfd上树struct epoll_event ev,evs[1024];ev.data.fd = lfd;ev.events = EPOLLIN;epoll_ctl(epfd,EPOLL_CTL_ADD,lfd,&ev);// while循环监听while(1){int nready = epoll_wait(epfd,evs,-1,-1);if (nready < 0){perror("");break;}else if(nready == 0){continue;}else   // nread > 0 文件描述符有变化{for(int i =0;i<nready;i++){// 判断lfd变换,并且是读事件变换if(evs->data.fd == lfd && evs[i].events & EPOLLIN){struct sockaddr_in cliaddr;char ip[16] = "";socklen_t len = sizeof(cliaddr);int cfd = Accept(lfd,(struct sockaddr *)&cliaddr, &len);printf("new client ip = %s port =%d\n",inet_ntop(AF_INET,&cliaddr.sin_addr.s_addr,ip,16),ntohs(cliaddr.sin_port));// 将cfd上树ev.data.fd = cfd;ev.events = EPOLLIN;epoll_ctl(epfd,EPOLL_CTL_ADD,cfd,&ev);}else if(evs[i].events & EPOLLIN) // cfd 变换,而且是读事件变换{char buf[1024] = "";int n = read(evs[i].data.fd,buf,sizeof(buf));if(n < 0)   // 出错{perror("");epoll_ctl(epfd,EPOLL_CTL_DEL,evs[i].data.fd,&evs[i]);}else if(n == 0) // 客户端关闭,下树{printf("client close]\n");close(evs[i].data.fd); // 关闭cfdepoll_ctl(epfd,EPOLL_CTL_DEL,evs[i].data.fd,&evs[i]); }else    // 服务端进行处理{printf("%s\n",buf);write(evs[i].data.fd,buf,n);}}}}}return 0;
}

9 Epoll的两种工作方式

在这里插入图片描述

  1. 监听读缓存区的变化
    水平触发
    只要缓存区有数据,就会触发epoll_wait
    边缘触发
    数据来一次,epoll_wait只触发一次

2.监听写缓存区的变化
水平触发:只要可以写,就会触发
边沿触发:数据从有到无就会触发

边缘触发
在这里插入图片描述
在这里插入图片描述
触发一次的时候只读4位,但发送了10位,所以虽然只读一次,但是读不完

设置为一次读完
设置cfd为非阻塞

在这里插入图片描述
在这里插入图片描述

因为设置水平触发,只要缓存区有数据epoll_wait就会被触发,epoll_wait 是一个系统调用,尽量少调用边缘触发,边缘触发数据来一次只触发一次,这个时候要求一次性将数据读完,所以while循环读,堵到最后read默认带阻塞,不能让read阻塞,因为不能再去监听,设置cfd为非阻塞,read堵到最后一次返回值为-1,判断errno的值为eagain,则代表数据读干净、

工作中 边缘触发 + 非阻塞 = 高速模式

边缘触发代码

#include<sys/socket.h>
#include<stdio.h>
#include<arpa/inet.h>
#include <unistd.h>
#include<sys/time.h>
#include<sys/types.h>
#include<sys/select.h>
#include "wrap.h"
#include<sys/epoll.h>
#include <fcntl.h>#define PORT 8000
int main()
{// 创建套接字int lfd = tcp4bind(PORT,NULL);// 监听Listen(lfd,128);// 创建树int epfd = epoll_create(1);// 将lfd上树struct epoll_event ev,evs[1024];ev.data.fd = lfd;ev.events = EPOLLIN;epoll_ctl(epfd,EPOLL_CTL_ADD,lfd,&ev);// while循环监听while(1){int nready = epoll_wait(epfd,evs,-1,-1);printf("epoll wait");if (nready < 0){perror("");break;}else if(nready == 0){continue;}else   // nread > 0 文件描述符有变化{for(int i =0;i<nready;i++){// 判断lfd变换,并且是读事件变换if(evs->data.fd == lfd && evs[i].events & EPOLLIN){struct sockaddr_in cliaddr;char ip[16] = "";socklen_t len = sizeof(cliaddr);int cfd = Accept(lfd,(struct sockaddr *)&cliaddr, &len);printf("new client ip = %s port =%d\n",inet_ntop(AF_INET,&cliaddr.sin_addr.s_addr,ip,16),ntohs(cliaddr.sin_port));// 获得cfd的标志位int flag = fcntl(cfd, F_GETFL); // 设置为非阻塞flag |= O_NONBLOCK;fcntl(cfd,F_SETFL,flag);// 将cfd上树ev.data.fd = cfd;ev.events = EPOLLIN | EPOLLET;epoll_ctl(epfd,EPOLL_CTL_ADD,cfd,&ev);}else if(evs[i].events & EPOLLIN) // cfd 变换,而且是读事件变换{while(1){char buf[4] = "";// 如果读一个缓冲区,缓冲区域没有数据,如果是阻塞,就阻塞等待// 是非阻塞,返回值等于 -1,并且会将errorno值设置为EAGAIN   int n = read(evs[i].data.fd,buf,sizeof(buf));if(n < 0)   // 出错{   // 如果缓冲区读干净了,这个时候应该跳出while循环,继续监听if(errno == EAGAIN){break;}// 普通错误perror("");close(evs[i].data.fd); // 关闭cfdepoll_ctl(epfd,EPOLL_CTL_DEL,evs[i].data.fd,&evs[i]);break;}else if(n == 0) // 客户端关闭,下树{printf("client close]\n");close(evs[i].data.fd); // 关闭cfdepoll_ctl(epfd,EPOLL_CTL_DEL,evs[i].data.fd,&evs[i]); break;}else    // 服务端进行处理{// printf("%s\n",buf);write(STDOUT_FILENO,buf,4);write(evs[i].data.fd,buf,n);}}}}}}return 0;
}

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

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

相关文章

Iot解决方案开发的体系结构模式和技术

前言 Foreword 计算机技术起源于20世纪40年代&#xff0c;最初专注于数学问题的基本原理&#xff1b;到了60年代和70年代&#xff0c;它以符号系统为中心&#xff0c;该领域首先开始面临复杂性问题&#xff1b;到80年代&#xff0c;随着个人计算的兴起和人机交互的问题&#x…

【进阶篇】Java 项目中对使用递归的理解分享

前言 笔者在最近的项目开发中&#xff0c;遇到了两个父子关系紧密相关的场景&#xff1a;评论树结构、部门树结构。具体的需求如&#xff1a;找出某条评论下的所有子评论id集合&#xff0c;找出某个部门下所有的子部门id集合。 在之前的项目开发经验中&#xff0c;递归使用得是…

【LeetCode】十、二分查找法:寻找峰值 + 二维矩阵的搜索

文章目录 1、二分查找法 Binary Search2、leetcode704&#xff1a;二分查找3、leetcode35&#xff1a;搜索插入位置4、leetcode162&#xff1a;寻找峰值5、leetcode74&#xff1a;搜索二维矩阵 1、二分查找法 Binary Search 找一个数&#xff0c;有序的情况下&#xff0c;直接…

【动态规划 前缀和】2478. 完美分割的方案数

本文涉及知识点 划分型dp 动态规划汇总 C算法&#xff1a;前缀和、前缀乘积、前缀异或的原理、源码及测试用例 包括课程视频 LeetCode 2478. 完美分割的方案数 给你一个字符串 s &#xff0c;每个字符是数字 ‘1’ 到 ‘9’ &#xff0c;再给你两个整数 k 和 minLength 。 如…

Wireshark - tshark支持iptables提供数据包

tshark现在的数据包获取方式有两种&#xff0c;分别是读文件、网口监听&#xff08;af-packet原始套接字&#xff09;。两种方式在包获取上&#xff0c;都是通过读文件的形式&#xff1b;存在文件io操作&#xff0c;在专门处理大流量的情境下&#xff0c; 我们复用wireshark去做…

Windows编程上

Windows编程[上] 一、Windows API1.控制台大小设置1.1 GetStdHandle1.2 SetConsoleWindowInfo1.3 SetConsoleScreenBufferSize1.4 SetConsoleTitle1.5 封装为Innks 2.控制台字体设置以及光标调整2.1 GetConsoleCursorInfo2.2 SetConsoleCursorPosition2.3 GetCurrentConsoleFon…

python如何输出list

直接输出list_a中的元素三种方法&#xff1a; list_a [1,2,3,313,1] 第一种 for i in range(len(list_a)):print(list_a[i]) 1 2 3 313 1 第二种 for i in list_a:print(i) 1 2 3 313 1 第三种&#xff0c;使用enumerate输出list_a方法&#xff1a; for i&#xff0c;j in enum…

Redis的使用(二)redis的命令总结

1.概述 这一小节&#xff0c;我们主要来研究一下redis的五大类型的基本使用&#xff0c;数据类型如下&#xff1a; redis我们接下来看一看这八种类型的基本使用。我们可以在redis的官网查询这些命令:Commands | Docs,同时我们也可以用help 数据类型查看命令的帮助文档。 2. 常…

opencascade AIS_InteractiveContext源码学习7 debug visualization

AIS_InteractiveContext 前言 交互上下文&#xff08;Interactive Context&#xff09;允许您在一个或多个视图器中管理交互对象的图形行为和选择。类方法使这一操作非常透明。需要记住的是&#xff0c;对于已经被交互上下文识别的交互对象&#xff0c;必须使用上下文方法进行…

【问题已解决】Vue管理后台,点击登录按钮,会发起两次网络请求(竟然是vscode Compile Hero编译插件导致的)

问题 VueElement UI 做的管理后台&#xff0c;点击登录按钮&#xff0c;发现 接口会连续掉两次&#xff0c;发起两次网络请求&#xff0c;但其他接口都是正常调用的&#xff0c;没有这个问题&#xff0c;并且登录按钮也加了loading&#xff0c;防止重复点击&#xff0c;于是开…

JavaMySQL 学习(基础)

目录 Java CMD Java发展 计算机存储规则 Java学习 switch新用法&#xff08;可以当做if来使用&#xff09; 数组定义 随机数 Java内存分配 MySQL MySQL概述 启动和停止 客户端连接 数据模型 关系型数据库 SQL SQL通用语法 SQL分类 DDL--数据定义语言 数据库…

浏览器开发者工具辅助爬虫开发

文章目录 浏览器开发者工具辅助爬虫开发打开开发者工具使用Network面板分析请求数据示例步骤&#xff1a; 使用Elements面板查看和修改DOM结构示例步骤&#xff1a; 使用Console面板调试JavaScript代码示例步骤&#xff1a;示例代码&#xff1a;1. 输出日志信息2. 输出对象信息…

左值右值, 左值引用右值引用,完美转发

一. 左值和右值 左值: 可以取地址的对象 右值: 不可以取地址的对象 double x1.0, y 2.0; 1; // 字面量, 不可取地址, 是右值 x y; // 表达式返回值, 不可取地址, 是右值 max(x, y); // 传值返回函数的返回值 (非引用返回)总结就是: 根据是否可以取地址来区分是左值还…

线程池666666

1. 作用 线程池内部维护了多个工作线程&#xff0c;每个工作线程都会去任务队列中拿取任务并执行&#xff0c;当执行完一个任务后不是马上销毁&#xff0c;而是继续保留执行其它任务。显然&#xff0c;线程池提高了多线程的复用率&#xff0c;减少了创建和销毁线程的时间。 2…

Ubuntu开通5005端口 记录

Ubuntu版本&#xff1a;20.04 使用systemctl status firewalld查看防火墙状态&#xff0c;报错Unit firewalld.service could not be found 报错的原因是没有安装firewall&#xff0c;安装命令为sudo apt install firewalld&#xff0c;然后进行安装 安装完成后输入systemctl…

vscode jupyter选择Python环境时找不到我安装的Python

在一些情况下&#xff0c;我们需要自己安装一个Python&#xff0c;在选择内核是可能找不到指定的Python版本&#xff0c; 再次打开内核选择页面就能看到Python环境了 注意先到指定环境下安装依赖包&#xff1a; ./python3 pip install ipykernel notebook jupyter

人工智能-NLP简单知识汇总01

人工智能-NLP简单知识汇总01 1.1自然语言处理的基本概念 自然语言处理难点&#xff1a; 语音歧义句子切分歧义词义歧义结构歧义代指歧义省略歧义语用歧义 总而言之&#xff1a;&#xff01;&#xff01;语言无处不歧义 1.2自然语言处理的基本范式 1.2.1基于规则的方法 通…

[DataWhale大模型应用开发]学习笔记1-尝试搭建向量数据库

1.词向量 1.定义 词向量&#xff08;Word Vector&#xff09;是将单词表示为向量形式的技术&#xff0c;是自然语言处理&#xff08;NLP&#xff09;中的一种常用方法。通过将单词转化为向量&#xff0c;计算机能够更好地理解和处理语言。简单来说&#xff0c;词向量就是将单…

Windows系统安装NVM,实现Node.js多版本管理

目录 一、前言 二、NVM简介 三、准备工作 1、卸载Node 2、创建文件夹 四、下载NVM 五、安装NVM 六、使用NVM 1、NVM常用操作命令 2、查看NVM版本信息 3、查看Node.js版本列表&#xff1b; 4、下载指定版本Node.js 5、使用指定版本Node.js 6、查看已安装Node.js列…

【区块链+基础设施】国家健康医疗大数据科创平台 | FISCO BCOS应用案例

在医疗领域&#xff0c;疾病数据合法合规共享是亟待解决的难题。一方面&#xff0c;当一家医院对患者实施治疗后&#xff0c;若患者转到其 他医院就医&#xff0c;该医院就无法判断诊疗手段是否有效。另一方面&#xff0c;医疗数据属于个人敏感数据&#xff0c;一旦被泄露或被恶…