多进程并发服务器

news/2024/4/20 7:43:27/文章来源:https://blog.csdn.net/weixin_47173597/article/details/128089698
  • TCP三次握手建立连接
  • 错误处理模块:wrap.c,函数声明:wrap.h
  • 并发服务器模型(多进程,多线程)

转换大小写程序

服务端

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>        
#include <sys/socket.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <string.h>
#define PORT 6799
#define IP "127.0.0.1"
void print_err(char* str){perror(str);exit(-1);
}
int main(){int res_bind=0;int cfd=0;struct sockaddr_in serverAddr;struct sockaddr_in clientAddr;socklen_t cli_addr_len;int sfd=socket(AF_INET,SOCK_STREAM,0);if(sfd==-1) print_err("socket fails\n");//把内存清零,在使用结构体赋值前将缓冲区清零bzero(&serverAddr,sizeof(serverAddr));serverAddr.sin_family=AF_INET;serverAddr.sin_port=htons(PORT);//serverAddr.sin_addr.s_addr=htonl(INADDR_ANY);inet_pton(sfd,IP,&serverAddr.sin_addr.s_addr);res_bind=bind(sfd,(struct sockaddr*)&serverAddr,\sizeof(serverAddr));if(res_bind==-1) print_err("bind fails\n");listen(sfd,120);cli_addr_len=sizeof(clientAddr);cfd=accept(sfd,(struct sockaddr*)&clientAddr,&cli_addr_len);if(cfd==-1) print_err("accept fails\n");/*显示一下哪个客户端连接了*/char client_IP[100]={0};printf("client IP=%s,client PORT=%d\n",\inet_ntop(AF_INET,&clientAddr.sin_addr.s_addr,\client_IP,sizeof(client_IP)),\ntohs(clientAddr.sin_port));/*转换大小写*/char buf[30]={0};int n=read(cfd,buf,sizeof(buf));int i;for(i=0;i<n;i++){buf[i]=toupper(buf[i]);}write(cfd,buf,sizeof(buf));close(sfd);close(cfd);return 0;
}

用户端

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>        
#include <sys/socket.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <string.h>
#define PORT 6799
#define IP "127.0.0.1"
void print_err(char* str){perror(str);exit(-1);
}
int main(){int cfd=socket(AF_INET,SOCK_STREAM,0);if(cfd==-1) print_err("socket fails\n");struct sockaddr_in server_addr;int connect_res=0;memset(&server_addr,0,sizeof(server_addr));server_addr.sin_family=AF_INET;server_addr.sin_port=htons(PORT);inet_pton(AF_INET,IP,&server_addr.sin_addr.s_addr);connect_res=connect(cfd,(struct sockaddr*)&server_addr,\sizeof(server_addr));if(connect_res==-1) print_err("connect fails\n");/*执行程序*///把用户输入读到bufchar buf[100]={0};fgets(buf,sizeof(buf),stdin);//把buf内容写到用户端write(cfd,buf,strlen(buf));//把返回结果写回屏幕int n=read(cfd,buf,sizeof(buf));write(1,buf,sizeof(buf));close(cfd);return 0;
}

执行结果

程序分析

  • 每次创建出一个socket,socket的描述符都指向两个缓冲区,一个用来读,一个用来写
  • 两个套接字想进行网络通信,必须通过ip地址+端口号,才能建立网络连接

套接字的读写缓冲区在内核中定义,所以用户定义的缓冲区,需要借助write和read进行读写

用户定义的缓冲区在栈中

在网络通信中,套接字一定是成对出现的

  • 一端的发送缓冲区对应另一端的接收缓冲区
服务器端
  • 接收端也就是读缓冲区,需要从用户端套接字的写缓冲区读数据

    read(cfd,buf,sizeof(buf));

  • 然后本地实现大小写功能后,需要把数据写到用户端的接收端

    write(cfd,buf,sizeof(buf));

用户端
  • 首先需要把用户的键盘输入的内容读到自己定义的用户缓冲区buf

    fgets(buf,sizeof(buf),stdin);

  • 然后自己的发送端的内容(客户端写的)写到自己定义的buf,以便输出

    write(cfd,buf,strlen(buf));

  • 最后buf的数据,写出到屏幕上

    write(1,buf,sizeof(buf));

查看端口的命令

netstat -apn | grep 具体端口号

把错误处理进行封装

wrap.c(没有主函数)

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>         
#include <sys/socket.h>
#include <string.h>
#include <arpa/inet.h>
void print_err(char* str){perror(str);exit(-1);
}
/*socket封装*/
int Socket(int domain, int type, int protocol){int n=socket(domain,type,protocol);if(n==-1) print_err("socket fails\n");return n;}/*bind封装*/
int Bind(int sockfd, const struct sockaddr *addr,\socklen_t addrlen){int n=bind(sockfd,addr,addrlen);if(n==-1) print_err("bind fails\n");return n;
}
/*listen封装*/
int Listen(int sockfd, int backlog){int n=listen(sockfd,backlog);if(n==-1) print_err("listen fails\n");return n;
}
/*accept封装*/
int Accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen){int n=accept(sockfd,addr,addrlen);if(n==-1) print_err("accept fails\n");return n;
}
/*connect封装*/
int Connect(int sockfd, const struct sockaddr *addr,\socklen_t addrlen){int n=connect(sockfd,addr,addrlen);if(n==-1) print_err("connect fails\n");return n;
}

wrap.h(头文件声明)

#ifndef MY_WRAP
#define MY_WRAP
extern void print_err(char* str);
extern int Socket(int domain, int type, int protocol);
extern int Bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
extern int Listen(int sockfd, int backlog);
extern int Accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
extern int Connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
#endif

server.c(调用大写程序)

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>        
#include <sys/socket.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <string.h>
#include "wrap.h"
#define PORT 6799
#define IP "127.0.0.1"
int main(){int cfd,sfd;char buf[30];int n;struct sockaddr_in serverAddr;struct sockaddr_in clientAddr;socklen_t cli_addr_len;//socket()sfd=Socket(AF_INET,SOCK_STREAM,0);//bind()bzero(&serverAddr,sizeof(serverAddr));serverAddr.sin_family=AF_INET;serverAddr.sin_port=htons(PORT);inet_pton(sfd,IP,&serverAddr.sin_addr.s_addr);Bind(sfd,(struct sockaddr*)&serverAddr,\sizeof(serverAddr));//listen()Listen(sfd,12);//accept()cli_addr_len=sizeof(clientAddr);cfd=Accept(sfd,(struct sockaddr*)&clientAddr,&cli_addr_len);/*显示一下哪个客户端连接了*/char client_IP[100]={0};printf("client IP=%s,client PORT=%d\n",\inet_ntop(AF_INET,&clientAddr.sin_addr.s_addr,\client_IP,sizeof(client_IP)),\ntohs(clientAddr.sin_port));/*转换大小写*/n=read(cfd,buf,sizeof(buf));int i;for(i=0;i<n;i++){buf[i]=toupper(buf[i]);}write(cfd,buf,n);close(sfd);close(cfd);return 0;
}

client.c(调用小写函数)

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>        
#include <sys/socket.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <string.h>
#include "wrap.h"
#define PORT 6799
#define IP "127.0.0.1"
int main(){int n;char buf[100];int cfd=Socket(AF_INET,SOCK_STREAM,0);struct sockaddr_in server_addr;memset(&server_addr,0,sizeof(server_addr));server_addr.sin_family=AF_INET;server_addr.sin_port=htons(PORT);inet_pton(AF_INET,IP,&server_addr.sin_addr.s_addr);Connect(cfd,(struct sockaddr*)&server_addr,\sizeof(server_addr));//把用户输入读到buffgets(buf,sizeof(buf),stdin);//把buf内容写到用户端write(cfd,buf,strlen(buf));//把返回结果写回屏幕n=read(cfd,buf,sizeof(buf));write(1,buf,n);close(cfd);return 0;
}

程序运行

accept()函数错误处理

int Accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen){int n=accept(sockfd,addr,addrlen);/*异常处理和错误都会返回-1,所以要单独分析---比如:一个连接被断开了,错误号:ECONNABORTED---比如:因为收到信号被断开连接,错误号:EINTR这时候要么重新连接,要么结束连接,这里选“重新连接”*/
reconnectted:if(n==-1){if(errno==ECONNABORTED||(errno==EINTR))goto reconnectted;else print_err("accept fails\n");}return n;
}

需要考虑是正常退出(信号主动退出)还是异常退出

read()函数返回值

  • 正常情况大于0—实际读到字节数

    • 比如buf[1024],read恰好读到那么大,==1024
    • 如果不足,读到多少字节就返回多少字节
  • 恰好返回0----读到了文件末尾,管道被关闭了,socket被关闭

    如果没被关闭,还没写数据,不会读到0,因为会阻塞,直到读到或被终止

  • 等于-1

    • 正常退出
      • errno==EINTR,被信号终止,退出或重启
      • errno==EAGAIN,非阻塞方式读,但是还没有数据的情况
      • 。。。
    • 异常—(-1,出现错误)
/*read封装*/
ssize_t Read(int fd, void *buf, size_t count){int n=read(fd,buf,count);if(n==-1){again:if(errno==EINTR||errno==EAGAIN)goto again;else print_err("read fails\n");}return n;
}

TCP协议

由于网络层与硬件连接紧密,所以具有不稳定性(路由器宕机,网络传输慢)

传输层:

  • 如果完全不弥补,尽力而为

选用:无连接不可靠的报文传输—UDP

  • 如果完全弥补

选用:面向连接的可靠数据包传输—TCP

TCP的通信时序图

  • 两条竖线表示通讯的两端
  • 从上到下表示时间的先后顺序
  • 图中的箭头都是斜的—数据从一端传到网络的另一端也需要时间
  • 请求标志 数据包编号(携带数据大小) 序号

  • 这个序号在网络通讯中用作临时地址

    • 每发一个数据字节,这个序号要加1
    • 在接收端可以根据序号排出数据包的正确顺序,也可以发现丢包的情况
    • 规定SYN位FIN位也要占一个序号
  1. 首先客户端主动发起连接、发送请求
  2. 然后服务器端响应请求
  3. 然后客户端主动关闭连接

三次握手----建立连接

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-grYQqYNp-1669650410479)(/home/guojiawei/.config/Typora/typora-user-images/image-20221128154849051.png)]

miss表示最大段尺寸

  • 如果一个段太大,封装成帧后超过了链路层的最大帧长度,就必须在IP层分片
  • 为了避免这种情况,客户端声明自己的最大段尺寸,建议服务器端发来的段不要超过这个长度

在建立连接的同时,双方协商了一些信息,例如双方发送序号的初始值最大段尺寸

数据传输:三次握手后,四次握手前

在这里插入图片描述

  1. 客户端发出段4

从序号1001开始的20个字节数据,发送数据

  1. 服务器发出段5
  • 确认序号为1021,对序号为1001-1020的数据表示确认收到
  • 服务器在应答的同时也向客户端发送从序号8001开始的10个字节数据
  1. 客户端发出段6

对服务器发来的序号为8001-8010的数据表示确认收到

在数据传输过程中,ACK和确认序号是非常重要的,

  • 应用程序交给TCP协议发送的数据会暂存在TCP层的发送缓冲区
  • 发出数据包给对方之后,只有收到对方应答的ACK段才知道该数据包确实发到了对方
  • 如果因为网络故障丢失了数据包或者丢失了对方发回的ACK段,经过等待超时后TCP协议自动将发送缓冲区中的数据包重发

如图,不是发送一个数据就会得到一个应答请求

比如发送端发送数据快,接收端可以一下接收三四个数据,再答复ack

只要数据包序号是累加的即可
在这里插入图片描述

四次握手----关闭连接

TCP连接是全双工的,因此每个方向都必须单独进行关闭

一个 FIN只能终止这个方向的连接,只意味着这一方向上没有数据流动

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TtLvsB3M-1669650410483)(/home/guojiawei/.config/Typora/typora-user-images/image-20221128173307143.png)]

链路层的以太网帧大小很小,目的就是封装的包小,方便丢包后重传

如果数据过大,就只能分割数据,获得多个传输的数据包

多进程并发服务器

父进程应答请求,fork子进程通信

//父进程不断通过复制子进程实现多个连接while(1){//accept()----别忘了对第三个参数取地址cfd=Accept(sfd,(struct sockaddr*)&clientAddr,&client_addr_len);//fork()pid=fork();if(pid >0){//父进程close(cfd);//没有请求了,关闭cfd}else if(pid==0){close(sfd);break;//跳出进入子进程交互}}//结束应答if(pid==0){//子进程去交互while(1){//转换大小写n=Read(cfd,buf,sizeof(buf));if(n==0){//读到了末尾close(cfd);return 0;}for(i=0;i<n;i++)buf[i]=toupper(buf[i]);write(cfd,buf,n);}}
运行结果(nc)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-m6MOhdQp-1669650410484)(/home/guojiawei/.config/Typora/typora-user-images/image-20221128214210678.png)]

僵尸进程问题

子进程结束,但是父进程没有回收

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3NZlnApu-1669650410485)(/home/guojiawei/.config/Typora/typora-user-images/image-20221128221409998.png)]

 #include <sys/types.h>#include <sys/wait.h>
pid_t wait(int *wstatus);
pid_t waitpid(pid_t pid, int *wstatus, int options);

实现:

void wait_child(int singo){//0---所有子进程无差别回收while(waitpid(0,NULL,WNOHANG)>0);exit(-1);
}
if(pid >0){//父进程close(cfd);//没有请求了,关闭cfdsignal(SIGINT,wait_child);//子进程回收}

打印信息:网络字节序to本机

printf("client ip=%s,port=%d\n",inet_ntop(AF_INET,\&serverAddr.sin_addr.s_addr,client_IP,sizeof(client_IP)),\ntohs(serverAddr.sin_port));
  • inet_ntop(AF_INET**,&地址.s_addr,写入的缓存地址,**缓存长度);
  • ntohs(地址.sin_port);

实现了多进程并发的完整程序

wrap.c

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>         
#include <sys/socket.h>
#include <string.h>
#include <arpa/inet.h>
#include <errno.h>
void print_err(char* str){perror(str);exit(-1);
}
/*socket封装*/
int Socket(int domain, int type, int protocol){int n=socket(domain,type,protocol);if(n==-1) print_err("socket fails\n");return n;}/*bind封装*/
int Bind(int sockfd, const struct sockaddr *addr,\socklen_t addrlen){int n=bind(sockfd,addr,addrlen);if(n==-1) print_err("bind fails\n");return n;
}
/*listen封装*/
int Listen(int sockfd, int backlog){int n=listen(sockfd,backlog);if(n==-1) print_err("listen fails\n");return n;
}
/*accept封装*/
int Accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen){int n=accept(sockfd,addr,addrlen);/*异常处理和错误都会返回-1,所以要单独分析---比如:一个连接被断开了,错误号:ECONNABORTED---比如:因为收到信号被断开连接,错误号:EINTR这时候要么重新连接,要么结束连接,这里选“重新连接”*/
reconnectted:if(n==-1){if(errno==ECONNABORTED||(errno==EINTR))goto reconnectted;else print_err("accept fails\n");}return n;
}
/*connect封装*/
int Connect(int sockfd, const struct sockaddr *addr,\socklen_t addrlen){int n=connect(sockfd,addr,addrlen);if(n==-1) print_err("connect fails\n");return n;
}
/*read封装*/
ssize_t Read(int fd, void *buf, size_t count){int n=read(fd,buf,count);if(n==-1){again:if(errno==EINTR||errno==EAGAIN)goto again;else print_err("read fails\n");}return n;
}

wrap .o

#ifndef MY_WRAP
#define MY_WRAP
extern void print_err(char* str);
extern int Socket(int domain, int type, int protocol);
extern int Bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
extern int Listen(int sockfd, int backlog);
extern int Accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
extern int Connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
extern ssize_t Read(int fd, void *buf, size_t count);
#endif

server.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <string.h>
#include <ctype.h>
#include "wrap.h"
#include <sys/wait.h>
#include <signal.h>
#define PORT 6767
void wait_child(int singo){//0---所有子进程无差别回收while(waitpid(0,NULL,WNOHANG)>0);exit(-1);
}
int main(){int sfd,cfd;struct sockaddr_in serverAddr;struct sockaddr_in clientAddr;socklen_t client_addr_len;pid_t pid;char buf[100],client_IP[100];int n,i;sfd=Socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);/*bind()*/bzero(&serverAddr,sizeof(serverAddr));serverAddr.sin_family=AF_INET;serverAddr.sin_addr.s_addr=htonl(INADDR_ANY);//inet_pton(AF_INET,自己的IP,&地址.sin_adds.s_addr)serverAddr.sin_port=htons(PORT);Bind(sfd,(struct sockaddr*)&serverAddr,sizeof(serverAddr));Listen(sfd,12);client_addr_len=sizeof(clientAddr);/*利用多进程实现*///父进程不断通过复制子进程实现多个连接while(1){//accept()----别忘了取地址cfd=Accept(sfd,(struct sockaddr*)&clientAddr,&client_addr_len);//打印信息:ip+端口号printf("client ip=%s,port=%d\n",inet_ntop(AF_INET,\&serverAddr.sin_addr.s_addr,client_IP,sizeof(client_IP)),\ntohs(serverAddr.sin_port));//fork()pid=fork();if(pid >0){//父进程close(cfd);//没有请求了,关闭cfdsignal(SIGINT,wait_child);//子进程回收}else if(pid==0){close(sfd);break;//跳出进入子进程交互}}if(pid==0){//子进程去交互while(1){n=Read(cfd,buf,sizeof(buf));if(n==0){//读到了末尾close(cfd);return 0;}for(i=0;i<n;i++)buf[i]=toupper(buf[i]);write(cfd,buf,n);}}return 0;
}

client.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <string.h>
#include <ctype.h>
#include "wrap.h"
#define PORT 6767
int main(int argc,char** argv){int cfd;char* ip_ADDR=argv[1];//不可以用char ip_ADDR[100]char buf[100];struct sockaddr_in serverAddr;cfd=Socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);bzero(&serverAddr,sizeof(serverAddr));serverAddr.sin_family=AF_INET;serverAddr.sin_port=htons(PORT);inet_pton(AF_INET,ip_ADDR,&serverAddr.sin_addr.s_addr);Connect(cfd,(struct sockaddr*)&serverAddr,sizeof(serverAddr));/*程序实现*/while(1){fgets(buf,sizeof(buf),stdin);write(cfd,buf,strlen(buf));int n=read(cfd,buf,sizeof(buf));write(1,buf,n);}return 0;
}

#include"wrap .h"的头文件需要放到后面,不然编译不通过

基础补充

典型协议

传输层 常见协议有TCP/UDP协议

  • TCP传输控制协议(Transmission Control Protocol)

是一种面向连接的可靠的基于字节流的传输层通信协议

  • UDP用户数据报协议(User Datagram Protocol)

是OSI参考模型中一种无连接的传输层协议,提供面向事务简单不可靠信息传送服务

应用层常见的协议有HTTP协议,FTP协议

  • HTTP超文本传输协议(Hyper Text Transfer Protocol)
  • FTP文件传输协议(File Transfer Protocol)

网络层 常见协议有IP协议ICMP协议IGMP协议

  • IP协议是因特网互联协议(Internet Protocol)
  • ICMP协议是Internet控制报文协议(Internet Control Message Protocol)

是TCP/IP协议族的一个子协议,用于在IP主机、路由器之间传递控制消息

  • IGMP协议是 Internet 组管理协议(Internet Group Management Protocol)

是因特网协议家族中的一个组播协议。该协议运行在主机和组播路由器之间。

网络接口层 常见协议有ARP协议RARP协议、以太网帧协议

  • [ARP]协议是正向地址解析协议(Address Resolution Protocol)

通过已知的IP,寻找对应主机的MAC地址

  • [RARP]是反向地址转换协议

    通过MAC地址确定IP地址

网络应用程序设计模式

C/S模式

  • 传统的网络应用设计模式,客户机(client)/服务器(server)模式
  • 需要在通讯两端各自部署客户机和服务器来完成数据通信

B/S模式

  • 浏览器()/服务器(server)模式
  • 只需在一端部署服务器,而另外一端使用每台PC都默认配置的浏览器即可完成数据的传输。

优缺点

C/S模式

  1. 优点
  • 将数据缓存至客户端本地,从而提高数据传输效率,可以保证性能
  • 所采用的协议相对灵活,可以在标准协议的基础上根据需求裁剪及定制
  1. 缺点
  • 工作量将成倍提升,开发周期较长
  • 从用户角度出发,需要将客户端安装至用户主机上,对用户主机的安全性构成威胁

B/S模式

  • 没有独立的客户端,使用标准浏览器作为客户端,其工作开发量较小
  • 移植性非常好,不受平台限制

缺点:

  • 协议选择不灵活,必须采用标准协议
  • 缓存数据慢,传输数据量受限制

分层模型

OSI七层模型

TCP/IP四层模型

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-f3CCDmtA-1669650410486)(/home/guojiawei/.config/Typora/typora-user-images/image-20221124212002846.png)]

TCP/IP网络协议栈:

应用层(Application),传输层(Transport),网络层(Network)和链路层(Link)四层

目的主机收到数据包后,如何经过各层协议栈最后到达应用程序

  1. 以太网驱动程序根据以太网首部中的“上层协议”字段确定该数据帧的有效载荷是IP、ARP还是RARP协议的数据报

有效载荷:payload,指除去协议首部之外实际传输的数据

  1. 假如是IP数据报,IP协议再根据IP首部中的“上层协议”字段确定该数据报的有效载荷是TCP、UDP、ICMP还是IGMP
  2. 假如是TCP段或UDP段,TCP或UDP协议再根据TCP首部或UDP首部的“端口号”字段确定应该将应用层数据交给哪个用户进程
  • IP地址是标识网络中不同主机的地址

  • 而端口号就是同一台主机上标识不同进程的地址

  • IP地址和端口号合起来标识网络中唯一的进程。

  • 虽然IP、ARP和RARP数据报都需要以太网驱动程序来封装成

    • 但是从功能上划分,ARP和RARP属于链路层,IP属于网络层
  • 虽然ICMP、IGMP、TCP、UDP的数据都需要IP协议来封装成数据报

    • 但是从功能上划分,ICMP、IGMP与IP同属于网络层,TCP和UDP属于传输层。

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

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

相关文章

Mybatis Plus 多租户id使用

本文就不多逼逼&#xff0c;直接进入正题。 什么是多租户 多租户技术&#xff08;Multi-TenancyTechnology&#xff09;又称多重租赁技术&#xff0c;简称SaaS&#xff0c;是一种软件架构技术&#xff0c;是实现如何在多用户环境下 &#xff08;此处的多用户一般是面向企业用…

Java SPI机制的使用和理解

前言&#xff1a; SPI(Service Provider Interface)&#xff0c;是JDK内置的一种服务提供发现机制&#xff0c;Java中 SPI 机制主要思想是将装配的控制权移到程序之外&#xff0c;在模块化设计中这个机制尤其重要&#xff0c;其核心思想就是解耦 1、大家都知道API&#xff0c;却…

【C++】STL —— map和set的模拟实现

目录 一、基础铺垫 二、基本结构分析 1. 节点结构分析 2. 模板参数中仿函数分析 三、正向迭代器 四、封装完成的红黑树 五、map的模拟实现 六、set的模拟实现 一、基础铺垫 在前面的博客中我们了解了map和set的基本使用&#xff0c;以及对二叉搜索树、AVL树和红黑树的…

IPv6进阶:IPv6 过渡技术之 NAT64(IPv6 节点主动访问 IPv4 节点-地址池方式)

实验拓扑 PC1是IPv4网络的一个节点&#xff0c;处于Trust安全域&#xff1b;PC2是IPv6网络的一个节点&#xff0c;处于Untrust安全域。 实验需求 完成防火墙IPv4、IPv6接口的配置&#xff0c;并将接口添加到相应的安全域&#xff1b;在防火墙上配置NAT64的IPv6前缀3001::/64&…

【毕业设计】30-基于单片机矿井瓦斯_气体浓度_烟雾浓度报警设计(原理图+源代码+仿真+答辩论文+答辩PPT)

【毕业设计】30-基于单片机矿井瓦斯/气体浓度/烟雾浓度报警设计&#xff08;原理图源代码仿真答辩论文答辩PPT&#xff09; 文章目录【毕业设计】30-基于单片机矿井瓦斯/气体浓度/烟雾浓度报警设计&#xff08;原理图源代码仿真答辩论文答辩PPT&#xff09;任务书设计说明书摘要…

网络套接字编程(UDP协议)

文章目录预备知识socket&#xff08;网络套接字&#xff09;编程接口简单的UDP网络程序增加多用户可以互相通信预备知识 网络字节序 大端存储&#xff1a;数据的高字节内容保存在内存的低地址处&#xff0c;数据的低字节内容保存在内存的高地址处 小端存储&#xff1a;数据的高…

global关键字、python实现ATM简单功能

目录 一.局部变量、全局变量 二.global关键字 演示 三.编写ATM程序 要求 详细步骤 存在问题 改进 完整代码 一.局部变量、全局变量 1.什么是局部变量 作用范围在函数内部&#xff0c;在函数外部无法使用 2.什么是全局变量 在函数内部和外部均可使用 3.如何将函数内定…

[附源码]SSM计算机毕业设计校园自行车租售管理系统JAVA

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

高等数学(第七版)同济大学 习题10-3 (前9题)个人解答

高等数学&#xff08;第七版&#xff09;同济大学 习题10-3&#xff08;前9题&#xff09; 函数作图软件&#xff1a;Mathematica 1.化三重积分I∭Ωf(x,y,z)dxdydz为三次积分&#xff0c;其中积分区域Ω分别是\begin{aligned}&1. \ 化三重积分I\iiint_{\Omega}f(x, \ y, …

【C++】类型转换

目录 一、C语言风格类型转换 二、C风格类型转换 1.static_case 2.reinterpret_case 3、const_case 4、dynamic_case 三、RTTI 总结 一、C语言风格类型转换 在C语言中&#xff0c;如果赋值运算符左右两侧类型不同&#xff0c;或者形参与实参类型不匹配&#xff0c;或者返…

【正点原子FPGA连载】 第二十章 LCD触摸屏实验摘自【正点原子】DFZU2EG/4EV MPSoC 之FPGA开发指南V1.0

1&#xff09;实验平台&#xff1a;正点原子MPSoC开发板 2&#xff09;平台购买地址&#xff1a;https://detail.tmall.com/item.htm?id692450874670 3&#xff09;全套实验源码手册视频下载地址&#xff1a; http://www.openedv.com/thread-340252-1-1.html 第二十章 LCD触摸…

Vue.js 加入高德地图的实现方法

一、功能需求 1.根据输入内容进行模糊查询&#xff0c;选择地址后在地图上插上标记&#xff0c;并更新经纬度坐标显示 2.在地图点击后&#xff0c;根据回传的左边更新地址信息和坐标显示 二、准备 1.申请高德地图账号&#xff0c;创建应用 2.在应用管理中 获得key 和安全密…

[附源码]Python计算机毕业设计Django常见Web漏洞对应POC应用系统

项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等。 环境需要 1.运行环境&#xff1a;最好是python3.7.7&#xff0c;…

Python学习:json对象与string相互转换教程

首先要明确&#xff0c;python里有json这个库&#xff0c;但并没有json这个类&#xff0c;所以所谓的json对象本质上就是一个dict&#xff1b;而json这个库&#xff0c;用于实现dict到string、string到dict的互转。 更具体一点&#xff0c;json对象&#xff08;dict&#xff0…

Linux网络编程——IO多路复用

文章目录1&#xff0c;I/O模型2&#xff0c;阻塞I/O 模式2.1&#xff0c;读阻塞&#xff08;以read函数为例&#xff09;2.2&#xff0c;写阻塞3&#xff0c;非阻塞I/O模式3.1&#xff0c;非阻塞I/O模式的实现&#xff08;fcntl()函数、ioctl() 函数&#xff09;3.1.1&#xff…

leetcode 343. 整数拆分(动态规划)

题目链接&#xff1a;343. 整数拆分 动态规划 (1) 确定 dpdpdp 数组下标含义&#xff1a; dp[i]dp[i]dp[i]: 将 iii 拆分为至少两个正整数之后的最大乘积&#xff1b; (2) 确定递推公式&#xff1a; 当 i≥2i \ge 2i≥2 时, 设 jjj 是 iii 拆分出来的第一个正整数&#xff0c…

关于uni-app小程序接入微信登录

https://uniapp.dcloud.net.cn/api/plugins/login.html#login 官网上有关于uni.login()的说明&#xff0c;如果是要微信登录&#xff0c;则需要wx.login()。 小程序登录 | 微信开放文档 如下图&#xff0c;在小程序管理平台生成AppSecret&#xff0c;同时将AppId在HubilderX中…

swift @State @Published @ObservedObject sink

State struct ContentView: View {State private var isRain truevar body: some View {VStack {Image(systemName: isRain ? "cloud.rain.fill" : "sun.max.fill").resizable().frame(width: 100, height: 100)Text(isRain ? "我們淋著大雨不知何…

【PS-7】移动工具

目录 移动工具快捷键【v】&#xff08;英文状态&#xff09; 多文件间拖拽图层对象 快捷键【ALT】复制图层 【ALTSHIFT】只能垂直/水平/45角地去复制图层 4个方向键可以微调图层的位置 变换控件 对齐分布 【题外话】设置参考线颜色 【题外话】快捷键【F12】让已经动过…

实验三-----数据库

一、实验目的 1.掌握SQL Server Management Studio中SQL 查询操作&#xff1b; 2.掌握SQL 的单表查询命令&#xff1b; 3.掌握SQL 的连接查询操作&#xff1b; 4.掌握SQL 的嵌套查询操作&#xff1b; 5.掌握SQL 的集合查询操作。 二、实验环境 1&#xff0e;实验室名称&…