【C++高并发服务器WebServer】-12:TCP详解及实现

在这里插入图片描述

本文目录

  • 一、TCP通信流程
  • 二、套接字函数
    • 2.1 socket()
    • 2.2 bind()
    • 2.3 listen()
    • 2.4 accept()
    • 2.5 connect()
  • 三、demo实现
    • 3.1 server端代码
    • 3.2 client端代码
  • 四、TCP三次握手
  • 五、TCP滑动窗口
  • 六、TCP四次挥手
  • 七、多进程并发服务器

一、TCP通信流程

先来讲讲服务器端,是被动接受连接的角色,因为是被动的,所以需要先创建一个用于监听的套接字fd。监听有客户端的连接。套接字就是一个文件描述符。

socket创建的fd会有对应的读写缓冲区,然后将监听文件描述符和本地IP+端口绑定(IP+端口就是服务器的地址信息),这一步也就是bind,然后再进行listen。

开始监听之后,就是阻塞等待,当有客户端发起连接,会向监听的fd读缓冲区写数据,解除阻塞,接受客户端连接的时候,会得到一个和客户端通信的套接字fd,这个fd是新的,会有新的读/写缓冲区。

然后来看看客户端的流程,首先创建一个用于通信的套接字fd,然后连接服务器,需要指定连接的服务器的IP和端口,如果连接成功了就可以直接和服务器进行通信。

下图是TCP通信流程。

在这里插入图片描述

二、套接字函数

套接字的头文件主要有三个, 如下所示。

#include <sys/types.h>
#include <sys/socket.h>#include <arpa/inet.h> // 包含了这个头文件,上面两个就可以省略

具体的函数有以下几个常用的,接下来挨个来看看作用和使用。

int socket(int domain,int type,int protocol);
int bind(int sockfd,const struct sockaddr *addr, socklen_t addrlen);
int listen(int sockfd,int backlog);
int accept(int sockfd,const struct sockaddr *addr,socklen_t *addrlen);
int connect(int sockfd,const struct sockaddr *addr,socklen_t addrlen);ssize_t write(int fd,const void *buf,size_t count);
ssize_t read(int fd,void *buf,size_t count);

2.1 socket()

int socket(int domain,int type,int protocol);

功能:创建一个套接字。我们来查看下linux文档中系统函数库中socket的定义。man 2 socket,通过创建一个文件描述符fd进行读写操作。

在这里插入图片描述

参数1【domain】:协议族。常见的协议族protocol familiy,也称为domain。协议族的内容都定义在了头文件<sys/socket.h>当中。

AF_INET代表IPv4,AF_INET6代表ipv6,AF_UNIXAF_LCOAL代表本地套接字通信(也就是进程间通信)。

在这里插入图片描述

在这里插入图片描述

第二参数【type】:通信过程中使用的协议类型。
SOCK_STREAM流式协议(TCP),SOCK_DGRAM报式协议(如UDP等等)。

在这里插入图片描述

第三个参数【protocol】:具体的一个协议,一般写0。
流式报协议默认使用TCP,报式协议默认使用UDP。

在这里插入图片描述

这个函数的返回值就是返回文件描述符,这个文件描述符操作的就是内核的一个缓冲区(包括了读和写缓冲区)。失败了就会返回-1。

2.2 bind()

int bind(int sockfd,const struct sockaddr *addr, socklen_t addrlen);

作用就是将fd和本地的IP+端口进行绑定。

三个参数,sockfd是通过socket函数获得的文件描述符fd。addr就是需要绑定的socket地址,封装了ip和端口号的信息。addrlen是第二个参数结构体所占的内存的大小。

2.3 listen()

int listen(int sockfd,int backlog);

作用是进行监听,监听这个socket上的连接。

sockfd就是通过socket()得到的文件描述符,backlog是未连接和已连接的和的最大值。

调用listen在底层会有两个队列,一个是未连接的队列,一个是已经连接的队列。超过了backlog就会丢失。(一般不用指定太大。)

这个backlog最大值在路径/proc/sys/net/core/somaxconn下可以查看到。

在这里插入图片描述

2.4 accept()

int accept(int sockfd,const struct sockaddr *addr,socklen_t *addrlen);

接收客户端连接,默认是一个阻塞的函数,阻塞等待客户端连接。

addr:传出参数,记录连接成功后的客户端的地址信息(ip+port)。addrlen:指定第二个参数的对应的内存大小。

成功就返回用于通信的文件描述符,这个文件描述符专门用来通信的,失败了返回-1。

2.5 connect()

int connect(int sockfd,const struct sockaddr *addr,socklen_t addrlen);

这个是客户端用于连接服务器的函数。

sockfd用于通信的文件描述符,调用socket创建的。addr客户端要连接的服务器地址信息。

第三个参数addrlen可以直接sizeof(addrlen),但是上面accpet是不行的,需要定义某个参数=addrlen,然后把这个参数的地址传入给accpet作为第三个参数。

三、demo实现

3.1 server端代码

# include<stdio.h>
# include <arpa/inet.h>
# include<unistd.h>
# include<string.h>
# include<stdlib.h>int main() {//1.创建socket(用于创建套件字)// lfd 就是listen_fd,用于监听的fd。int lfd = socket(AF_INET, SOCK_STREAM, 0);//代码健壮性,判断返回值if (lfd==-1){perror("socket");exit(-1);}//2. bind绑定struct sockaddr_in saddr; //需要初始化,不然会随机数据saddr.sin_family = AF_INET;//把主机字节序转换成网络字节序,然后存给第三个传出参数//inet_pton(AF_INET,"127.0.0.1",saddr.sin_addr.s_addr);   saddr.sin_addr.s_addr = INADDR_ANY; //服务器端才可以这么写saddr.sin_port= htons(9999); //9999是主机字节序,进行网络通信的时候需要转换为网络字节序//因为saddr是sockaddr_in类型的指针,所以还需要转换成sockaddr类型的指针int ret = bind(lfd, (struct sockaddr *)&saddr,sizeof(saddr) );if (ret==-1){perror("bind");exit(-1);}//3.监听ret = listen(lfd,8);if (ret==-1){perror("bind");exit(-1);}//4.接收客户端连接struct sockaddr_in clientaddr;int len = sizeof(clientaddr);int cfd = accept(lfd,(struct sockaddr *)&clientaddr,&len);if(cfd==-1){perror("client");exit(-1);}//5.输出客户端的信息,获取过来的信息是网络字节序,需要转换为主机字节序char clientIP[16];inet_ntop(AF_INET,&clientaddr.sin_addr.s_addr,clientIP,sizeof(clientIP));unsigned short clientPort = ntohs(clientaddr.sin_port);printf("client ip is %s,port is %d\n",clientIP,clientPort);//6.进行通信,先获取客户端的数据,然后给客户端发送数据char recvBuf[1024]={0};int num = read(cfd,recvBuf,sizeof(recvBuf));if(num==-1){perror("read");exit(-1);}else if(num>0){printf("recv client data: %s\n",recvBuf);}else if(num==0){//客户端断开连接printf("client closed...");}char *data = "hello,i am server";write(cfd,data,strlen(data));//关闭文件描述符close(lfd);close(cfd);return 0;
}

3.2 client端代码

# include<stdio.h>
# include<arpa/inet.h>
# include<unistd.h>
# include<string.h>
# include<stdlib.h>int main(){//创建套接字int fd = socket(AF_INET,SOCK_STREAM,0);if(fd==-1){perror("socket");exit(-1);}//连接服务器struct sockaddr_in serveraddr;serveraddr.sin_family = AF_INET;inet_pton(AF_INET,"127.0.0.1",&serveraddr.sin_addr.s_addr);serveraddr.sin_port = htons(9999);int ret = connect(fd,(struct sockaddr *)&serveraddr,sizeof(serveraddr));if(fd==-1){perror("socket");exit(-1);} //3.通信char *data = "hello,i am client";write(fd,data,strlen(data));char recvBuf[1024]={0};int len = read(fd,recvBuf,sizeof(recvBuf));if(len==-1){perror("read");exit(-1);}else if(len>0){printf("recv server data: %s\n",recvBuf);}else if(len==0){//客户端断开连接printf("server closed...");}close(fd);return 0;
}

上面的代码就是实现了一次的收发。

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

四、TCP三次握手

在这里插入图片描述

我们先看看TCP头部结构中的一些重要数据。

32 位序号(sequence number):一次 TCP 通信(从TCP 连接建立到断开)过程中某一个传输方向上的字节流的每个字节的编号。假设主机A和主机 B进行 TCP 通信,A发送给 B的第一个TCP 报文段中,序号值被系统初始化为某个随机值ISN(Initial Sequence Number,初始序号值)。那么在该传输方向上(从 A到 B),后续的TCP 报文段中序号值将被系统设置成ISN 加上该报文段所携带数据的第一个字节在整个字节流中的偏移。例如,某个 TCP 报文段传送的数据是字节流中的第 1025~2048 字节,那么该报文段的序号值就是ISN+1025。另外一个传输方向(从B 到 A)的 TCP 报文段的序号值也具有相同的含义。

32 位确认号(acknowedgement number):用作对另一方发送来的TCP 报文段的响应。其值是收到的 TCP 报文段的序号值+标志位长度(SYN,FIN)+数据长度。假设主机A和主机B进行TCP 通信,那么 A发送出的TCP 报文段不仅携带自己的序号,而且包含对B发送来的 TCP 报文段的确认号。反之,B发送出的 TCP 报文段也同样携带自己的序号和对A发送来的报文段的确认序号。

头部长度这个字段是4个字节为单位的,4位最大能够表示15,所以TCP头部的长度最长是60字节。

6位的标志位中最重要的几个分别是ACK、SYN、FIN。
ACK 标志,表示确认号是否有效。我们称携带 ACK 标志的 TCP 报文段为确认报文段。
SYN 标志,表示请求建立一个连接。我们称携带 SYN 标志的 TCP 报文段为同步报文段。
FIN 标志,表示通知对方本端要关闭连接了。我们称携带 FIN 标志的 TCP 报文段为结束报文
段。

三次握手发生在客户端连接的时候, 当调用connect的时候,底层会通过TCP协议进行三次握手。

两次握手是不行的,通信双方各自都要确认自己是能收发数据的

客户端第一次发的时候,只确认了自己能够发。

服务端收到第一次数据之后,能够确认自己的收和客户端的发是没问题的,然后服务端发送ACK确认,进一步又确认了服务端自己的发是没问题的。(此时服务端能够确认自己能够收发,并且客户端能够发。)

然后客户端收到了之后,能够确认服务端能够进行收、发的,并且确认了自己能够没问题的收发。(此时只差服务端能够确认客户端能够正常收到自己所发送的数据,所以还差一次客户端发送,也就是最后一次握手。)

然后最后客户端会再发一次给服务端,服务端收到之后,能够清楚客户端是能够正常收的。
在这里插入图片描述
在这里插入图片描述
第一次握手:
1.客户端将SYN标志位置为1
2.生成一个随机的32位的序号seq=J , 这个序号后边是可以携带数据(数据的大小)

第二次握手:
1.服务器端接收客户端的连接: ACK=1
2.服务器会回发一个确认序号: ack=客户端的序号 + 数据长度 + SYN/FIN(按一个字节算)
3.服务器端会向客户端发起连接请求: SYN=1
4.服务器会生成一个随机序号:seq = K

第三次握手:
1.客户单应答服务器的连接请求:ACK=1
2.客户端回复收到了服务器端的数据:ack=服务端的序号 + 数据长度 + SYN/FIN(按一个字节算)。

只有当SYN或者FIN为1的时候,ack才会为seq+1,其余的时候都是加上具体的数据。

在这里插入图片描述

五、TCP滑动窗口

滑动窗口(Sliding window)是一种流量控制技术。早期的网络通信中,通信双方不会考虑网络的拥挤情况直接发送数据。由于大家不知道网络拥塞状况,同时发送数据,导致中间节点阻塞掉包,谁也发不了数据,所以就有了滑动窗口机制来解决此问题。

滑动窗口协议是用来改善吞吐量的一种技术,即容许发送方在接收任何应答之前传送附加的包。接收方告诉发送方在某一时刻能送多少包(称窗口尺寸)。TCP 中采用滑动窗口来进行传输控制,滑动窗口的大小意味着接收方还有多大的缓冲区可以用于接收数据。发送方可以通过滑动窗口的大小来确定应该发送多少字节的数据。当滑动窗口为 0时,发送方一般不能再发送数据报。

滑动窗口是 TCP 中实现诸如 ACK 确认、流量控制、拥塞控制的承载结构。

在这里插入图片描述

窗口可以理解为缓冲区的大小,滑动窗口的大小会随着发送数据和接收数据而变化。通信的双方都有发送缓冲区和接收数据的缓冲区。

下图中的一些数据说明,mssMaximum Segment Size,一条数据的最大数据量。win:Windows,滑动窗口。
在这里插入图片描述

六、TCP四次挥手

四次挥手发生在断开连接的时候,在程序当中调用了close()使用TCP协议进行四次挥手。

客户端和服务端都可以主动发起断开连接,谁先调用close谁就是发起者。

因为连接是双向的,所以 断开的时候也需要双向断开。
在这里插入图片描述

七、多进程并发服务器

在main函数中,程序首先通过sigaction系统调用注册了SIGCHLD信号的处理函数,确保能够及时回收子进程。接着,程序创建了一个TCP套接字,并将其绑定到本地的9999端口上,然后进入监听状态,等待客户端的连接请求。一旦有客户端连接,服务器会通过accept函数接受连接,并为每个连接创建一个子进程来处理与该客户端的通信。子进程会获取客户端的IP地址和端口号,并通过循环不断读取客户端发送的数据,然后将接收到的数据原样发送回客户端,直到客户端关闭连接。子进程在处理完客户端请求后会退出。父进程则继续等待新的客户端连接。

信号处理函数recyleChild,用于回收子进程,防止僵尸进程的产生。当收到SIGCHLD信号时,该函数会调用waitpid函数以非阻塞的方式回收所有已经结束的子进程,并打印被回收子进程的PID。

#include <stdio.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <wait.h>
#include <errno.h>void recyleChild(int arg) {while(1) {int ret = waitpid(-1, NULL, WNOHANG);if(ret == -1) {// 所有的子进程都回收了break;}else if(ret == 0) {// 还有子进程活着break;} else if(ret > 0){// 被回收了printf("子进程 %d 被回收了\n", ret);}}
}int main() {struct sigaction act;act.sa_flags = 0;sigemptyset(&act.sa_mask);act.sa_handler = recyleChild;// 注册信号捕捉sigaction(SIGCHLD, &act, NULL);// 创建socketint lfd = socket(PF_INET, SOCK_STREAM, 0);if(lfd == -1){perror("socket");exit(-1);}struct sockaddr_in saddr;saddr.sin_family = AF_INET;saddr.sin_port = htons(9999);saddr.sin_addr.s_addr = INADDR_ANY;// 绑定int ret = bind(lfd,(struct sockaddr *)&saddr, sizeof(saddr));if(ret == -1) {perror("bind");exit(-1);}// 监听ret = listen(lfd, 128);if(ret == -1) {perror("listen");exit(-1);}// 不断循环等待客户端连接while(1) {struct sockaddr_in cliaddr;int len = sizeof(cliaddr);// 接受连接int cfd = accept(lfd, (struct sockaddr*)&cliaddr, &len);if(cfd == -1) {if(errno == EINTR) {continue;}perror("accept");exit(-1);}// 每一个连接进来,创建一个子进程跟客户端通信pid_t pid = fork();if(pid == 0) {// 子进程// 获取客户端的信息char cliIp[16];inet_ntop(AF_INET, &cliaddr.sin_addr.s_addr, cliIp, sizeof(cliIp));unsigned short cliPort = ntohs(cliaddr.sin_port);printf("client ip is : %s, port is %d\n", cliIp, cliPort);// 接收客户端发来的数据char recvBuf[1024];while(1) {int len = read(cfd, &recvBuf, sizeof(recvBuf));if(len == -1) {perror("read");exit(-1);}else if(len > 0) {printf("recv client : %s\n", recvBuf);} else if(len == 0) {printf("client closed....\n");break;//这个break也是有作用的,如果不break就会输出两次close,因为会继续到write,然后到循环,然后又判一次close}write(cfd, recvBuf, strlen(recvBuf) + 1);}close(cfd);exit(0);    // 退出当前子进程}}close(lfd);return 0;
}

这里简单回顾下信号集的知识点。之所以不使用wait是因为会阻塞。

    //SIGCHLD信号是在子进程结束时由操作系统发送给父进程的信号。//sigaction是POSIX标准中用于信号处理的结构体//sa_flags是一个标志位,用于控制信号处理的特殊行为。//将它设置为0表示不启用任何特殊行为,即使用默认的信号处理方式。//调用了sigemptyset函数,用于初始化act.sa_mask.//sa_mask是一个信号集,用于指定在信号处理函数执行期间需要屏蔽的信号。//sigemptyset函数的作用是清空信号集,即不屏蔽任何信号。struct sigaction act;  act.sa_flags = 0;sigemptyset(&act.sa_mask);act.sa_handler = recyleChild;// 注册信号捕捉sigaction(SIGCHLD, &act, NULL);

客户端代码如下。

// TCP通信的客户端
#include <stdio.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>int main() {// 1.创建套接字int fd = socket(AF_INET, SOCK_STREAM, 0);if(fd == -1) {perror("socket");exit(-1);}// 2.连接服务器端struct sockaddr_in serveraddr;serveraddr.sin_family = AF_INET;inet_pton(AF_INET, "127.0.0.1", &serveraddr.sin_addr.s_addr);serveraddr.sin_port = htons(9999);int ret = connect(fd, (struct sockaddr *)&serveraddr, sizeof(serveraddr));if(ret == -1) {perror("connect");exit(-1);}// 3. 通信char recvBuf[1024];int i = 0;while(1) {sprintf(recvBuf, "data : %d\n", i++);// 给服务器端发送数据//这里+1 是因为要算进去字符换行的结束符,不然会有问题。//服务器端write也是同理的。write(fd, recvBuf, strlen(recvBuf)+1);int len = read(fd, recvBuf, sizeof(recvBuf));if(len == -1) {perror("read");exit(-1);} else if(len > 0) {printf("recv server : %s\n", recvBuf);} else if(len == 0) {// 表示服务器端断开连接printf("server closed...");break;}sleep(1);}// 关闭连接close(fd);return 0;
}

这里有个需要注意的地方是,在strlen(recvBuf)后面进行了+1的操作。在处理字符串时,strlen函数返回字符串的实际长度,但不包括字符串末尾的空字符(‘\0’)。然而,C语言中的字符串是以空字符结尾的,因此在将字符串写入套接字时,如果需要将整个字符串(包括空字符)发送给客户端,就需要在strlen返回的长度基础上加1。

另外还有一个点需要注意,服务端中的代码:

当某个子进程退出的时候,会产生信号,然后执行回调,执行回调就相当于一个软中断,软终端会产生一个错误EINTR,执行完中断之后会回到accpet,然后继续执行accpet,但是这个时候会执行不了,会出错,会返回-01,然后导致退出进程。

也就是说,accept是一个阻塞系统调用,用于等待客户端的连接请求。当父进程正在执行accept时,如果子进程退出,操作系统会发送SIGCHLD信号给父进程。父进程接收到SIGCHLD信号后,会中断当前的accept调用,并执行信号处理函数recyleChild。信号处理函数执行完成后,accept调用会返回-1,并将errno设置为EINTR,表示系统调用被中断了。

所以需要改进下面的代码。

        int cfd = accept(lfd, (struct sockaddr*)&cliaddr, &len);if(cfd == -1) {perror("accept");exit(-1);}

改进后的代码如下所示。

        int cfd = accept(lfd, (struct sockaddr*)&cliaddr, &len);if(cfd == -1) {if(errno == EINTR) {continue;}perror("accept");exit(-1);}

在这里插入图片描述
EINTR是一个常见的Linux错误代码,表示一个系统调用被一个信号中断了。在这种情况下,系统调用是在建立一个有效的连接之前被信号中断的。这通常发生在服务器或网络编程中,当一个阻塞调用如accept、read或write正在等待时,如果进程收到了一个信号,这些调用可能会被中断。

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

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

相关文章

【Maven】项目管理工具-Maven

目录 1. Maven简介 1.1 项目管理 1.2 项目构建 1.3 项目构建工具 1.4 Maven的四大特征 1.4.1 依赖管理系统 1.4.2 多模块构建 1.4.3 一致的项目结构 1.4.4 一致的构建模型和插件机制 1.5 Maven模型 ​编辑 2.maven的安装配置 2.1 Maven的安装配置 2.1.1检测jdk的版…

dijkstra算法类型题解

dijkstra算法&#xff08;有权图&#xff0c;无权图&#xff09;&#xff1a; 带权路径长度——当图是带权图时&#xff0c;一条路径上所有边的权值之和&#xff0c;称为该路径的带权路径长度 初始化三个数组&#xff0c;final标记各顶点是否已找到最短路径&#xff0c;dist最…

RabbitMQ 消息顺序性保证

方式一&#xff1a;Consumer设置exclusive 注意条件 作用于basic.consume不支持quorum queue 当同时有A、B两个消费者调用basic.consume方法消费&#xff0c;并将exclusive设置为true时&#xff0c;第二个消费者会抛出异常&#xff1a; com.rabbitmq.client.AlreadyClosedEx…

基于开源AI智能名片2+1链动模式S2B2C商城小程序的个人IP活动运营策略与影响力提升研究

摘要&#xff1a;本文围绕个人IP运营者借助活动运营提升影响力这一主题&#xff0c;深入探讨如何将开源AI智能名片21链动模式S2B2C商城小程序融入借势、造势、提升参与感及用户激励等活动运营环节。通过分析该创新模式与活动运营各要素的结合点&#xff0c;为个人IP运营者提供切…

计算机图形学论文 | 面向制造的设计: 五轴铣削的几何制造可行性评估

&#x1f355;&#x1f355;&#x1f355;宝子们好久不见&#xff0c;新年快乐~~~&#xff0c;今天我们来更新一篇关于五轴CNC制造中的模型制造可达性分析的论文。老规矩&#xff1a; 红色是名词&#xff0c;蓝色是结论&#xff0c;绿色是文章工作&#xff0c;黄色是一些其他重…

deepseek搭建本地知识库

ollama是一个大模型的运行框架&#xff0c;在上面可以运行不同的大模型 部署deepseek 下载ollama&#xff1a;https://ollama.com/ 下载模型&#xff1a;https://ollama.com/library/deepseek-r1:1.5b ollama run deepseek-r1:1.5b运行起来之后&#xff0c;本地命令行就可以…

青少年编程与数学 02-009 Django 5 Web 编程 01课题、概要

青少年编程与数学 02-009 Django 5 Web 编程 01课题、概要 一、Django 5Django 5 的主要特性包括&#xff1a; 二、MVT模式三、官方网站四、内置功能数据库 ORM&#xff08;对象关系映射&#xff09;用户认证和授权表单处理模板引擎URL 路由缓存框架国际化和本地化安全性功能管…

deepseek本地部署-linux

1、官网推荐安装方法(使用脚本,我绕不过github,未采用) 登录ollama下载网站https://ollama.com/download/linux,linux下有下载脚本。 正常来说,在OS系统下直接执行脚本即可。 2、手动安装方法 2.1获取ollama-linux-arm64.tgz wget https://ollama.com/download/ollam…

多光谱技术在华为手机上的应用发展历史

2018 年&#xff0c;华为 P20 系列首次搭载 5 通道色温传感器&#xff0c;可帮助手机在不同光照条件下保持画面色彩一致性。 2020 年&#xff0c;华为 P40 系列搭载 8 通道多光谱色温传感器&#xff08;实际为 11 通道&#xff0c;当时只用 8 个通道检测可见光&#xff09;&am…

增加工作台菜单页面,AI问答应用支持上下文设置,数据库表索引优化,zyplayer-doc 2.4.8 发布啦!

zyplayer-doc是一款适合企业和个人使用的WIKI知识库管理工具&#xff0c;支持在线编辑富文本、Markdown、表格、Office文档、API接口、思维导图、Drawio以及任意的文本文件&#xff0c;专为私有化部署而设计&#xff0c;最大程度上保证企业或个人的数据安全&#xff0c;支持以内…

4.python+flask+SQLAlchemy+达梦数据库

前提 1.liunx Centos7上通过docker部署了达梦数据库。从达梦官网下载的docker镜像。(可以参考前面的博文) 2.windows上通过下载x86,win64位的达梦数据库,只安装客户端,不安装服务端。从达梦官网下载达梦数据库windows版。(可以参考前面的博文) 这样就可以用windows的达…

基础入门-网站协议身份鉴权OAuth2安全Token令牌JWT值Authirization标头

知识点&#xff1a; 1、网站协议-http/https安全差异&#xff08;抓包&#xff09; 2、身份鉴权-HTTP头&OAuth2&JWT&Token 一、演示案例-网站协议-http&https-安全测试差异性 1、加密方式 HTTP&#xff1a;使用明文传输&#xff0c;数据在传输过程中可以被…

【零基础学Mysql】常用函数讲解,提升数据操作效率的利器

以耳倾听世间繁华&#xff0c;以语表达心中所想 大家好,我是whisperrrr. 前言&#xff1a; 大家好&#xff0c;我是你们的朋友whisrrr。在日常工作中&#xff0c;MySQL作为一款广泛使用的开源关系型数据库&#xff0c;其强大的功能为我们提供了便捷的数据存储和管理手段。而在…

C++ 使用CURL开源库实现Http/Https的get/post请求进行字串和文件传输

CURL开源库介绍 CURL 是一个功能强大的开源库&#xff0c;用于在各种平台上进行网络数据传输。它支持众多的网络协议&#xff0c;像 HTTP、HTTPS、FTP、SMTP 等&#xff0c;能让开发者方便地在程序里实现与远程服务器的通信。 CURL 可以在 Windows、Linux、macOS 等多种操作系…

win编译openssl

一、perl执行脚本 1、安装perl脚本 perl安装 2、配置perl脚本 perl Configure VC-WIN32 no-asm no-shared --prefixE:\openssl-x.x.x\install二、编译openssl 1、使用vs工具编译nmake 如果使用命令行nmake编译会提示“无法打开包括文件: “limits.h”“ 等错误信息 所以…

idea启动报错# EXCEPTION_ACCESS_VIOLATION (0xc0000005) at pc=0x00007ffccf76e433

# EXCEPTION_ACCESS_VIOLATION (0xc0000005) at pc0x00007ffccf76e433, pid17288, tid6696 # # JRE version: (11.0.248) (build ) # Java VM: OpenJDK 64-Bit Server VM (11.0.248-LTS, mixed mode, sharing, tiered, compressed oops, g1 gc, windows-amd64) 不知道为什么…

穷举vs暴搜vs深搜vs回溯vs剪枝系列一>不同路径 III

目录 整体思路&#xff1a;代码设计&#xff1a;代码呈现&#xff1a; 整体思路&#xff1a; 代码设计&#xff1a; 代码呈现&#xff1a; class Solution {int ret,step;int m,n;boolean[][] vis;public int uniquePathsIII(int[][] grid) {m grid.length;n grid[0].length…

Idea 2024.3 使用CodeGPT插件整合Deepseek

哈喽&#xff0c;大家好&#xff0c;我是浮云&#xff0c;最近国产大模型Deepseek异常火爆&#xff0c;作为程序员我也试着玩了一下&#xff0c;首先作为简单的使用&#xff0c;大家进入官网&#xff0c;点击开始对话即可进行简单的聊天使用&#xff0c;点击获取手机app即可安装…

Houdini subuv制作输出阵列图

在游戏开发中经常需要用到sheet阵列图&#xff0c;并用其制作翻页动画。通过Houdini强大的节点组合可以配合输出subuv阵列图供游戏引擎使用。 本文出处&#xff1a;https://zhuanlan.zhihu.com/p/391796978 博主参考学习并写该文。 1.在obj分类下创建font节点以进行测试&#…

使用page assist浏览器插件结合deepseek-r1 7b本地模型

为本地部署的DeepSeek R1 7b模型安装Page Assist&#xff0c;可以按照以下步骤进行&#xff1a; 一、下载并安装Ollama‌ 首先&#xff0c;你需要下载并安装Ollama&#xff0c;这是部署DeepSeek所必需的工具。你可以访问Ollama的官方网站&#xff08;ollama.com&#xff09;下…