网络套接字补充——TCP网络编程

六、TCP网络编程

6.1IP地址字符串和整数之间的转换接口

//字符串转整数接口
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int inet_aton(const char *cp, struct in_addr *inp);
int inet_pton(int af, const char *strptr, void *addrptr);//注意dst指的是in_addr *的地址
in_addr_t inet_addr(const char *cp);//将字符串转32位并且是网络序列的;
//整数转字符串
char *inet_ntoa(struct in_addr in);//将整数转为字符串并且将网络字节序转为主机字节序
const char *inet_ntop(int af, const void *addrptr,char *strptr, socklen_t size);

​ 需要注意的是inet_ntoa函数这个函数返回的是一个静态变量地址,使用时有覆盖问题和线程安全问题;最好是使用inet_ntop;

6.2补充知识

1.将网络套接字进行封装

​ 构造函数之中最好少做一些有风险的事情,这样可以保证最起码对象是没有问题的;其他如打开文件之类的操作就交给其他函数去完成;

2.获取新连接会产生多个文件描述符

​ 服务器本地的文件描述符用来进行监听连接,获取新连接,真正进行IO通信的文件描述符是后生成的;这样既提高了服务器的并发度;

3.telnet的使用

​ 默认使用的就是TCP;

​ 使用ctrl+]进入,回车后进行输入(会自动在输入的文本后面加\r\n)会回显数据,q退出;

4.TCP连接中的异常问题

​ a.当客户端直接退出时,服务端就会读到0,此时需要关闭为客户端打开的文件描述符;

​ b.读端关闭,写端继续写会导致操作系统发送SIGPIPE信号,然后出现杀死服务器的情况,所以一般要将13号信号忽略;
5.网络抖动断开连接,客户端自动发起连接请求设计

​ 长服务不合理,但是可以将客户端设置成循环发起连接;当网络出错会发生读写错误,这时候就可以进行对服务器重连;

int main(int argc, char *argv[])
{if (argc != 3){Usage(argv[0]);return 0;}std::string serverip = argv[1];uint16_t serverport = std::stoi(argv[2]);// 发起连接请求// 设置服务器套接字sockaddr_in server;bzero(&server, sizeof(server));server.sin_family = AF_INET;server.sin_port = htons(serverport);inet_pton(AF_INET, serverip.c_str(), &server.sin_addr);while (true){int cnt = 5;bool isreconnect = false;// 1.创建套接字int sockfd = socket(AF_INET, SOCK_STREAM, 0);if (sockfd < 0){cerr << "sockket" << endl;return 1;}do{// 2.发起连接if (connect(sockfd, (sockaddr *)&server, sizeof(server)) < 0){isreconnect = true;cnt--;cerr << "connect error, reconnect count: " << 5 - cnt << endl;sleep(1);}else if (isreconnect){isreconnect = false;cout << "connect success" << endl;}} while (isreconnect && cnt);if (isreconnect == true){cout << "reconnect fail" << endl;close(sockfd);return 1;}// 3.产生数据并且发送数据string message;cout << "Please Enter@ ";getline(cin, message);if (write(sockfd, message.c_str(), message.size()) < 0){cerr << "write error" << endl;isreconnect = true;continue;}char buff[4096];int n = read(sockfd, buff, sizeof(buff) - 1);if (n > 0){buff[n] = 0;cout << buff << endl;}close(sockfd);}return 0;
}

6.tcp服务器重连

​ 服务器断开后不能直接连接,一般要等待120s左右;

6.3使用接口

6.3.1创建套接字

​ 和udp使用是一样的;

6.3.2绑定套接字

​ 和udp使用是一样的;

6.3.3设置监听

​ 由于TCP是面向连接的,所以在通信前要建立连接,将套接字设置为监听状态;

#include <sys/types.h>         
#include <sys/socket.h>
int listen(int sockfd, int backlog);
//第二个参数表示的是全连接的队列的长度,一般不能设置的太大;
6.3.4获取新连接

​ 此处包括以上接口都是阻塞的;

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
//返回值,成功返回一个文件描述符,失败返回-1,错误码被设置;

​ 获取新连接成功后要根据客户端的套接字信息提供服务;

6.3.5客户端发起连接请求

​ 客户端需要绑定但是不需要显式进行绑定,系统会在客户端发起连接请求的时候自动绑定

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

6.4查看网络状态

netstat -nltp
#n显示成数字,l表示listen,表示tcp

6.5单进程版echo服务器

​ 缺陷是同时最多只能有一个客户端进行访问;UDP所有的客户端用的是一个sockfd,一个文件,可以同时读写,而TCP每个客户端对应一个sockfd,一个文件;单进程下对一个文件读写时,服务器因为处理消息是循环处理,必须读完退出循环服务,才能继续获取新连接,此时另一个客户端已经想打开的sockfd文件写入很多数据,当服务端接收连接请求时,会将发送过来的一大批数据处理后返回,这样就无法实现正常的服务器;

char buff[4096];
while (true)
{// 数据读取ssize_t n = read(sockfd, buff, sizeof(buff) - 1);if (n > 0){buff[n] = '\0';std::cout << "client say@ " << buff << std::endl;// 数据回显std::string echo_string;echo_string += "tcpserver say#";echo_string += buff;write(sockfd, echo_string.c_str(), echo_string.size());}else if (n == 0){lg(Info, "%s:%d quit..., server close sockfd: %d", clientip.c_str(), clientport, sockfd);break;}else{lg(Warning, "read error, sockfd: %d, clientip: %s, clientport: %d", sockfd, clientip.c_str(), clientport);break;}
}

6.6多进程版echo服务器

​ 1.子进程可以看见listenfd_,所以要关闭无关的文件描述符;2.父进程不关心sockfd,要去接收新的连接,如果不关闭就会导致之后的很多文件描述符没有关闭,不断从新的下标打开文件描述符,而不是重新分配;

​ 2.子进程中继续fork(),然后子进程退出,被父进程阻塞等待回收,父进程继续获取新的连接,孙子进程被操作系统领养,执行服务部分;也可以使用信号异步等待的方式实现;

​ 3.也可以在循环执行获取连接和执行任务之前创建子进程,但是会存在数据不一致问题需要用信号量;

​ 4.多进程创建的成本过高,所以应该选择多线程;

//方式1
pid_t id = fork();
if (id < 0)
{std::cerr << "fork error" << std::endl;
}
else if (id == 0)
{close(listensockfd_);if (fork() > 0){exit(0);}// 此处执行代码的是孙子进程,会被做系统领养service(sockfd, clientip, clientport);close(sockfd);exit(0);
}
close(sockfd);
pid_t rid = waitpid(id, nullptr, 0);
(void)rid;
//方式2
signal(SIGCHLD, SIG_IGN);
pid_t id = fork();
if (id == 0)
{close(listensockfd_);service(sockfd, clientip, clientport);close(sockfd);exit(0);
}
close(sockfd);

6.7多线程版本的echo服务器

​ 1.因为线程中大部分资源都是共享的所以不可以关闭文件描述符,否则会出错;

​ 2.线程没有退出时会有峰值的,服务器此时压力很大,所以长服务是不合理的;

​ 3.创建线程也是有成本的,即系统调用的成本,所以应该用线程池;

struct threaddata
{threaddata(const int sockfd, const std::string &clientip, const uint16_t &clientport, tcpserver *t) : sockfd_(sockfd), clientport_(clientport),clientip_(clientip), t_(t){}int sockfd_;uint16_t clientport_;std::string clientip_;tcpserver *t_;
};pthread_t tid;
threaddata *td = new threaddata(sockfd, clientip, clientport, this);
pthread_create(&tid, nullptr, routine, (void *)td);static void *routine(void *args)
{threaddata *td = static_cast<threaddata *>(args);pthread_detach(pthread_self());td->t_->service(td->sockfd_, td->clientip_, td->clientport_);delete td;return nullptr;
}

6.8线程池版本的echo服务器

​ 1.线程池里不可以执行长时间的服务;2.服务器关闭了客户端套接字,客户端继续写入,会触发服务器异常,返回一个RST消息,然后客户端操作系统发送SIGPIPE信号杀死客户端进程;

class Task
{public:Task(const int &sockfd, const std::string &clientip, const uint16_t &clientport): sockfd_(sockfd), clientport_(clientport), clientip_(clientip) {}void run(){char buff[4096];// 数据读取ssize_t n = read(sockfd_, buff, sizeof(buff) - 1);if (n > 0){buff[n] = '\0';std::cout << "client say@ " << buff << std::endl;// 数据回显std::string echo_string;echo_string += "tcpserver say#";echo_string += buff;write(sockfd_, echo_string.c_str(), echo_string.size());}else if (n == 0){lg(Info, "%s:%d quit..., server close sockfd: %d", clientip_.c_str(), clientport_, sockfd_);}else{lg(Warning, "read error, sockfd: %d, clientip: %s, clientport: %d", sockfd_, clientip_.c_str(), clientport_);}close(sockfd_);}void operator()(){run();}~Task(){}private:int sockfd_;uint16_t clientport_;std::string clientip_;
};Task t(sockfd, clientip, clientport);
ThreadPool<Task>::GetInstance()->Push(t);#include <iostream>
#include <vector>
#include <string>
#include <queue>
#include <pthread.h>
#include "Task.hpp"
#include <unistd.h>struct ThreadInfo
{pthread_t tid;std::string name;
};static const int defaultnum = 5;template <class T>class ThreadPool{private:void Lock(){pthread_mutex_lock(&_mutex);}void UnLock(){pthread_mutex_unlock(&_mutex);}void Wakeup(){pthread_cond_signal(&_cond);}void ThreadSleep(){pthread_cond_wait(&_cond, &_mutex);}bool IsQueueEmpty(){return _tasks.empty();}std::string GetThreadName(pthread_t tid){for (const auto e : _threads){if (e.tid == tid){return e.name;}}return "None";}public:T Pop(){T t = _tasks.front();_tasks.pop();return t;}void Push(const T &t){Lock();_tasks.push(t);Wakeup();UnLock();}static void *HandlerTask(void *args) // 类内函数默认都有一个this指针,静态成员函数无法直接看到成员属性{ThreadPool<T> *tp = static_cast<ThreadPool<T> *>(args);std::string name = tp->GetThreadName(pthread_self());while (true){tp->Lock();while (tp->IsQueueEmpty()){tp->ThreadSleep();}Task t = tp->Pop();tp->UnLock();t();}}void Start() // 创建线程{int num = _threads.size();for (int i = 0; i < num; i++){_threads[i].name = "thread-" + std::to_string(i + 1);pthread_create(&(_threads[i].tid), nullptr, HandlerTask, this);}}static ThreadPool<T> *GetInstance(){pthread_mutex_lock(&_smutex);if (_tp == nullptr){std::cout << "log : singleton create done first!" << std::endl;_tp = new ThreadPool<T>();}pthread_mutex_unlock(&_smutex);return _tp;}private:ThreadPool(int num = defaultnum) : _threads(num){pthread_mutex_init(&_mutex, nullptr);pthread_cond_init(&_cond, nullptr);}ThreadPool(const ThreadPool<T> &tp) = delete;const ThreadPool<T> &operator=(const ThreadPool<T> tp) = delete;~ThreadPool(){pthread_mutex_destroy(&_mutex);pthread_cond_destroy(&_cond);}std::vector<ThreadInfo> _threads;std::queue<T> _tasks;pthread_mutex_t _mutex;pthread_cond_t _cond;static ThreadPool<T> *_tp;static pthread_mutex_t _smutex;};
template <class T>ThreadPool<T> *ThreadPool<T>::_tp = nullptr;
template <class T>pthread_mutex_t ThreadPool<T>::_smutex = PTHREAD_MUTEX_INITIALIZER;

6.9线程池版翻译服务器

​ 打开KV式的字符串文件,来比较进行翻译;

#include <iostream>
#include <string>
#include <unordered_map>
#include <fstream>
#include <cstring>
#include "log.hpp"extern Log lg;const std::string filename = "./dict.txt";// 打开字典并且自动将初始化dict
class init
{public:bool split(const std::string &line, std::string &part1, std::string &part2){auto pos = line.find(sep);if (pos == std::string::npos){return false;}part1 = line.substr(0, pos);part2 = line.substr(pos + sep.size());return true;}init(){// 1.打开文件std::ifstream in(filename); // 默认打开文件if (!in.is_open()){lg(Fatal, "open %s file error, errno: %d, strerror: %s", filename.c_str(), errno, strerror(errno));exit(4);}std::string line;// 2.对文件进行按行读取while (std::getline(in, line)){std::string part1, part2;split(line, part1, part2);dict_[part1] = part2;}// 3.关闭文件in.close();}std::string translation(const std::string &key){auto it = dict_.find(key);if (it != dict_.end()){return dict_[key];}else{return "unknown";}}private:std::unordered_map<std::string, std::string> dict_;static const std::string sep;
};
const std::string init::sep = ": ";
// 处理任务
buff[n] = '\0'; // 当作字符串使用
std::string echo_string;
echo_string += it.translation(buff);
write(sockfd_, echo_string.c_str(), echo_string.size());

七、对服务守护进程化

7.1会话session相关概念

​ 在Linux系统中,用户进行登陆的时候,操作系统会为用户形成一个会话(session);为每一个会话创建一个bash进程,来为用户提供命令行的服务,这个bash与键盘和显示器是直接相关的;这个bash默认是前台进程,而一个会话最多只能有一个前台进程,但是可以有多个后台进程

​ 键盘信号只能发送给前台进程,bash对键盘信号做了特殊处理无法直接ctrl+c杀死;

​ 当直接运行程序时形成的进程默认是前台进程,可以被键盘信号直接俄杀死,但是在命令行最后加上&就会形成后台进程,bash默认变为前台进程,而后台进程无法获取键盘信号,会一直执行;

​ 前台进程和后台进程都可以正常运行;但是后台进程运行不影响前台输入命令,这样提高了并发度;前台和后台进程的区别就是只有前台进程才可以获取键盘输入信息;

​ 为了保证总有进程可以键盘输入,所以前台进程是必须一直存在;

在这里插入图片描述

7.2会话相关操作

​ 注意是在当前会话进行操作,其他会话是看不到的;

1.前台进程

./a.out
#前台任务的方式运行程序;

2.后台进程

./a.out &
#后台任务的方式运行程序;

3.fg

fg 任务号
#将任务号对应的后台任务提到前台并唤醒,方便用ctrl+c杀死进程;
#任务号默认从1开始,之后每次创建一个后台程序,任务号就++;

在这里插入图片描述

4.jobs

jobs
#查看当前会话的后台任务;

在这里插入图片描述

5.ctrl+z

ctrl+z
#将前台任务暂停,因为暂停后的组内进程没有意义的,导致没人获取键盘,所以会被放到后台去,bash提到前台

6.bg

bg 任务号
#将后台暂停的任务唤醒

7.3Linux中进程间关系

​ 即进程之间除了独立的关系外还构成组的关系;进程组来负责完成一个任务

​ 操作系统要将会话进行管理,登陆时创建了session的结构体来描述会话的信息;通过sid来标识会话的唯一性,sid为bash进程pid;

​ 单进程的任务是自成一个进程组;多进程的任务,父进程是组长,构成一个进程组;进程组以组长的pid为进程组号;

在这里插入图片描述

​ 当会话关闭时,bash会释放(前台任务会关闭),但是后台任务确实没有释放;由于bash是后台任务的父进程,直接释放会导致执行后台任务的进程组内进程托孤给操作系统,需要注意的是ppid变成了1,与终端没有关系了;其他属性也被保留;这就会导致后台任务收到会话登录和退出的影响;

​ Windows中的用户在退出(注销)时会将会话内的所有的进程全部关闭;

在这里插入图片描述

7.4守护进程化

​ 为了使得进程不被会话登录和退出影响,需要进行守护进程化;即让一个进程自成一个会话,不需要和键盘显示器进行关联,自成进程组自成会话;

​ 守护进程的本质也是孤儿进程;

​ 守护进程不需要使用显示器和键盘文件了;但是也不可以将这些文件关闭,防止后续使用出错 ;最好的方式就是重定向到**/dev/null**这个垃圾桶,所有的内容都会被自动丢弃;这样就实现了远程服务的效果;

守护进程一般以d结尾;

#include <unistd.h>
pid_t setsid(void);
//进程组组长不可以守护进程化,所以需要子进程来执行任务;
//1.自己实现守护进程
void Daemon(const std::string &cwd = "")
{// 1.忽略异常信号signal(SIGPIPE, SIG_IGN);signal(SIGCHLD, SIG_IGN);signal(SIGSTOP, SIG_IGN);// 2.变成独立的会话if (fork() > 0)exit(0);setsid();// 3.更改调用进程的当前工作目录if (!cwd.empty()){chdir(cwd.c_str());}// 4.将标准输入、标准输出、标准错误重定向到/dev/null中,或者写到日志当中去int fd = open("/dev/null", O_RDWR);if (fd > 0){dup2(fd, 0);dup2(fd, 1);dup2(fd, 2);}
}//2.系统调用守护进程
#include <unistd.h>
int daemon(int nochdir, int noclose);
//第一个参数为0表示将进程当前的工作目录改为根目录,第二个参数为.表示不将文件描述符重定向到\dev\null;

八、TCP三次握手和四次挥手

​ TCP协议在建立连接是通过三次握手,而释放连接使用的是四次挥手;建立连接成功之前accept会阻塞,成功之后才会返回;关闭一次文件就是两次挥手,双方都是要关闭文件的所以共挥手四次;

​ TCP和UDP都是全双工通信的;TCP底层创建了两个缓冲区,一个是发送缓冲区,一个是接收缓冲区;即一个文件描述符对应两个文件缓冲区,所以通信时全双工的,读和写是不会互相影响的;UDP没有对应的发送缓冲区;

​ 操作系统要对连接进行管理,创建结构体对象进行描述,然后用链表进行管理;

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

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

相关文章

SWM341系列SDRAM应用

SWM341系列SDRAM应用 1、不同的时钟频率下&#xff0c;SDRAM的初始化参数设置 现象&#xff1a;驱屏应用&#xff0c;显示一段时间后出现卡住的现象 分析&#xff1a;SDRAM的初始 化参数优化 主频150Mhz,建议配置CASL 3&#xff0c;TRFC ≥8。 主频100Mhz,ClkDiv可配置为1…

Windows提权—数据库提权-mysql提权mssql提权Oracle数据库提权

目录 Windows 提权—数据库提权一、mysql提权1.1 udf提权1.1.2 操作方法一 、MSF自动化--UDF提权--漏洞利用1.1.3 操作方法二、 手工导出sqlmap中的dll1.1.4 操作方法三、 moon.php大马利用 1.2 mof提权1.3 启动项提权1.4 反弹shell 二、MSSQL提权MSSQL提权方法1.使用xp_cmdshe…

webGL开发:3D图形学概念大扫盲,恍然大悟。

一、3D图形学及常用概念 3D图形学是研究和开发用于创建、渲染和处理三维图形的学科领域。它涉及到计算机图形学、数学、物理学和计算机科学等多个学科的知识和技术。 在3D图形学中&#xff0c;主要关注的是如何使用计算机生成和呈现逼真的三维图像。这包括创建三维模型、应用材…

C++ | Leetcode C++题解之第1题两数之和

题目&#xff1a; C 题解&#xff1a; class Solution { public:vector<int> twoSum(vector<int>& nums, int target) {unordered_map<int, int> hashtable;for (int i 0; i < nums.size(); i) {auto it hashtable.find(target - nums[i]);if (it …

HarmonyOS NEXT应用开发案例——阻塞事件冒泡

介绍 本示例主要介绍在点击事件中&#xff0c;子组件enabled属性设置为false的时候&#xff0c;如何解决点击子组件模块区域会触发父组件的点击事件问题&#xff1b;以及触摸事件中当子组件触发触摸事件的时候&#xff0c;父组件如果设置触摸事件的话&#xff0c;如何解决父组…

护眼台灯什么牌子好一点,五大热销护眼台灯品牌推荐

台灯已成为每个家庭中不可或缺的照明设备&#xff0c;它的作用不仅限于在夜晚提供充分的光亮&#xff0c;还能迅速营造出适宜的氛围&#xff0c;为用眼提供一个更佳的环境。随着生活品质的提高&#xff0c;人们对台灯的期望也逐步升级&#xff0c;智能化和护眼功能逐渐成为消费…

达梦DMHS-Manager工具日常操作

目录 1、前言 2、同步服务管理 2.1、DMHS Agent节点管理 2.2、DMHS实例节点管理 2.3、DMHS模块节点管理 3、监控及告警 3.1、主机资源监控 3.2、同步链路监控 3.3、告警配置 4、系统管理 4.1、用户管理 4.2、角色管理 4.3、系统配置 4.4、审计信息 5、联机帮助 …

0基础 三个月掌握C语言(16)

⽂件操作 为什么使⽤⽂件&#xff1f; 如果没有⽂件&#xff0c;我们写的程序的数据是存储在电脑的内存中&#xff0c;如果程序退出&#xff0c;内存回收&#xff0c;数据就丢失了&#xff0c;等再次运⾏程序&#xff0c;是看不到上次程序的数据的&#xff0c;如果要将数据…

基于ssm鲸落文化线上体验馆论文

摘 要 现代经济快节奏发展以及不断完善升级的信息化技术&#xff0c;让传统数据信息的管理升级为软件存储&#xff0c;归纳&#xff0c;集中处理数据信息的管理方式。本鲸落文化线上体验馆就是在这样的大环境下诞生&#xff0c;其可以帮助管理者在短时间内处理完毕庞大的数据信…

74HC595芯片工作原理(附使用方法)

一、74HC595脚位图及说明 管脚说明&#xff1a; 14脚&#xff1a;DS&#xff08;SER&#xff09;&#xff0c;串行数据输入引脚 13脚&#xff1a;OE&#xff0c;输出使能控制脚&#xff0c;它是低电才使能输出&#xff0c;所以接GND 12脚&#xff1a;RCK&#xff08;STCP&…

基于SpringBoot+Vue信息化在线教学平台的设计与实现(源码+部署说明+演示视频+源码介绍+lw)

您好&#xff0c;我是码农飞哥&#xff08;wei158556&#xff09;&#xff0c;感谢您阅读本文&#xff0c;欢迎一键三连哦。&#x1f4aa;&#x1f3fb; 1. Python基础专栏&#xff0c;基础知识一网打尽&#xff0c;9.9元买不了吃亏&#xff0c;买不了上当。 Python从入门到精通…

PSA制氧设备装置的使用注意事项解析

PSA制氧设备&#xff0c;即变压吸附制氧设备&#xff0c;是一种利用物理吸附原理&#xff0c;通过特定的吸附剂&#xff0c;在压力变化的情况下&#xff0c;从空气中分离出氧气的设备。由于其高效、节能、环保等特点&#xff0c;PSA制氧设备在工业、能源等领域得到了广泛应用。…

golang语言系列:Scrum、Kanban等敏捷管理策略

云原生学习路线导航页&#xff08;持续更新中&#xff09; 本文是 golang语言系列 文章&#xff0c;主要对编程通用技能 Scrum、Kanban等敏捷管理策略 进行学习 1.什么是敏捷开发 敏捷是一个描述软件开发方法的术语&#xff0c;它强调增量交付、团队协作、持续规划和持续学习。…

宝塔面板 -- 打包前端项目并部署提升访问速度

文章目录 前言一、打包前端项目二、添加PHP项目三、部署打包文件四、开通防火墙五、运行网站总结 前言 在前面写到的文章使用宝塔面板部署前端项目中&#xff0c;并没有将前端项目打包而是直接部署&#xff0c;导致网站访问速度非常慢&#xff0c;加载甚至要十几秒。因此&…

每天五分钟深度学习:神经网络和深度学习有什么样的关系?

本文重点 神经网络是一种模拟人脑神经元连接方式的计算模型&#xff0c;通过大量神经元之间的连接和权重调整&#xff0c;实现对输入数据的处理和分析。而深度学习则是神经网络的一种特殊形式&#xff0c;它通过构建深层次的神经网络结构&#xff0c;实现对复杂数据的深度学习…

校园公共广播网络音频解码终端SV-7102

SV-7102T网络播放解码器 一、描述18123651365微信 SV-7102T是一款壁挂式网络播放终端&#xff0c;具有10/100M以太网接口&#xff0c;配置一路本地扩音线路输入和一路线路输出&#xff0c;可将内部音源输出到外接功放&#xff0c;还提供两路立体声15W的功率输出&#xff0c;可…

ios 之 netty版本swiftNio(socket创建)

SwiftNio 简介 用于高性能协议服务器和客户端的事件驱动、无阻塞的网络应用程序框架。 SwiftNIO是一个跨平台异步事件驱动的网络应用程序框架&#xff0c;用于快速开发可维护的高性能协议服务器和客户端。 这就像Netty&#xff0c;但是为Swift写的。 Xcode引入swiftNio 在实…

如何生成一个指定长度的空数组?

简便写法&#xff1a;使用constructor构造函数进行创建&#xff0c;第一个空数组 [ ] 表示创建一个空数组&#xff0c;然后调用 constructor 属性并传入参数指定数组长度。 [].constructor(17)可用于遍历&#xff0c;例如使用ngFor进行单纯的遍历&#xff0c;参数为遍历次数。

电商API分享:如何批量获取商品详情页数据(属性图价格sku视频评论)

电商API&#xff08;应用程序接口&#xff09;通常提供了丰富的数据获取功能&#xff0c;使开发者能够方便地获取商品详情页的各种数据&#xff0c;包括商品属性、图片、价格、SKU&#xff08;库存量单位&#xff09;、视频以及评论等。以下是一个基本的步骤指南&#xff0c;用…

SSM框架学习——SqlSession以及Spring与MyBatis整合

SqlSession以及Spring与MyBatis整合 准备所需要的JAR包 要实现MyBatis与Spring的整合&#xff0c;很明显需要这两个框架的JAR包&#xff0c;但是只是使用这两个框架中所提供的JAR包是不够的&#xff0c;还需要配合其他包使用&#xff1a; Spring的JAR包MyBatis的JAR包Spring…