Linux网络编程1——socket通信
一.网络准备
1.套接字
在TCP/IP
协议中,“ip 地址+TCP 或UDP 端口号”唯一标识网络通讯中的一个进程。“IP 地址+端口号”就对应一个socket。欲建立连接的两个进程各自有一个 socket 来标识,那么这两个 socket 组成的 socket pair 就唯一标识一个连接。因此可以用 socket 来描述网络连接的一对一关系。
简单来说,用数学里面的坐标系类比一下,(IP地址,端口号)表示一个进程即一个套接字,两个套接字连线表示通信。
- 一个文件描述符指向一个套接字(该套接字内部由内核借助两个缓冲区实现)。
- 在通信过程中, 套接字一定是成对出现的。
2.网络字节序列
计算机(主机)对于多字节序列采用的是小端法存储,而网络通信的多字节序列是大端法存储的,因此在进行网络通信时,主机把数据送到客户端的发送缓冲区前需要把数据进行网络字节转换,同样的接受主机在服务器的接收端取出数据后,又要把数据转化成主机字节序列。
#include <arpa/inet.h>uint32_t htonl(uint32_t hostlong); //本地---》网络(IP地址)
uint16_t htons(uint16_t hostshort); //本地---》网络(端口)
uint32_t ntohl(uint32_t netlong); //网络---》本地(IP地址)
uint16_t ntohs(uint16_t netshort); //网络---》本地(端口)
解释:
h
表示本地主机(host)to
表示变换n
表示网络(net)l
表示IP
地址(IP
地址用32位表示)s
表示端口
注意:我们平时用点分十进制表示IP地址,所以要想进行网络字节转换,先要使用
atoi
把string
转化成int
3.IP地址转换函数
为了直接从点分十进制进行转化成网络字节序,所以有了IP地址转换函数。
#include <arpa/inet.h>int inet_pton(int af, const char *src, void *dst); //IP地址---》网络
解释:
af
:表示版本协议号,只能取AF_INET
表示IPv4或AF_INET6
表示IPv6src
:传入的IP地址(点分十进制,string
类型)dst
:传出的转换后的IP地址(网络字节序)- 返回值:1表示成功;0表示异常,说明传入的src不是一个IP地址;-1表示失败
#include <arpa/inet.h>const char *inet_ntop(int af, const void *src, char *dst, socklen_t size); //网络---》IP地址
解释:
src
:传入的转换后的IP地址(网络字节序)dst
:传出的IP地址(点分十进制,string
类型)size
:dst缓冲区的大小- 返回值:成功返回dst,失败返回NULL
4.sockaddr数据结构
直接说结论:后面的bind()
函数的参数要用到strcut sockaddr
这种结构体的指针,但是现在IPv4使用的结构体普遍是strcut sockaddr_in
,所以现在的使用方式是我们一般先定义strcut sockaddr_in
的结构体,使用的时候进行强转。比如:
struct sockaddr_in servaddr;
bind(listen_fd, (struct sockaddr *)&servaddr, sizeof(servaddr));
其余不要管,重点看bind()
的第二个参数。
所以,现在我们重点学习一下strcut sockaddr_in
结构体:
struct sockaddr_in {__kernel_sa_family_t sin_family; //地址结构类型__be16 sin_port; //端口号(网络字节序)struct in_addr sin_addr; //IP地址(网络字节序)
};struct in_addr { __be32 s_addr; //IP地址(网络字节序)
};
注意:这里的sockaddr_in
的第三个参数是一个结构体的形式,所以赋值时需要注意,例如:
struct sockaddr_in addr;
addr.sin_family = AF_INET/AF_INET6;
addr.sin_port = htons(9527);
int dst;
inet_pton(AF_INET,"192.157.22.45",(void *)&dst);
addr.sin_addr.s_addr = dst;
这里使用强转把dst
变成void *
类型,赋值是不要忘了.s_addr
进入对应结构体内部赋值。
一般的时候,我们用的是本地主机写代码,所以可以将:
int dst; inet_pton(AF_INET,"192.157.22.45",(void *)&dst); addr.sin_addr.s_addr = dst;
写成
addr.sin_addr.s_addr = htonl(INADDR_ANY);
这里的
INADDR_ANY
是一种宏,表示取出系统中有效的任意IP地址,但是是二进制类型。
二.网络套接字函数
1.socket模型
socket()
:创建套接字bind()
:绑定服务器的IP和端口listen()
:设置同时监听上限accept()
:阻塞监听客户端连接connect()
:客户端绑定IP和端口,进行连接
注意,在一个模型中其实有三个socket套接字,其中服务器刚开始的套接字在accept()函数里作为参数传入,返回另一个套接字来与客户端进行真正的连接。
2.socket函数
函数原型:
#include <sys/socket.h>int socket(int domain, int type, int protocol);
domain
:用来指定传输协议,AF_INET
、AF_INET6
、AF_UNIX
(表示本地协议,使用在Unix和Linux系统上,一般都是当客户端和服务器在同一台及其上的时候使用)type
:用来指定协议类型,可以取SOCK_STREAM
表示流式协议或SOCK_DGRAM
表示报式协议protocol
:传0表示默认协议
type为
SOCK_STREAM
且protocol=0表示使用TCP传输,type为SOCK_DGRAM
且protocol=0表示使用UDP传输。
3.bind函数
作用:给socket绑定一个地址结构(IP地址+端口)
#include <sys/socket.h>int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
sockfd
:socket
文件描述符,一般取上面socket()
函数的返回值*addr
:上面介绍过,用来指定地址结构信息addrlen
:地址结构大小,总是取sizeof(addr)
4.listen函数
作用:设置同时与服务器建立连接的客户端数量
#include <sys/socket.h>int listen(int sockfd, int backlog);
sockfd
:socket文件描述符,一般取上面socket()
函数的返回值backlog
:连接上限,最大为128
5.accept函数
作用:阻塞等待客户端连接,成功的话,返回一个成功与客户端连接的socket文件描述符
#include <sys/socket.h>int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
sockdf
: socket文件描述符,一般取上面socket()
函数的返回值,相当于把开始的socket传入addr
:传出参数,返回成功链接客户端地址信息,含IP地址和端口号addrlen
:传入传出参数(值-结果),传入sizeof(addr)
大小,函数返回时返回真正接收到地址结构体的大小- 返回值: 成功返回一个新的socket文件描述符,用于和客户端通信,失败返回-1,设置
errno
6.connect函数
作用:使用现有的socket
与服务器建立连接
#include <sys/socket.h>int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
sockfd
:socket
文件描述符,一般取上面socket()
函数的返回值*addr
:服务器的地址结构,用来连接addrlen
:地址结构大小,总是取sizeof(addr)
7.read函数
read函数用于从socket中读取数据。它的原型如下:
ssize_t read(int fd, void *buffer, size_t count);
fd
:是socket的文件描述符,用于标识一个打开的socket。buffer
:是一个指针,指向一个缓冲区,该缓冲区用于存储从socket读取的数据。count
:指定了buffer的大小,即希望读取的最大字节数。
read
函数返回实际读取的字节数。如果read返回0,表示连接已经关闭。如果返回-1,表示发生了错误,此时可以通过errno
变量查看错误类型。
8.write函数
write函数用于向socket写入数据。它的原型如下:
ssize_t write(int fd, const void *buffer, size_t count);
fd
:同样是socket的文件描述符。buffer
:是一个指针,指向包含要发送数据的缓冲区。count
:指定了要发送的字节数。write
函数返回实际写入的字节数。如果返回-1,表示发生了错误,同样可以通过errno
变量查看错误类型。
8.文件描述符
fd_set 是一个数据类型,用于在 select 系统调用中表示一组文件描述符。以下是与 fd_set 相关的一些常用宏函数,它们用于操作 fd_set:
- FD_ZERO:
作用:将 fd_set 清零,即初始化 fd_set,使其不包含任何文件描述符。
使用方法:FD_ZERO(&fdset); - FD_SET:
作用:将一个文件描述符添加到 fd_set 中。
使用方法:FD_SET(fd, &fdset); 其中 fd 是要添加的文件描述符,fdset 是 fd_set 的实例。 - FD_CLR:
作用:从 fd_set 中移除一个文件描述符。
使用方法:FD_CLR(fd, &fdset); 其中 fd 是要移除的文件描述符,fdset 是 fd_set 的实例。 - FD_ISSET:
作用:测试一个文件描述符是否在 fd_set 中。
使用方法:FD_ISSET(fd, &fdset); 如果 fd 在 fdset 中,则返回非零值;否则返回0。
三.实现一个简单的通信
1.通信流程分析
TCP通信流程分析:
server:
1. socket() 创建socket
2. bind() 绑定服务器地址结构
3. listen() 设置监听上限
4. accept() 阻塞监听客户端连接
5. read(fd) 读socket获取客户端数据
6. 小--大写 toupper()
7. write(fd)
8. close(); client:
1. socket() 创建socket
2. connect(); 与服务器建立连接
3. write() 写数据到 socket
4. read() 读转换后的数据。
5. 显示读取结果
6. close()
2.实现服务器
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <ctype.h>#define ser_port 9527
int main()
{//1.socket partint sfd=0,cfd=0;sfd = socket(AF_INET, SOCK_STREAM, 0);if(sfd==-1){printf("socket error\n");}//2.bind partstruct sockaddr_in ser_addr,cet_addr;ser_addr.sin_family=AF_INET;ser_addr.sin_port=htons(ser_port);ser_addr.sin_addr.s_addr = htonl(INADDR_ANY);bind(sfd, (struct sockaddr *)&ser_addr, sizeof(ser_addr));//3.listen partlisten(sfd,20);//4.accept partchar client_ip[BUFSIZ];socklen_t cet_addr_len = sizeof(cet_addr);cfd = accept(sfd,(struct sockaddr *)&cet_addr,&cet_addr_len);if(cfd==-1){printf("accept socket error\n");}//5.read partchar buf[BUFSIZ];while(true){int ret = read(cfd,buf,sizeof(buf));write(STDOUT_FILENO,buf,ret);for(int i=0;i<ret;i++){buf[i]=toupper(buf[i]);}write(cfd,buf,ret);}//6.close partclose(sfd);close(cfd);return 0;
}
3.实现客户端
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/in.h>
#include <arpa/inet.h>#define ser_port 9527
int main() {//1.socket partint hfd=0;hfd=socket(AF_INET, SOCK_STREAM, 0);if(hfd==-1){printf("socket error\n");}//2.connect partstruct sockaddr_in cil_addr;cil_addr.sin_family=AF_INET;cil_addr.sin_port=htons(ser_port);inet_pton(AF_INET,"192.168.242.128",&cil_addr.sin_addr.s_addr);connect(hfd,(struct sockaddr *)&cil_addr,sizeof(cil_addr));// 3. Write and Read partchar buf[BUFSIZ];int count = 10;while (count-- > 0) {if (write(hfd, "hello", 5) == -1) {perror("write");close(hfd);exit(EXIT_FAILURE);}int ret = read(hfd, buf, sizeof(buf));if (ret == -1) {perror("read");close(hfd);exit(EXIT_FAILURE);} else if (ret == 0) {printf("Server disconnected\n");break;}write(STDOUT_FILENO, buf, ret);}close(hfd);return 0;
}
四.出错封装函数思想
1.封装思想
上面简单的写了一个实现大小写转换的通信服务器和客户端,但是并不完善,里面的很多函数调用都没有做错误处理,在实际编写过程中并不规范。
我们以accept()
函数为例,在上面的例子中,我们是这样写的:
char client_ip[BUFSIZ];socklen_t cet_addr_len = sizeof(cet_addr);cfd = accept(sfd,(struct sockaddr *)&cet_addr,&cet_addr_len);if(cfd==-1){printf("accept socket error\n");}
但是实际过程中的main.cpp
代码量其实是很少的,我们可以把accept()
函数进行封装成类似自定义函数,存储在另一个源文件中,这样就可以在main.cpp调用自定义的Accept()
函数啦。
一般这种错误函数的封装,我们把重新自定义的函数名取为原函数名基础上首字母大写,然后在自定义函数内部实现错误封装,所以上述代码可以改成:
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;
}
注意:自定义函数封装在wrcp.cpp
文件中,不要忘了对应的wrcp.h
头文件。
2.读写函数
我们在C语言文件操作时,学习了一些读写函数,但是强调过只有read()
和write()
是满足系统调用的(即在socket通信过程中使用),所以平时的读写我们要自定义一些读(写)n字节函数或读(写)n行函数。下面代码仅供参考:
ssize_t Readn(int fd, void *vptr, size_t n)
{size_t nleft;ssize_t nread;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;
}
相关文章:

Linux网络编程1——socket通信
一.网络准备 1.套接字 在TCP/IP 协议中,“ip 地址TCP 或UDP 端口号”唯一标识网络通讯中的一个进程。“IP 地址端口号”就对应一个socket。欲建立连接的两个进程各自有一个 socket 来标识,那么这两个 socket 组成的 socket pair 就唯一标识一个连接。因…...
【每日一题】LeetCode 1052.爱生气的书店老板(数组、滑动窗口)
【每日一题】LeetCode 1052.爱生气的书店老板(数组、滑动窗口) 题目描述 书店老板的商店每天有不同数量的顾客进入。每分钟,老板可能或可能不会生气。如果老板生气,那一分钟的顾客就会不满意。老板知道一个秘密技巧,…...

IDEA中无法使用 Subversion 命令行客户端 svn Subversion 可执行文件的路径可能是错误的
IDEA中无法使用 Subversion 命令行客户端 svn 我在新电脑上安装好IDEA和SVN后使用IDEA拉取和提交项目时提示无法使用。 解决方案 我这边的问题是在安装TortoiseSVN的时候少启用了一个功能,需要重新安装并把这个功能启用。 在这一步需要把command line client to…...
ThreadLocal 在线程池中的内存泄漏问题
ThreadLocal 是一种非常方便的工具,它为每个线程创建独立的变量副本,避免了线程之间的共享数据问题。然而,在线程池环境中,ThreadLocal 的使用必须非常谨慎,否则可能会引发内存泄漏问题。 为什么 ThreadLocal 可能导致…...

如何编写Prompt,利用AI高效生成图表——图表狐(FoxChart)指南
在数据可视化领域,图表是数据的重要表达方式。为了让更多人能够轻松高校地生成美观、专业的图表,图表狐(FoxChart)应用而生。然而,要想充分发挥AI的潜力,编写合适的Prompt至关重要。本文介绍一些编写Prompt的原则,帮助…...

Redis主从数据同步过程:命令传播、部分重同步、复制偏移量等
请记住胡广一句话,所有的中间件所有的框架都是建立在基础之上,数据结构,计算机网络,计算机原理大伙一定得看透!!~ 1. Redis数据同步 1.1 数据同步过程 大家有没想过为什么Redis多机要进行数据同步&#…...

《JavaEE进阶》----13.<Spring Boot【配置文件】>
本篇博客讲解 1.SpringBoot配置文件的格式以及对应的语法 2.了解两个配置文件格式的差异、优缺点。 我们这里只做简单的介绍。看会,了解,学会读取就行了。 因为配置文件实在太多了,这里只做基础的介绍。 一、配置文件的作用 前言 计算机中有许…...

【练习8】
链接:https://www.nowcoder.com/questionTerminal/e671c6a913d448318a49be87850adbcc 分析: 创建一个二维数组来实现杨辉三角,因为当前元素的值是上一行的当前列与前一列的和,所以创建数组的时候要实现n1,相当于罩子一…...

vivado 时间汇总报告
步骤7:时间汇总报告 定时路径在时钟元素处开始和结束。输入和输出端口不是顺序的 元素,默认情况下,Vivado时序分析不会对进出I/O端口的路径进行计时 设计,除非指定了输入/输出延迟约束。 在此步骤中,您将在Vivado中生成…...

【软考】设计模式之代理模式
目录 1. 说明2. 应用场景3. 结构图4. 构成5. 适用性6. 优点7. 缺点8. java示例 1. 说明 1.代理模式(Proxy Pattern)。2.意图:为其他对象提供一种代理以控制对这个对象的访问。3.通过提供与对象相同的接口来控制对这个对象的访问。4.是设计模…...
3.创建型设计模式详解:生成器模式与原型模式的深度解析
设计模式(Design Patterns)是软件开发中常用的解决方案,帮助开发者处理常见的设计问题。创建型设计模式专注于对象的实例化,旨在提高系统的灵活性和可维护性。在这篇文章中,我们将深入探讨创建型设计模式中的生成器模式…...
goframe结构体标签和命令行标签
元数据gmeta 基础标签 更多了解:https://swagger.io/specification/ g.Meta path:"/profile" method:"get" summary:"展示个人资料页面" tags:"个人" g.Meta mime:"text/html" type:"string" example…...

pytest压力测试:不断发送数据,直到发现数据丢失
示例场景 假设有一个 send_data 函数接受数据并返回成功或失败的状态。 创建一个测试用例,通过逐步增加数据量来测试这个函数,直到返回失败为止。 步骤 定义压力测试函数 定义一个函数。不断发送数据,直到发现数据丢失。 创建 pytest 测试…...

自选择问题和处理效应模型
自选择问题和处理效应模型 DGP 注意: 这里的概率密度超过了1,这是正常的。概率密度的三原则,1是大于等于0;2是积分等于1;对于连续型随机变量,给定一个具体的x值,f(x)并不是该事件发生的概率。而…...

[数据集][目标检测]水面垃圾检测数据集VOC+YOLO格式2027张1类别
数据集格式:Pascal VOC格式YOLO格式(不包含分割路径的txt文件,仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数):2027 标注数量(xml文件个数):2027 标注数量(txt文件个数):2027 标注…...

OpenCV 之 模版匹配多个对象、图片旋转 综合应用
引言 在图像处理和计算机视觉中,模板匹配是一种常用的技术,用于在一幅较大的图像中查找与给定模板图像相似的部分。然而,在实际应用中,目标物体可能会出现在不同的角度,这就需要我们在匹配之前对模板进行旋转处理。本…...
ZooKeeper 中的 Curator 框架解析
Apache ZooKeeper 是一个为分布式应用提供一致性服务的软件。它提供了诸如配置管理、分布式同步、组服务等功能。在使用 ZooKeeper 时,Curator 是一个非常流行的客户端库,它简化了 ZooKeeper 的使用,提供了高级的抽象和丰富的工具。本文将详细…...

机械学习—零基础学习日志(Python做数据分析02)
现在开始使用Python尝试做数据分析。具体参考的网址链接放在了文章末尾。 引言 我通过学习《利用Python进行数据分析》这本书来尝试使用Python做数据分析。书里让下载,anaconda,使用Jupyter来写代码,只是下载一个anaconda的确有点费时间&am…...

BRAM IP Native模式使用
简介 BRAM(Block RAM)是FPGA(Field-Programmable Gate Array)中的一种专用RAM资源,固定分布在FPGA内部的特定位置。该内容主要对BRAM(Block RAM”的缩写)Native模式下IP界面做详细描述和使用…...
react的useRef用什么作用
useRef 是 React 提供的一个钩子,用于在函数组件中创建和管理对 DOM 元素或组件实例的引用。它返回一个包含 current 属性的对象,可以用来存储对某个值的引用,而这个引用在组件的整个生命周期内保持不变。 useRef 的主要用途 1.访问 DOM 元素…...

MPNet:旋转机械轻量化故障诊断模型详解python代码复现
目录 一、问题背景与挑战 二、MPNet核心架构 2.1 多分支特征融合模块(MBFM) 2.2 残差注意力金字塔模块(RAPM) 2.2.1 空间金字塔注意力(SPA) 2.2.2 金字塔残差块(PRBlock) 2.3 分类器设计 三、关键技术突破 3.1 多尺度特征融合 3.2 轻量化设计策略 3.3 抗噪声…...
spring:实例工厂方法获取bean
spring处理使用静态工厂方法获取bean实例,也可以通过实例工厂方法获取bean实例。 实例工厂方法步骤如下: 定义实例工厂类(Java代码),定义实例工厂(xml),定义调用实例工厂ÿ…...

高危文件识别的常用算法:原理、应用与企业场景
高危文件识别的常用算法:原理、应用与企业场景 高危文件识别旨在检测可能导致安全威胁的文件,如包含恶意代码、敏感数据或欺诈内容的文档,在企业协同办公环境中(如Teams、Google Workspace)尤为重要。结合大模型技术&…...

从零开始打造 OpenSTLinux 6.6 Yocto 系统(基于STM32CubeMX)(九)
设备树移植 和uboot设备树修改的内容同步到kernel将设备树stm32mp157d-stm32mp157daa1-mx.dts复制到内核源码目录下 源码修改及编译 修改arch/arm/boot/dts/st/Makefile,新增设备树编译 stm32mp157f-ev1-m4-examples.dtb \stm32mp157d-stm32mp157daa1-mx.dtb修改…...
unix/linux,sudo,其发展历程详细时间线、由来、历史背景
sudo 的诞生和演化,本身就是一部 Unix/Linux 系统管理哲学变迁的微缩史。来,让我们拨开时间的迷雾,一同探寻 sudo 那波澜壮阔(也颇为实用主义)的发展历程。 历史背景:su的时代与困境 ( 20 世纪 70 年代 - 80 年代初) 在 sudo 出现之前,Unix 系统管理员和需要特权操作的…...

让AI看见世界:MCP协议与服务器的工作原理
让AI看见世界:MCP协议与服务器的工作原理 MCP(Model Context Protocol)是一种创新的通信协议,旨在让大型语言模型能够安全、高效地与外部资源进行交互。在AI技术快速发展的今天,MCP正成为连接AI与现实世界的重要桥梁。…...

springboot整合VUE之在线教育管理系统简介
可以学习到的技能 学会常用技术栈的使用 独立开发项目 学会前端的开发流程 学会后端的开发流程 学会数据库的设计 学会前后端接口调用方式 学会多模块之间的关联 学会数据的处理 适用人群 在校学生,小白用户,想学习知识的 有点基础,想要通过项…...
QT3D学习笔记——圆台、圆锥
类名作用Qt3DWindow3D渲染窗口容器QEntity场景中的实体(对象或容器)QCamera控制观察视角QPointLight点光源QConeMesh圆锥几何网格QTransform控制实体的位置/旋转/缩放QPhongMaterialPhong光照材质(定义颜色、反光等)QFirstPersonC…...

七、数据库的完整性
七、数据库的完整性 主要内容 7.1 数据库的完整性概述 7.2 实体完整性 7.3 参照完整性 7.4 用户定义的完整性 7.5 触发器 7.6 SQL Server中数据库完整性的实现 7.7 小结 7.1 数据库的完整性概述 数据库完整性的含义 正确性 指数据的合法性 有效性 指数据是否属于所定…...

Python 实现 Web 静态服务器(HTTP 协议)
目录 一、在本地启动 HTTP 服务器1. Windows 下安装 node.js1)下载安装包2)配置环境变量3)安装镜像4)node.js 的常用命令 2. 安装 http-server 服务3. 使用 http-server 开启服务1)使用 http-server2)详解 …...