[计算机网络]---网络编程套接字
前言
作者:小蜗牛向前冲
名言:我可以接受失败,但我不能接受放弃
如果觉的博主的文章还不错的话,还请
点赞,收藏,关注👀支持博主。如果发现有问题的地方欢迎❀大家在评论区指正
目录
一、基础知识
1、源IP地址和目的IP地址
2、端口号
二、网络套接字
1、网络字节序
2、socket编程接口
三、基于tcp协议的网络通信
1、服务器的编写
2、客户端的编写
3、日志报告的编写
四、TCP协议通讯流程
1、通信流程
2、三次握手和四次挥手
本期学习:网络基础知识,网络套接字,基于tcp协议的网络编程,tcp协议的三次握手和四次挥手。
一、基础知识
1、源IP地址和目的IP地址
源IP地址:
- 源IP地址是指发起网络通信的设备或主机的IP地址。
- 在TCP/IP协议中,源IP地址用于标识数据包的来源,使得接收方知道从哪里收到数据。
- 源IP地址包含在网络数据包的IP头部中。
目的IP地址:
- 目的IP地址是指网络通信的目标设备或主机的IP地址。
- 在TCP/IP协议中,目的IP地址用于指定数据包的目标,确保数据包被传递到正确的位置。
- 目的IP地址同样包含在网络数据包的IP头部中。
下面我们用唐僧取经的例子来理解:
唐僧到女儿国,那国王问,高僧从那来,到哪里去 ,在网络中就是问源ip和目的ip。
也可能会问高僧上一站从那来,下一站到哪里去。在网络中指的就是MAC地址。
MAC地址:
MAC地址(Media Access Control address),也称为物理地址或硬件地址,是网络通讯中用于唯一标识网络接口控制器(NIC,网络接口卡)的一个地址。每个网络设备的NIC都有一个全球唯一的MAC地址,这个地址在生产时被固化在硬件中。
MAC地址的长度通常是48位(6个字节),有时也表示为64位以适应某些特定技术标准。
MAC地址通常以十六进制数表示,每个字节之间用冒号(:)或者破折号(-)分隔,例如00:1A:2B:3C:4D:5E
或00-1A-2B-3C-4D-5E
。
2、端口号
我们光有IP地址就可以完成通信了嘛? 想象一下发qq消息的例子, 有了IP地址能够把消息发送到对方的机器上, 但是还需要有一个其他的标识来区分出, 这个数据要给哪个程序进行解析。所以就提出用端口号来标识唯一的程序(服务器)。
端口号(port)是传输层协议的内容:
- 端口号是一个2字节16位的整数;
- 端口号用来标识一个进程, 告诉操作系统, 当前的这个数据要交给哪一个进程来处理;
- IP地址 + 端口号能够标识网络上的某一台主机的某一个进程;
- 一个端口号只能被一个进程占用
问题1: 我们之前在学习系统编程的时候, 学习了 pid 表示唯一一个进程; 此处我们的端口号也是唯一表示一个进程. 那么这 两者之间是怎样的关系?
a:系统是系统,网络是网络,这里可以达到解耦的效果
b: 需要客户端每次都能找到服务端,而pid是在进程每次生成时随机分配的
c:不是所以的网络进程都需要网络提供网络服务或者请求,但是所以的进程都需要用pid进行标识
所以说:一个进程可以绑定多个端口号; 但是一个端口号不能被多个进程绑定
注意:
- 端口号范围从0到65535,其中0到1023是被知名服务占用的端口号,称为“系统端口”或“保留端口”,而1024到65535是动态或私有端口,用于一般应用程序或自定义服务(下面我们进行的测试常用8080端口)
- 传输层协议(TCP和UDP)的数据段中有两个端口号, 分别叫做源端口号和目的端口号. 就是在描述 "数据是谁发的, 要 发给谁"。
二、网络套接字
1、网络字节序
我们已经知道,内存中的多字节数据相对于内存地址有大端和小端之分, 磁盘文件中的多字节数据相对于文件中的偏 移地址也有大端小端之分, 网络数据流同样有大端小端之分. 那么如何定义网络数据流的地址呢?
在网络中无论是发送主机还是接收主机都是将:缓冲区中的数据按内存地址从低到高的顺序发送或者接收
因此,网络数据流的地址应这样规定:先发出的数据是低地址,后发出的数据是高地址
TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节
也就是说不管这台主机是大端机还是小端机,到会按照大端机发送。
库函数做网络 字节序和主机字节序的转换。
#include<arpa/inet.h>uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);
- 这些函数名很好记,h表示host,n表示network,l表示32位长整数,s表示16位短整数。
- 例如htonl表示将32位的长整数从主机字节序转换为网络字节序,例如将IP地址转换后准备发送。
- 如果主机是小端字节序,这些函数将参数做相应的大小端转换然后返回; 如果主机是大端字节序,这些函数不做转换,将参数原封不动地返回.
2、socket编程接口
API(Application Programming Interface)是一组定义在软件中不同组件之间交互的规范和工具。API可以看作是一座桥梁,它定义了如何访问或使用软件组件的方法。在软件开发中,API允许不同的程序部分之间进行通信,以便它们能够相互协作而无需详细了解彼此的内部实现。
常见的API
// 创建 socket 文件描述符 (TCP/UDP, 客户端 + 服务器)
int socket(int domain, int type, int protocol);
// 绑定端口号 (TCP/UDP, 服务器)
int bind(int socket, const struct sockaddr *address,socklen_t address_len);
// 开始监听socket (TCP, 服务器)
int listen(int socket, int backlog);
// 接收请求 (TCP, 服务器)
int accept(int socket, struct sockaddr* address,socklen_t* address_len);
// 建立连接 (TCP, 客户端)
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
sockaddr结构
sockaddr
结构(有时候在不同的系统中会有稍微不同的变体,如 sockaddr_in
)是用于表示套接字地址信息的数据结构,在网络编程中经常会遇到。它通常用于指定网络通信中的端点地址,包括 IP 地址和端口号。
struct sockaddr {unsigned short sa_family; // 地址族,如 AF_INET(IPv4)或 AF_INET6(IPv6)char sa_data[14]; // 地址数据
};
主要的字段:
sa_family
:用于指定地址族,表示地址的类型。例如,AF_INET
表示 IPv4 地址族,AF_INET6
表示 IPv6 地址族等。这个字段是一个无符号短整型(unsigned short
)。
sa_data
:包含地址的具体数据,通常用于存储 IP 地址和端口号等信息。在不同的地址族下,这个字段的内容会有所不同。
在实际使用中,为了更方便地表示 IPv4 地址,通常会使用更具体的套接字地址结构,如 sockaddr_in
,它的定义如下:
struct sockaddr_in {short int sin_family; // 地址族,如 AF_INETunsigned short int sin_port; // 端口号struct in_addr sin_addr; // IPv4 地址unsigned char sin_zero[8]; // 未使用的填充字段
};
三、基于tcp协议的网络通信
上面我们说了怎么多,下面我们就用起来,虽然我们现在还是那么清楚udp和tcp协议,但是我大概清楚了他是用来通信的。
这里我们要实现一个简单版本的服务器为客户端提供服务,这里我们的服务仅仅需要回显信息。
我们要写一个服务器
tcpServer.hpp
tcpServer.cc
一个客户端
tcpClient.hpp
tcpClinet.cc
一个日志报告
log.hpp
1、服务器的编写
tcpServer.hpp:这里我们要完成服务器的初始化,启动和销毁。
服务器的初始化是通过socket创建套嵌字,bind绑定网络,在进行listen监听.
启动要完成accept获取新链接,在执行程序任务。
这里我们在执行任务的过程中,我们可以:
- 让程序自己执行
- 创建子进程执行
- 让多线程执行
- 让线程池执行
这里为了让程序更好的理解,我们就让子进程执行我们serverio。
#pragma once#include <iostream>
#include <string>
#include <cstring>
#include <cstdlib>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <signal.h>
#include <pthread.h>#include "log.hpp"
#include "Task.hpp"
#include "ThreadPool.hpp"namespace server
{enum{USAGE_ERR = 1, // usage_errSOCKET_ERR, // sockft_errBIND_ERR, // bind_errLISTEN_ERR // listen_err};static const uint16_t gport = 8080;static const int gbacklog = 5;class TcpServer; // 这是一个前置声明,我们在类tcpServer中定义了ThreadData数据的类防止出现循环依赖class ThreadData{public:ThreadData(TcpServer *self, int sock) : _self(self), _sock(sock){}public:TcpServer *_self;int _sock;};class TcpServer{public:TcpServer(const uint16_t &port = gport) : _listensock(-1), _port(port){}void initServer(){// 1 创建套接字_listensock = socket(AF_INET, SOCK_STREAM, 0); // sock_streamif (_listensock < 0){logMessage(FATAL, "create error socket");exit(SOCKET_ERR);}logMessage(NORMAL, "create socket success:%d", _listensock);// 2 bind自己的网络信息struct sockaddr_in local;memset(&local, 0, sizeof(local));local.sin_family = AF_INET;local.sin_port = htons(_port);local.sin_addr.s_addr = INADDR_ANY; // inaddr_anyif (bind(_listensock, (struct sockaddr *)&local, sizeof(local)) < 0){logMessage(FATAL, "bind error ");exit(BIND_ERR);}logMessage(NORMAL, "bind socket success");// 3 设置socket 为监听状态if (listen(_listensock, gbacklog) < 0){logMessage(FATAL, "listen socket success");exit(LISTEN_ERR);}logMessage(NORMAL, "listen socket success");}void start(){for (;;){// 4 server获取链接// sock, 和client进行通信的fdstruct sockaddr_in peer;socklen_t len = sizeof(peer);int sock = accept(_listensock, (struct sockaddr *)&peer, &len);if (sock < 0){logMessage(ERROR, "accept error, next");continue;}logMessage(NORMAL, "accept a new link success, get new sock: %d", sock);// 5. 这里就是一个sock,未来通信我们就用这个sock,面向字节流的,后续全部都是文件操作!// version1// serviceIO(sock);// close(sock);// version2多进程版本pid_t id = fork();// 我们让子进程执行的io任务,父进程什么都不做,回收子进程就好if (id == 0){close(_listensock);serviceIO(sock);close(sock);exit(0);}close(sock);// 父进程pid_t ret = waitpid(id, nullptr, 0);if (ret > 0){std::cout << "waitsuccess: " << ret << std::endl;}// version3多线程// pthread_t tid;// ThreadData *td = new ThreadData(this, sock);// pthread_create(&tid, nullptr, threadRoutine, td);// pthread_join(tid, nullptr);// version4 线程池// ThreadPool<Task>::getInstance()->push(Task(sock, serviceIO));}}// static void *threadRoutine(void *args)// {// pthread_detach(pthread_self());// ThreadData *td = static_cast<ThreadData *>(args);// serviceIO(td->_sock);// close(td->_sock);// delete td;// return nullptr;// }~TcpServer(){}private:int _listensock; // 用了监听uint16_t _port;};
}
下面是一些任务:
#pragma once#include <iostream>
#include <string>
#include <cstdio>
#include <functional>using namespace std;void serviceIO(int sock)
{char buffer[1024];while (true){ssize_t n = read(sock, buffer, sizeof(buffer) - 1);if (n > 0){// 把读到的数据当做字符串buffer[n] = 0;cout << "recv message" << buffer << endl;// 信息返回string outbuffer = buffer;outbuffer += buffer;outbuffer += "server[echo]";// outbuffer.c_str(),将字符串类型的指针,转换为字符类型的指针write(sock, outbuffer.c_str(), sizeof(outbuffer) - 1);}else if (n == 0){// client退出logMessage(NORMAL, "client quit,me to");break;}}
}class Task
{// using 用于创建类型别名using func_t = function<void(int)>;public:Task(){}Task(int sock, func_t func): _sock(sock), _callback(func){}void operator()(){_callback(_sock);}~Task(){}private:int _sock;func_t _callback;
};
tcpServer.cc:主程序的执行
#include "tcpserver.hpp"
#include "daemon.hpp"
#include <memory>using namespace server;
using namespace std;static void Usage(string proc)
{cout << "\nUsage:\n\t" << proc << " local_port\n\n";
}// tcp服务器,启动上和udp server一模一样
// ./tcpserver local_port
int main(int argc, char *argv[])
{if (argc != 2){Usage(argv[0]);exit(USAGE_ERR);}uint16_t port = atoi(argv[1]);unique_ptr<TcpServer> tsvr(new TcpServer(port));tsvr->initServer();// daemonSelf();tsvr->start();// daemonSelf();return 0;
}
2、客户端的编写
tcpclient.hpp:对于客户端的初始化,我们仅仅只是需要创建套接字,而bind操作系统会帮助我们完成。
客户端的启动我们要用connect接收链接
#pragma once#include <iostream>
#include <string>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>#define NUM 1024
using namespace std;
class tcpclient
{
public:tcpclient(const string &serverip, const uint16_t &serverport): _sock(-1), _serverip(serverip), _serverport(serverport){}void initclient(){// 1 创建套接字_sock = socket(AF_INET, SOCK_STREAM, 0);if (_sock < 0){std::cerr << "socket create error" << std::endl;exit(2);}}void start(){struct sockaddr_in server;memset(&server, 0, sizeof(server));server.sin_family = AF_INET;server.sin_port = htons(_serverport);server.sin_addr.s_addr = inet_addr(_serverip.c_str());if (connect(_sock, (struct sockaddr *)&server, sizeof(server)) != 0){std::cerr << "socket connect error" << std::endl;}else{string msg;while (true){cout << "Enter#";std::getline(std::cin, msg);write(_sock, msg.c_str(), sizeof(msg));char buffer[NUM];int n = read(_sock, buffer, sizeof(buffer));if (n > 0){// 目前我们把读到的数据当成字符串, 截止目前buffer[n] = 0;std::cout << "Server回显# " << buffer << std::endl;}elsebreak;}}}~tcpclient(){if (_sock >= 0)close(_sock);}private:int _sock;std::string _serverip;uint16_t _serverport;
};
tcpcline.cc:执行程序
#include "tcpclient.hpp"
#include <memory>using namespace std;static void Usage(string proc)
{cout << "\nUsage:\n\t" << proc << " serverip serverport\n\n";
}
// ./tcpclient serverip serverport
int main(int argc, char *argv[])
{if (argc != 3){Usage(argv[0]);exit(1);}string serverip = argv[1];uint16_t serverport = atoi(argv[2]);unique_ptr<tcpclient> tcli(new tcpclient(serverip, serverport));tcli->initclient();tcli->start();return 0;
}
3、日志报告的编写
对于日志报告我们通过对错误进行分类,进行不同等级的日志错误信息输入。
#pragma once#include <iostream>
#include <cstdio>
#include <sys/types.h>
#include <unistd.h>
#include <time.h>// 定义错误类型
#define DEBUG 0 // debug
#define NORMAL 1 // normal
#define WARNING 2 // warning
#define ERROR 3 // error
#define FATAL 4 // fatal致命#define NUM 1024// 定于不同类型日常写入的文件
#define LOG_NORMAL "log.txt"
#define LOG_ERR "log.error"
// 标志位
const char *to_levelstr(int level)
{switch (level){case DEBUG:return "DEBUG";case NORMAL:return "NORMAL";case WARNING:return "NORMAL";case ERROR:return "ERROR";case FATAL:return "FATAL";}
}// 日志信息
void logMessage(int level, const char *format, ...) // 可变参数函数
{// [日志等级] [时间戳/时间] [pid] [messge]// [WARNING] [2023-05-11 18:09:08] [123] [创建socket失败]char logprefix[NUM]; // 存放日志前缀snprintf(logprefix, sizeof(logprefix), "[%d][%ld][pid::%d]",to_levelstr(level), (long int)time(nullptr), getpid());char logcontent[NUM];va_list arg;snprintf(logprefix, sizeof(logprefix), format, arg);FILE *log = fopen(LOG_NORMAL, "a"); // log_normalFILE *err = fopen(LOG_ERR, "a"); // log_errorif (log != nullptr && err != nullptr){FILE *curr = nullptr;if (level == DEBUG || level == NORMAL || level == WARNING)curr = log;if (level == ERROR || level == FATAL)curr = err;if (curr)fprintf(curr, "%s%s\n", logprefix, logcontent);fclose(log);fclose(err);}
}
程序运行测试:
四、TCP协议通讯流程
1、通信流程
TCP协议的通讯流程可以分为以下几个步骤:
创建连接:客户端和服务端都需要创建一个套接字(socket),这通常涉及到系统调用如`socket()`。
连接请求:客户端向服务端的套接字发送连接请求报文,这一步可以通过`connect()`函数来实现。
服务器响应:服务器收到客户端的连接请求后,需要进行确认,以确定是否允许连接。这通常涉及三部分握手:
- 服务器的确认ACK
- 客户端的确认SYN
- 服务器的应答ACK
连接建立:在双方完成了三次握手后,连接正式建立。此时,客户端可以开始发送数据给服务器,而服务器也可以开始接收数据。
数据传输:在这个阶段,客户端和服务器端可以进行数据的读写操作。这些操作包括读取(`recv()`)、写入(`send()`)以及关闭连接(`close()`)。
断开连接:当通信结束后,客户端或服务器端可以选择断开当前的连接。这通常涉及到四次挥手:
- 服务器的释放FIN
- 客户端的确认ACK
- 服务器的确认ACK
- 客户端的释放ACK
错误处理:在通信过程中,可能会遇到各种错误,如数据包丢失或乱序。TCP提供了自动重传机制来处理这些问题。
2、三次握手和四次挥手
TCP三次握手(Three-way Handshake):
客户端发送连接请求(SYN): 客户端向服务器发送一个特殊的TCP报文段,该报文段中设置了SYN(同步)标志位,表明客户端要求建立连接,并指明初始序列号(ISN)。
服务器确认请求并发送自己的连接请求(SYN + ACK): 服务器接收到客户端的连接请求后,向客户端发送一个确认报文段。该报文段中既包含了确认序号(ACK),也设置了SYN标志位,表示服务器同意建立连接,并指明自己的初始序列号。
客户端确认服务器的连接请求(ACK): 客户端接收到服务器的确认报文后,会再次向服务器发送一个确认报文段,其中确认号字段会加1,表示客户端也同意建立连接。
TCP四次挥手(Four-way Handshake)
发起关闭请求: 通信的一方(称为主动关闭方)发送一个FIN报文段,表示它已经完成了数据的发送任务。
接收关闭请求并发送确认: 接收到关闭请求的一方(被动关闭方)收到FIN报文后,会发送一个确认报文,表明它已经接收到关闭请求。
发送数据并发起关闭请求: 被动关闭方发送完所有数据后,也会向主动关闭方发送一个FIN报文段,表示它已经完成了数据的发送任务,并且准备关闭连接。
确认关闭请求并发送关闭确认: 主动关闭方接收到被动关闭方的FIN报文后,会发送一个确认报文,表明它已经接收到了关闭请求。一旦被动关闭方收到了这个确认,连接就会关闭。
在linux下有许多命令,老是忘记,在下面的文章中多会为大家分享一些小命令:
Linux小命令 :
netstat
是一个用于显示网络状态信息的命令行工具,常用于查看网络连接、路由表、接口统计等信息。在给定的命令中,-nltp
参数的含义如下:
-n
: 显示数字形式的地址和端口号,而不进行域名和服务名称的解析。-l
: 仅显示监听状态的连接。-t
: 仅显示TCP协议的连接。-p
: 显示与连接相关的进程标识符(PID)及进程名称。
相关文章:

[计算机网络]---网络编程套接字
前言 作者:小蜗牛向前冲 名言:我可以接受失败,但我不能接受放弃 如果觉的博主的文章还不错的话,还请点赞,收藏,关注👀支持博主。如果发现有问题的地方欢迎❀大家在评论区指正 目录 一、基础知识…...

分布式文件系统 SpringBoot+FastDFS+Vue.js【二】
分布式文件系统 SpringBootFastDFSVue.js【二】 六、实现上传功能并展示数据6.1.创建数据库6.2.创建spring boot项目fastDFS-java6.3.引入依赖6.3.fastdfs-client配置文件6.4.跨域配置GlobalCrosConfig.java6.5.创建模型--实体类6.5.1.FastDfsFile.java6.5.2.FastDfsFileType.j…...

开源软件:推动软件行业繁荣的力量
文章目录 📑引言开源软件的优势分析开放性与透明度低成本与灵活性创新与协作 开源软件对软件行业的影响推动技术创新和进步促进软件行业的合作与交流培养人才和提高技能促进软件行业的可持续发展 结语 📑引言 随着信息技术的飞速发展,软件已经…...

[杂记]mmdetection3.x中的数据流与基本流程详解(数据集读取, 数据增强, 训练)
之前跑了一下mmdetection 3.x自带的一些算法, 但是具体的代码细节总是看了就忘, 所以想做一些笔记, 方便初学者参考. 其实比较不能忍的是, 官网的文档还是空的… 这次想写其中的数据流是如何运作的, 包括从读取数据集的样本与真值, 到数据增强, 再到模型的forward当中. 0. MMDe…...

阿里云香港轻量应用服务器怎么样,建站速度快吗?
阿里云香港服务器中国香港数据中心网络线路类型BGP多线精品,中国电信CN2高速网络高质量、大规格BGP带宽,运营商精品公网直连中国内地,时延更低,优化海外回中国内地流量的公网线路,可以提高国际业务访问质量。阿里云服务…...

事务及在SpringBoot项目中使用的两种方式
1.事务简介 事务(transaction)是访问并可能操作各种数据项的一个数据库操作序列,这些操作要么全部执行,要么全部不执行,是一个不可分割的工作单位。 事物的四大特性: 原子性(Atomicity)…...

stm32--笔记
一、引脚与变量 二、STM32时钟 [STM32-时钟系统详解_stm32时钟_KevinFlyn的博客-CSDN博客] 三、定时器中断实验 1、定时器中断实验 stm32关于通用定时器的周期、频率计算公式_stm32tim频率计算_胶囊咖啡的博客-CSDN博客 【STM32】通用…...
2024前端面试准备之CSS篇(二)
全文链接 1. 什么是伪类和伪元素 伪类(Pseudo-class): 伪类是选择器的一种,用于选择特定状态或条件下的元素。它们以冒号(:)开头,用于向选择器添加额外的特定条件。例如,:hover伪类用于选择鼠标悬停在元素上的状态,:nth-child(n)伪类用于选择父元素下的第n个子元素等。…...

轨道交通信号增强与覆盖解决方案——经济高效,灵活应用于各类轨道交通场景!
方案背景 我国是世界上轨道交通里程最长的国家,轨道交通也为我们的日常出行带来极大的便利。伴随着无线通信技术的快速发展将我们带入电子时代,出行的过程中对无线通信的依赖程度越来越高,无论是车站还是车内都需要强大、高质量的解决方案以…...
学习数据接构和算法的第10天
题目讲解 尾插 #include <stdio.h> #include <stdlib.h> // 定义顺序表结构 #define MAX_SIZE 100 struct ArrayList {int array[MAX_SIZE];int size; // 当前元素个数 }; // 初始化顺序表 void init(struct ArrayList *list) {list->size 0; // 初始时元素个…...

初识KMP算法
目录 1.KMP算法的介绍 2.next数组 3.总结 1.KMP算法的介绍 首先我们会疑惑,什么是KMP算法?这个算法是用来干什么的? KMP(Knuth-Morris-Pratt)算法是一种用于字符串匹配的经典算法,它的目标是在一个主文本…...

Javaweb之SpringBootWeb案例之AOP概述及入门的详细解析
2.1 AOP概述 什么是AOP? AOP英文全称:Aspect Oriented Programming(面向切面编程、面向方面编程),其实说白了,面向切面编程就是面向特定方法编程。 那什么又是面向方法编程呢,为什么又需要面向…...
【Java代码洁癖】NO.2 单元测试mock显式赋值,不能忍
反例 RunWith(MockitoJunitRunner.class) public class Test {Mockpublic SomeBean someBean new SomeBean(); } 正例 RunWith(MockitoJunitRunner.class) public class Test {Mockpublic SomeBean someBean ; } 解读 使用Mock注解的对象不应该被显式赋值,应当…...

2024.2.19
使用fread和fwrite完成两个文件的拷贝 #include<stdio.h> #include<stdlib.h> #include<string.h> int main(int argc, const char *argv[]) {FILE *fpNULL;if((fpfopen("./tset.txt","w"))NULL){perror("open error");retur…...

B端系统升级方案模板:针对美观性和体验性升级(总体方案)
大家好,我是大美B端工场,专注于前端开发和UI设计,有需求可以私信。本篇从全局分享如何升级B端系统,搞B端系统升级的有个整体思维,不是说美化几个图标,修改几个页面就能解决的,这个方案模板&…...

第九篇:node静态文件服务(中间件)
🎬 江城开朗的豌豆:个人主页 🔥 个人专栏 :《 VUE 》 《 javaScript 》 📝 个人网站 :《 江城开朗的豌豆🫛 》 ⛺️ 生活的理想,就是为了理想的生活 ! 📘 引言: 当今互联网时代&am…...
软件测试-功能测试-测试流程-如何进行需求评审?对于测试人员来讲,如何从测试的角度评审需求文档?
导言 产品人员编写的需求文档,无疑是一个项目或者一项新功能的开端。需求文档的优劣,直接影响开发人员的代码质量,更会影响到后续的测试工作。所以,我认为,需求评审对于开发质量以及测试质量至关重要,那么…...

无刷电机驱动详解
无刷电机驱动详解 有刷电机和无刷电机字面上理解最大的区别就是有无电刷,实际上区别还有换向器,电刷和换向器的作用是什么?电刷负责在旋转部件与静止部件之间传导电流,换向器则利用旋转惯性周期性的改变线圈中电流的方向。 所以…...

Linux+Win双系统远程重启到Win
背景 电脑安装了双系统(ubuntu 22.04 win11),默认进入ubuntu系统。给电脑设置了WoL(Wake-on-LAN),方便远程开机远程控制。 但是ubuntu的引导程序grub无法远程控制,远程开机会默认进入ubuntu。 虽然说可以进入ubuntu后…...

【XR806开发板试用】+移植rosserial到XR806
1 XR806简介 板子来源于极术社区的试用,XR806的在线网址 其主要参数: 主控XR806AF2LDDRSIP 288KB SRAM存储SIP 160KB Code ROM. SIP 16Mbit Flash.天线板载WiFi/BT双天线,可共存按键reboot按键 1,功能按键 1灯红色电源指示灯 1…...
基于算法竞赛的c++编程(28)结构体的进阶应用
结构体的嵌套与复杂数据组织 在C中,结构体可以嵌套使用,形成更复杂的数据结构。例如,可以通过嵌套结构体描述多层级数据关系: struct Address {string city;string street;int zipCode; };struct Employee {string name;int id;…...
《Playwright:微软的自动化测试工具详解》
Playwright 简介:声明内容来自网络,将内容拼接整理出来的文档 Playwright 是微软开发的自动化测试工具,支持 Chrome、Firefox、Safari 等主流浏览器,提供多语言 API(Python、JavaScript、Java、.NET)。它的特点包括&a…...
服务器硬防的应用场景都有哪些?
服务器硬防是指一种通过硬件设备层面的安全措施来防御服务器系统受到网络攻击的方式,避免服务器受到各种恶意攻击和网络威胁,那么,服务器硬防通常都会应用在哪些场景当中呢? 硬防服务器中一般会配备入侵检测系统和预防系统&#x…...

学校招生小程序源码介绍
基于ThinkPHPFastAdminUniApp开发的学校招生小程序源码,专为学校招生场景量身打造,功能实用且操作便捷。 从技术架构来看,ThinkPHP提供稳定可靠的后台服务,FastAdmin加速开发流程,UniApp则保障小程序在多端有良好的兼…...

Springcloud:Eureka 高可用集群搭建实战(服务注册与发现的底层原理与避坑指南)
引言:为什么 Eureka 依然是存量系统的核心? 尽管 Nacos 等新注册中心崛起,但金融、电力等保守行业仍有大量系统运行在 Eureka 上。理解其高可用设计与自我保护机制,是保障分布式系统稳定的必修课。本文将手把手带你搭建生产级 Eur…...
vue3 定时器-定义全局方法 vue+ts
1.创建ts文件 路径:src/utils/timer.ts 完整代码: import { onUnmounted } from vuetype TimerCallback (...args: any[]) > voidexport function useGlobalTimer() {const timers: Map<number, NodeJS.Timeout> new Map()// 创建定时器con…...

C# 类和继承(抽象类)
抽象类 抽象类是指设计为被继承的类。抽象类只能被用作其他类的基类。 不能创建抽象类的实例。抽象类使用abstract修饰符声明。 抽象类可以包含抽象成员或普通的非抽象成员。抽象类的成员可以是抽象成员和普通带 实现的成员的任意组合。抽象类自己可以派生自另一个抽象类。例…...

html-<abbr> 缩写或首字母缩略词
定义与作用 <abbr> 标签用于表示缩写或首字母缩略词,它可以帮助用户更好地理解缩写的含义,尤其是对于那些不熟悉该缩写的用户。 title 属性的内容提供了缩写的详细说明。当用户将鼠标悬停在缩写上时,会显示一个提示框。 示例&#x…...
Typeerror: cannot read properties of undefined (reading ‘XXX‘)
最近需要在离线机器上运行软件,所以得把软件用docker打包起来,大部分功能都没问题,出了一个奇怪的事情。同样的代码,在本机上用vscode可以运行起来,但是打包之后在docker里出现了问题。使用的是dialog组件,…...

学校时钟系统,标准考场时钟系统,AI亮相2025高考,赛思时钟系统为教育公平筑起“精准防线”
2025年#高考 将在近日拉开帷幕,#AI 监考一度冲上热搜。当AI深度融入高考,#时间同步 不再是辅助功能,而是决定AI监考系统成败的“生命线”。 AI亮相2025高考,40种异常行为0.5秒精准识别 2025年高考即将拉开帷幕,江西、…...