【计算机网络】Linux环境中的TCP网络编程
文章目录
- 前言
- 一、TCP Socket API
- 1. socket
- 2. bind
- 3. listen
- 4. accept
- 5. connect
- 二、封装TCPSocket
- 三、服务端的实现
- 1. 封装TCP通用服务器
- 2. 封装任务对象
- 3. 实现转换功能的服务器
- 四、客户端的实现
- 1. 封装TCP通用客户端
- 2. 实现转换功能的客户端
- 五、结果演示
- 六、多进程版服务器
- 七、线程池版服务器
前言
TCP和UDP都是工作在传输层,用于程序之间传输数据。二者之间的区别是TCP是面向连接的,而UDP是面向数据报的。那就意味着,TCP能够进行可靠的数据传输,而UDP进行不可靠的数据传输。关于TCP协议和UDP协议的详细内容可见博主的后续文章,本文的主要内容是关于TCP socket的网络编程。
接下来我们将基于TCP网络编程实现一个将小写字母转换成大写字母的网络服务器。
一、TCP Socket API
以下是关于使用TCP协议用到的socket API,这些函数都包含在头文件sys/socket.h
中。
1. socket
函数定义:
NAME//socket - create an endpoint for communication
SYNOPSIS#include <sys/socket.h>int socket(int domain, int type, int protocol);
功能:
socket()
会打开一个网络通信端口,如果打开成功,则像open()
函数一样返回一个文件描述符,如果失败则返回 -1。这样网络应用程序就可以像读写文件那样使用read/write
在网络上读取和发送数据。
参数详解:
-
第一个参数
domain
用于设置网络通信的域,函数socket()
根据这个参数选择通信协议的族。对于IPv4,domain
参数指定为AF_INET
,而IPv6则是AF_INET6
。并且AF_INET
和PFINET
的值是一致的。 -
第二个参数
type
用于设置通信协议的族,这些族也在文件sys/socket.h
中定义,包含如下表所示的值。
类型 | 说明 |
---|---|
SOCK_STREAM | 用于TCP连接,提供序列化、可靠的、双向连接的字节流 |
SOCK_DGRAM | 用于UDP连接(无连接状态的消息) |
SOCK_SEQPACKET | 序列化包,提供一个序列化的、可靠的、双向的基于连接的数据传输通道,数据长度定长。每次调用读系统调用时数据需要将全部数据读出 |
SOCK_RAW | RAW类型,提供原始网络协议访问 |
SOCK_RDM | 提供可靠的数据报文,不过可能数据会有乱序 |
SOCK_PACKET | 这是一个专用类型,不能在通用程序中使用,用于直接从设备驱动接收数据 |
【补充说明】
- 类型为
SOCK_STREAM
的套接字表示一个双向的字节流,与管道类似。流式的套接字在进行数据收发之前必须已经连接,连接使用connet()
函数进行。一旦连接,可以使用read/write
函数进行数据的传输。流式通信方式保证数据不会丢失或者重复接收,当数据在一段时间内仍然没有接收完毕,可以将这个连接认为已经断开。SOCK_DGRAM
和SOCK_RAW
这两种套接字可以使用函数sendto()
来发送数据,使用recvfrom()
函数接收数据,recvfrom()
接收来自指定IP地址的发送方的数据。
- 第三个参数
protocol
用于指定某个协议的特定类型,即type类型中的某个类型。通常某个协议中只有一种特定类型,这样protocol
参数仅能设置为0,但是有些协议有多种类型,就需要设置这个参数来选择特定的类型。
2. bind
函数定义:
NAME//bind - bind a name to a socketSYNOPSIS#include <sys/socket.h>int bind(int socket, const struct sockaddr *address, socklen_t address_len);
因为服务器程序所监听的网络地址和端口号通常都是固定不变的,客户端得知了服务器程序的地址和端口号后就可以向服务器发起连接,而服务器需要绑定一个固定的网络地址和端口号。因此bind()
的作用是将参数sockfd
和myaddr
绑定在一起,使sockfd
这个用于网络通讯的文件描述符监听sockaddr
所描述的地址和端口号。绑定成功返回0,失败则返回-1。
博主的上一篇文章【网络套接字编程】中提到过,struct sockaddr *
是一个通用指针类型,myaddr
参数实际上可以接受多种协议的sockaddr
结构体,而它们的长度各不相同,所以需要第三个参数addrlen指定结构体的长度。
在程序中myaddr
的定义及初始化如下:
struct sockaddr_in myaddr;
bzero(&myaddr, sizeof(myaddr));
myaddr.sin_family = AF_INET;
myaddr.sin_addr.s_addr = htonl(INADDR_ANY);
myaddr.sin_port = htons(SERV_PORT);
- 定义
myaddr
- 使用
bzero
函数将整个结构体清零 - 设置网络通信的域为
AF_INET
- 将网络地址设置为
INADDR_ANY
,这个宏表示本地的任意IP地址,因为服务器可能有多个网卡,每个网卡也可能绑定多个IP 地址,这样设置可以在所有的IP地址上监听,直到与某个客户端建立了连接时才确定下来到底用哪个IP 地址 - 最后填充端口号
虽然bind()
中的第二个参数类型是sockaddr
,但是我们真正填充信息使用的数据结构是sockaddr_in
,这个结构里主要有三部分信息:地址类型、端口号、IP地址。最后在进行函数传参的时候只需要将sockaddr_in*
强制类型转换成sockaddr
即可。
3. listen
函数定义:
NAME//listen - listen for socket connections and limit the queue of incoming connectionsSYNOPSIS#include <sys/socket.h>int listen(int socket, int backlog);
listen()
函数用于声明sockfd
处于监听状态,并且最多允许有backlog
个客户端处于连接等待状态,如果接收到更多的连接请求就忽略。(详细内容可见博主的后续文章【TCP协议】)。listen()
函数调用成功返回0,调用失败则返回 -1。
4. accept
函数定义:
NAMEaccept - accept a new connection on a socketSYNOPSIS#include <sys/socket.h>int accept(int socket, struct sockaddr *restrict_address, socklen_t *restrict_address_len);
accept()
函数的作用是,当客户端与服务端的三次握手完成后,服务器调用accept()
函数接受连接。如果服务器调用accept()
时还没有客户端的连接请求,就阻塞等待直到有客户端请求连接。
返回值:
- 调用成功则返回客户端
socket()
返回的文件描述符,调用失败则返回 -1。
参数:
- 第一个参数
socket
即是调用socket()
函数返回的文件描述符。 - 第二个参数
restrict_address
是输出型参数,用于获取客户端的网络地址和端口号,如果该参数为空,则表示当前服务端不关心客户端的地址。 - 第三个参数
restrict_address_len
也是输出型参数,它表示的是缓冲区restrict_address
的长度,以避免缓冲区溢出问题,最后传出客户端地址结构体的实际长度。
accept()
函数在服务器程序中的使用结构如下:
while (true)
{sockaddr_in peer_addr;socklen_t len = sizeof(peer_addr);int peer_sock = accept(_fd, (sockaddr *)&peer_addr, &len);ssize_t read_size = read(peer_sock, buf, sizeof(buf));. . .close(peer_sock);
}
5. connect
函数定义:
NAME//connect - connect a socketSYNOPSIS#include <sys/socket.h>int connect(int socket, const struct sockaddr *address, socklen_t address_len);
作用与参数说明:
connect
函数用于客户端连接服务器。其参数与bind()
函数的参数一致,区别在于bind()
函数绑定的参数是自己的地址,而connect()
函数的连接是服务器的地址。
返回值:
- 调用成功返回0,调用失败则返回 -1。
二、封装TCPSocket
#pragma once#include <iostream>
#include <string>
#include <cstring>
#include <cassert>#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>#define CHECK_RET(exp) \if (!(exp)) \{ \return false; \}class TcpSocket
{
public:TcpSocket() : _fd(-1) {}~TcpSocket() {}public:bool Socket(){_fd = socket(AF_INET, SOCK_STREAM, 0); // AF_INET表示采用IPv4, SOCK_STREAM表示采用tcp协议if (_fd < 0){std::cerr << "create socket error!" << std::endl;return false;}return true;}bool Close(){close(_fd);return true;}bool Bind(const std::string &ip, uint16_t port){sockaddr_in addr;// 填充addr信息addr.sin_family = AF_INET;addr.sin_port = htons(port);// addr.sin_addr.s_addr = ip.empty() ? htonl(INADDR_ANY) : inet_addr(ip.c_str());ip.empty() ? (addr.sin_addr.s_addr = INADDR_ANY) : inet_aton(ip.c_str(), &addr.sin_addr);if (bind(_fd, (const sockaddr *)&addr, sizeof(addr)) < 0){std::cerr << "bind error!" << std::endl;return false;}return true;}bool Listen(int num){if (listen(_fd, num) < 0){std::cerr << "listen error!" << std::endl;return false;}return true;}bool Accept(TcpSocket *peer, std::string *ip = nullptr, std::uint16_t *port = nullptr){sockaddr_in peer_addr;socklen_t len = sizeof(peer_addr);int peer_sock = accept(_fd, (sockaddr *)&peer_addr, &len);if (peer_sock < 0){std::cerr << "accept error!" << std::endl;return false;}peer->_fd = peer_sock;if (ip != nullptr){*ip = inet_ntoa(peer_addr.sin_addr);}if (port != nullptr){*port = ntohs(peer_addr.sin_port);}return true;}bool Recv(std::string *buf){buf->clear();char inbuf[1024 * 10] = {0};ssize_t read_size = recv(_fd, inbuf, sizeof(inbuf), 0);if (read_size < 0){std::cerr << "recv error!" << std::endl;return false;}if (read_size == 0){return false;}buf->assign(inbuf, read_size);return true;}bool Send(const std::string &buf){ssize_t write_size = send(_fd, buf.c_str(), buf.size(), 0);if (write_size < 0){std::cerr << "send error!" << std::endl;return false;}return true;}bool Connect(const std::string &ip, uint16_t port){sockaddr_in addr;// 填充addr信息addr.sin_family = AF_INET;addr.sin_port = htons(port);addr.sin_addr.s_addr = ip.empty() ? htonl(INADDR_ANY) : inet_addr(ip.c_str());if (connect(_fd, (sockaddr *)&addr, sizeof(addr)) < 0){std::cerr << "connect error!" << std::endl;return false;}return true;}int GetFd(){return _fd;}private:int _fd;
};
三、服务端的实现
1. 封装TCP通用服务器
#include "TcpSocket.hpp"
#include "Task.hpp"typedef std::function<void(TcpSocket, const std::string &, uint16_t)> Handler;// void transServer(TcpSocket sock, const std::string &ip, uint16_t port)
// {
// std::string inbuf;// while (true)
// {
// if (!sock.Recv(&inbuf))
// {
// // 如果读取失败,结束循环
// printf("[client %s:%d] disconnect!\n", ip.c_str(), port);
// break;
// }// if (strcasecmp(inbuf.c_str(), "quit") == 0)
// {
// printf("[client %s:%d] quit!\n", ip.c_str(), port);
// break;
// }// printf("transform before: %s[%d]--> %s\n", ip.c_str(), port, inbuf.c_str());
// fflush(stdout);// for (int i = 0; i < inbuf.size(); ++i)
// {
// if (isalpha(inbuf[i]) && islower(inbuf[i]))
// {
// inbuf[i] = toupper(inbuf[i]);
// }
// }// printf("transform after: %s[%d]--> %s\n", ip.c_str(), port, inbuf.c_str());
// fflush(stdout);// sock.Send(inbuf);
// }
// sock.Close();
// }class TcpServer
{
public:TcpServer(int port, const std::string &ip = ""): _port(port), _ip(ip){_sock.Socket();}~TcpServer() {}public:bool Start(Handler handler){// 绑定IP和端口号CHECK_RET(_sock.Bind(_ip, _port));// 监听CHECK_RET(_sock.Listen(5));// 进入循环while (true){// 进行acceptTcpSocket peer_sock;std::string ip;uint16_t port = 0;if (!_sock.Accept(&peer_sock, &ip, &port)){continue;}printf("[client %s:%d] connect!\n", ip.c_str(), port);// 执行任务// TODOTask task(peer_sock, ip, port, handler);task();}}private:// tcp socket对象TcpSocket _sock;// 端口号uint16_t _port;// ip地址std::string _ip;
};
2. 封装任务对象
#include <iostream>
#include <string>
#include <functional>
#include <pthread.h>#include "TcpSocket.hpp"class Task
{typedef std::function<void(TcpSocket, const std::string &, uint16_t)> callback_t;public:Task(TcpSocket sock, const std::string &ip, uint16_t port, callback_t func):_sock(sock), _ip(ip), _port(port), _func(func) {}~Task() {}void operator()(){printf("线程ID[%p]处理client[%s:%d]的请求开始了...\n", pthread_self(), _ip.c_str(), _port);fflush(stdout);_func(_sock, _ip, _port);printf("线程ID[%p]处理client[%s:%d]的请求结束了...\n", pthread_self(), _ip.c_str(), _port);fflush(stdout);}
private:TcpSocket _sock;std::string _ip;uint16_t _port;callback_t _func; // 处理任务的回调函数
};
3. 实现转换功能的服务器
#include <iostream>
#include "TcpSocket.hpp"
#include "TcpServer.hpp"void transServer(TcpSocket sock, const std::string &ip, uint16_t port)
{std::string inbuf;while (true){if (!sock.Recv(&inbuf)){// 如果读取失败,结束循环printf("[client %s:%d] disconnect!\n", ip.c_str(), port);break;}if (strcasecmp(inbuf.c_str(), "quit") == 0){printf("[client %s:%d] quit!\n", ip.c_str(), port);break;}printf("transform before: %s[%d]--> %s\n", ip.c_str(), port, inbuf.c_str());fflush(stdout);for (int i = 0; i < inbuf.size(); ++i){if (isalpha(inbuf[i]) && islower(inbuf[i])){inbuf[i] = toupper(inbuf[i]);}}printf("transform after: %s[%d]--> %s\n", ip.c_str(), port, inbuf.c_str());fflush(stdout);sock.Send(inbuf);}sock.Close();
}static void Usage(std::string proc)
{std::cout << "Usage:\n\t" << proc << " ip port" << std::endl;std::cout << "example:\n\t" << proc << " 127.0.0.1 8080\n"<< std::endl;
}int main(int argc, char *argv[])
{if (argc != 2 && argc != 3){Usage(argv[0]);return 1;}std::string ip;if (argc == 3){ip = argv[1];}uint16_t port = atoi(argv[2]);TcpServer server(port, ip);server.Start(transServer);return 0;
}
四、客户端的实现
1. 封装TCP通用客户端
#include "TcpSocket.hpp"class TcpClient
{
public:TcpClient(const std::string& ip, uint16_t port):_ip(ip), _port(port) {_sock.Socket();}~TcpClient(){_sock.Close();}
public:bool Connect(){return _sock.Connect(_ip, _port);}bool Recv(std::string* buf){return _sock.Recv(buf);}bool Send(const std::string& buf){_sock.Send(buf);}int GetFd(){return _sock.GetFd();}
private:TcpSocket _sock;std::string _ip;uint16_t _port;
};
2. 实现转换功能的客户端
#include <iostream>
#include "TcpClient.hpp"volatile bool quit = false;static void Usage(std::string proc)
{std::cerr << "Usage:\n\t" << proc << " serverIp serverPort" << std::endl;std::cerr << "Example:\n\t" << proc << " 127.0.0.1 8080\n"<< std::endl;
}
// ./clientTcp serverIp serverPort
int main(int argc, char *argv[])
{if (argc != 3){Usage(argv[0]);return 1;}std::string serverIp = argv[1];uint16_t serverPort = atoi(argv[2]);TcpClient client(serverIp, serverPort);// 建立连接if (!client.Connect()){std::cout << "connecte errer!" << std::endl;return 2;}std::cout << "connecte success! fd: " << client.GetFd() << std::endl;std::string message;while (!quit){message.clear();std::cout << "请输入您的内容# ";std::getline(std::cin, message);if (strcasecmp(message.c_str(), "quit") == 0){quit = true;}if (client.Send(message)){message.resize(message.size());if (client.Recv(&message)){std::cout << "Server Echo ---> " << message << std::endl;}}else{break;}}return 0;
}
五、结果演示
启动服务端:
启动客户端:
客户端连接服务端成功,此时服务端状态:
测试样例:
客户端输入样例,发现转换成功。
存在的问题:
此时,启动又一个客户端连接服务器,在此输入时,我们会发现输入的内容会卡住显示屏上。
此时我们关闭第一个客户端后发现有得出转换后的结果。
原因在于当前的服务器是单进程版本的,只能够同时为一个客户端服务,所以再来一个客户端会阻塞等待服务器结束对上一个客户端的服务。以下的改进的多进程和多线程版本的服务器。
六、多进程版服务器
改进思想是,父进程为每个客户端的请求都创建一个子进程去处理任务,父进程不做任何工作,但要注意的是父进程中要关闭不断创建的客户端的peer_sock
,避免内存泄漏。子进程执行客户端的请求,在请求结束后调用exit
函数退出,但不必单独释放_sock
对象,因为会自动调用其析构函数。同时设置signal函数,对SIGCHLD
做忽略动作,使得父进程不必等待子进程,只处理自己的任务。
#pragma once#include "TcpSocket.hpp"
#include "Task.hpp"
#include <cassert>
#include <signal.h>
#include <unistd.h>typedef std::function<void(TcpSocket, const std::string &, uint16_t)> Handler;class TcpProcessServer
{
public:TcpProcessServer(int port, const std::string &ip = ""): _port(port), _ip(ip){_sock.Socket();signal(SIGCHLD, SIG_IGN);}~TcpProcessServer() {}public:bool Start(Handler handler){// 绑定IP和端口号CHECK_RET(_sock.Bind(_ip, _port));// 监听CHECK_RET(_sock.Listen(5));// 进入循环while (true){// 进行acceptTcpSocket peer_sock;std::string ip;uint16_t port = 0;if (!_sock.Accept(&peer_sock, &ip, &port)){continue;}printf("[client %s:%d] connect!\n", ip.c_str(), port);// 执行任务// 多进程 v1.0pid_t id = fork();if (id > 0){// 父进程,不需要做什么peer_sock.Close(); // 父进程中需要关闭}else if (id == 0){// 子进程// 子进程的 socket 的关闭在析构函数中进行Task task(peer_sock, ip, port, handler);task();// 处理任务结束,退出子进程exit(0);}else{// fork失败std::cerr << "fork error!" << std::endl;return false;}}return true;}private:// tcp socket对象TcpSocket _sock;// 端口号uint16_t _port;// ip地址std::string _ip;
};
结果:
此时服务器便可以为多个客户端同时进行服务。
七、线程池版服务器
实现线程池:
#pragma once#include <iostream>
#include <queue>
#include <cassert>
#include <pthread.h>const uint32_t gDefaultThreadNum = 5; // 默认线程池中线程数量template <class T>
class ThreadPool
{
public:ThreadPool(uint32_t threadNum = gDefaultThreadNum): _isStart(false), _ThreadNum(threadNum){pthread_mutex_init(&_mutex, nullptr);pthread_cond_init(&_cond, nullptr);}~ThreadPool(){pthread_mutex_destroy(&_mutex);pthread_cond_destroy(&_cond);}public:// 启动线程池void start(){// 判断线程池是否启动assert(!_isStart); // 如果已经启动则失败for (int i = 0; i < _ThreadNum; ++i){pthread_t tid;pthread_create(&tid, nullptr, threadRoutine, this);}// 线程池已经启动_isStart = true;}// 放入任务void push(const T &in){lockQueue();_taskQueue.push(in);handleTask();unlockQueue();}// 消费任务T pop(){T task = _taskQueue.front();_taskQueue.pop();return task;}private:static void *threadRoutine(void *args){ThreadPool<T> *ptp = static_cast<ThreadPool<T> *>(args);while (true){ptp->lockQueue();// 判断当前任务队列中有没有任务while (!ptp->hasTask()){// 没有任务,进行循环等待ptp->waitTask();}// 当前线程获取任务T t = ptp->pop();ptp->unlockQueue();// 当前线程处理任务t();}}void lockQueue(){pthread_mutex_lock(&_mutex);}void unlockQueue(){pthread_mutex_unlock(&_mutex);}void waitTask(){pthread_cond_wait(&_cond, &_mutex);}void handleTask(){pthread_cond_signal(&_cond);}bool hasTask(){return !_taskQueue.empty();}private:bool _isStart; // 判断线程池是否开启uint32_t _ThreadNum; // 线程池中的线程数量std::queue<T> _taskQueue; // 任务队列pthread_mutex_t _mutex; // 保护任务队列的锁pthread_cond_t _cond; // 线程池的条件变量
};
线程池版服务器:
#pragma once#include "TcpSocket.hpp"
#include "Task.hpp"
#include "ThreadPool.hpp"typedef std::function<void(TcpSocket, const std::string &, uint16_t)> Handler;class TcpThreadPoolServer
{
public:TcpThreadPoolServer(int port, const std::string &ip = ""): _port(port), _ip(ip){_sock.Socket();_tp.start();}~TcpThreadPoolServer() {}public:bool Start(Handler handler){// 绑定IP和端口号CHECK_RET(_sock.Bind(_ip, _port));// 监听CHECK_RET(_sock.Listen(5));// 进入循环while (true){// 进行acceptTcpSocket peer_sock;std::string ip;uint16_t port = 0;if (!_sock.Accept(&peer_sock, &ip, &port)){continue;}printf("[client %s:%d] connect!\n", ip.c_str(), port);// 执行任务// TODOTask task(peer_sock, ip, port, handler);_tp.push(task);}}private:// tcp socket对象TcpSocket _sock;// 端口号uint16_t _port;// ip地址std::string _ip;// 线程池ThreadPool<Task> _tp;
};
结果
相关文章:

【计算机网络】Linux环境中的TCP网络编程
文章目录前言一、TCP Socket API1. socket2. bind3. listen4. accept5. connect二、封装TCPSocket三、服务端的实现1. 封装TCP通用服务器2. 封装任务对象3. 实现转换功能的服务器四、客户端的实现1. 封装TCP通用客户端2. 实现转换功能的客户端五、结果演示六、多进程版服务器七…...

idekCTF 2022 比赛复现
Readme 首先 []byte 是 go 语言里面的一个索引,比如: package mainimport "fmt"func main() {var str string "hello"var randomData []byte []byte(str)fmt.Println(randomData[0:]) //[104 101 108 108 111] }上面这串代码会从…...
jvm的类加载过程
加载 通过一个类的全限定名获取定义此类的二进制字节流将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口链接 验证 验证内容的合法性准备 把方法区的静态变量初…...

VOC数据增强与调整大小
数据增强是针对数据集图像数量太少所采取的一种方法。 博主在实验过程中,使用自己的数据集时发现其数据量过少,只有280张,因此便想到使用数据增强的方式来获取更多的图像信息。对于图像数据,我们可以采用旋转等操作来获取更多的图…...

Linux 安装jenkins和jdk11
Linux 安装jenkins和jdk111. Install Jdk112. Jenkins Install2.1 Install Jenkins2.2 Start2.3 Error3.Awakening1.1 Big Data -- Postgres4. Awakening1. Install Jdk11 安装jdk11 sudo yum install fontconfig java-11-openjdk 2. Jenkins Install 2.1 Install Jenkins 下…...

Pandas——Series操作【建议收藏】
pandas——Series操作 作者:AOAIYI 创作不易,觉得文章不错或能帮助到你学习,可以点赞收藏评论哦 文章目录pandas——Series操作一、实验目的二、实验原理三、实验环境四、实验内容五、实验步骤1.创建Series2.从具体位置的Series中访问数据3.使…...

JUC并发编程Ⅰ -- Java中的线程
文章目录线程与进程并行与并发进程与线程应用应用之异步调用应用之提高效率线程的创建方法一:通过继承Thread类创建方法二:使用Runnable配合Thread方法三:使用FutureTask与Thread结合创建查看进程和线程的方法线程运行的原理栈与栈帧线程上下…...

基于vue-admin-element开发后台管理系统【技术点整理】
一、Vue点击跳转外部链接 点击重新打开一个页面窗口,不覆盖当前的页面 window.open(https://www.baidu.com,"_blank")"_blank" 新打开一个窗口"_self" 覆盖当前的窗口例如:导入用户模板下载 templateDownload() {wi…...

【C语言学习笔记】:通讯录管理系统
系统中需要实现的功能如下: ✿ 添加联系人:向通讯录中添加新人,信息包括(姓名、性别、年龄、联系电话、家庭住址)最多记录1000人 ✿ 显示联系人:显示通讯录中所有的联系人信息 ✿ 删除联系人:按…...

开关电源环路稳定性分析(10)——OPA和OTA型补偿器传递函数
大家好,这里是大话硬件。 在前面9讲的内容中将开关电源环路分析进行了梳理,我相信很多人即使都看完了,应该还是不会设计,而且还存在几个疑问。比如我随便举几个: 开关电源的带宽怎么设定?开关电源精度和什…...
2.11知识点整理(关于pycharm,python,pytorch,conda)
pycharm 设置anaconda环境: File -> Settings->选择左侧的project xxx再选择打开Project Interpreter页->选择add添加解释器->添加Anaconda中Python解释器(Anaconda安装目录下的python.exe) (选择existing environment ÿ…...

Linux服务器开发-2. Linux多进程开发
文章目录1. 进程概述1.1 程序概览1.2 进程概念1.3 单道、多道程序设计1.4 时间片1.5 并行与并发1.6 进程控制块(PCB)2. 进程的状态转换2.1 进程的状态2.2 进程相关命令查看进程实时显示进程动态杀死进程进程号和相关函数3. 进程的创建-fork函数3.1 进程创…...

Excel中缺失数据值的自动填充
目录简单方法示例1:数据满足线性趋势示例2:数据满足增长(指数)趋势参考实验做完处理数据,发现有一组数据因为设备中途出现问题缺失了,之前做过的数据也找不到,为了不影响后续处理,这里使用Excel插入缺失值。…...

路由器刷固件
前言 我希望可以远程访问我的电脑。但,我不希望电脑总是处于运行状态,因为那样比较费电。所以需要一个方案,能将睡眠/关机中的电脑唤醒。 方案一:选用智能插座,远程给电脑上电。电脑设置上电自启。但,这存…...
leetcode: Two Sum II - Input Array is Sorted
leetcode: Two Sum II - Input Array is Sorted1. 题目2. 解答3. 总结1. 题目 Given a 1-indexed array of integers numbers that is already sorted in non-decreasing order, find two numbers such that they add up to a specific target number. Let these two number…...

STL——list
一、list介绍及使用 1. list文档介绍 (1)list是可以在常数范围内,在任意位置进行插入、删除的序列式容器,并且该容器可以前后双向迭代。 (2)list的底层是带头结点的双向循环链表,其中每个元素…...

实战打靶集锦-004-My-Cmsms
**写在前面:**记录一次艰难曲折的打靶经历。 目录1. 主机发现2. 端口扫描3. 服务枚举4. 服务探查4.1 WEB服务探查4.1.1 浏览器访问4.1.2 目录枚举4.1.3 控制台探查4.1.4 其他目录探查4.2 阶段小结5. 公共EXP搜索5.1 CMS搜索5.2 Apache搜索5.3 PHP搜索5.4 MySQL搜索5…...
c++代码实现我的世界(14)
c代码实现我的世界14|生成地貌兼工作台1前言的前言~前言生成地貌函数结构体struct dimao根据比例生成地貌工作台函数准备的东西写在最后前言的前言~ 实在对不起大家,有挺长时间没更新了。 前言 今天我们将写生成地形的函数与工作台前传的代码; 注&…...

RMQ--区间最值问题(在更)
RMQ(Range Minimum/Maximum Query)RMQ解决的问题ST算法 O(nlogn)线段树例题数列区间最大值最敏捷的机器人天才的记忆Frequent values总结(ST和线段树对比)RMQ解决的问题 RMQ是一个解决多个区间最值查询的算法,即区间最值查询&…...

一篇文章搞懂Cookie
目录 1 什么是Cookie 2 创建Cookie 3 浏览器查看Cookie 3.1 浏览器查看Cookie的第一种方式 3.2 浏览器查看Cookie的第二种方式 4 获取Cookie 5 修改Cookie 6 Cookie编码与解码 6.1 创建带中文Cookie 6.2 读取带中文Cookie 6.3 获取中文Cookie请求效果 6.4 解决创建和…...
web vue 项目 Docker化部署
Web 项目 Docker 化部署详细教程 目录 Web 项目 Docker 化部署概述Dockerfile 详解 构建阶段生产阶段 构建和运行 Docker 镜像 1. Web 项目 Docker 化部署概述 Docker 化部署的主要步骤分为以下几个阶段: 构建阶段(Build Stage):…...

Linux 文件类型,目录与路径,文件与目录管理
文件类型 后面的字符表示文件类型标志 普通文件:-(纯文本文件,二进制文件,数据格式文件) 如文本文件、图片、程序文件等。 目录文件:d(directory) 用来存放其他文件或子目录。 设备…...
Spring Boot 实现流式响应(兼容 2.7.x)
在实际开发中,我们可能会遇到一些流式数据处理的场景,比如接收来自上游接口的 Server-Sent Events(SSE) 或 流式 JSON 内容,并将其原样中转给前端页面或客户端。这种情况下,传统的 RestTemplate 缓存机制会…...

阿里云ACP云计算备考笔记 (5)——弹性伸缩
目录 第一章 概述 第二章 弹性伸缩简介 1、弹性伸缩 2、垂直伸缩 3、优势 4、应用场景 ① 无规律的业务量波动 ② 有规律的业务量波动 ③ 无明显业务量波动 ④ 混合型业务 ⑤ 消息通知 ⑥ 生命周期挂钩 ⑦ 自定义方式 ⑧ 滚的升级 5、使用限制 第三章 主要定义 …...

遍历 Map 类型集合的方法汇总
1 方法一 先用方法 keySet() 获取集合中的所有键。再通过 gey(key) 方法用对应键获取值 import java.util.HashMap; import java.util.Set;public class Test {public static void main(String[] args) {HashMap hashMap new HashMap();hashMap.put("语文",99);has…...

CentOS下的分布式内存计算Spark环境部署
一、Spark 核心架构与应用场景 1.1 分布式计算引擎的核心优势 Spark 是基于内存的分布式计算框架,相比 MapReduce 具有以下核心优势: 内存计算:数据可常驻内存,迭代计算性能提升 10-100 倍(文档段落:3-79…...

Python爬虫(一):爬虫伪装
一、网站防爬机制概述 在当今互联网环境中,具有一定规模或盈利性质的网站几乎都实施了各种防爬措施。这些措施主要分为两大类: 身份验证机制:直接将未经授权的爬虫阻挡在外反爬技术体系:通过各种技术手段增加爬虫获取数据的难度…...

网站指纹识别
网站指纹识别 网站的最基本组成:服务器(操作系统)、中间件(web容器)、脚本语言、数据厍 为什么要了解这些?举个例子:发现了一个文件读取漏洞,我们需要读/etc/passwd,如…...

如何更改默认 Crontab 编辑器 ?
在 Linux 领域中,crontab 是您可能经常遇到的一个术语。这个实用程序在类 unix 操作系统上可用,用于调度在预定义时间和间隔自动执行的任务。这对管理员和高级用户非常有益,允许他们自动执行各种系统任务。 编辑 Crontab 文件通常使用文本编…...
Python+ZeroMQ实战:智能车辆状态监控与模拟模式自动切换
目录 关键点 技术实现1 技术实现2 摘要: 本文将介绍如何利用Python和ZeroMQ消息队列构建一个智能车辆状态监控系统。系统能够根据时间策略自动切换驾驶模式(自动驾驶、人工驾驶、远程驾驶、主动安全),并通过实时消息推送更新车…...