当前位置: 首页 > news >正文

Linux--TCP编程--0216 17

观前提示:本篇博文的一些接口需要前几篇博文实现的

  • 线程池的实现

Liunx--线程池的实现--0208 09_Gosolo!的博客-CSDN博客

  • 线程池的单例模式

Linux--线程安全的单例模式--自旋锁--0211_Gosolo!的博客-CSDN博客


 1.TCP编程需要用的接口

创建 socket 文件描述符

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

type 给成  SOCK_STREAM 表示是流式套接

listensock=socket(AF_INET,SOCK_STREAM,0);

开始监听socket

int listen(int socket, int backlog);

TCP是面向连接的,listen其实也是一个套接字,不过他的用途在于建立连接,而不真正提供服务。类似拉人的,提供服务的是服务员。

接收请求

int accept(int socket, struct sockaddr* address,socklen_t* address_len);

相当于拉客的和服务员进行了交接,返回值是真正提供服务的套接字(fd)

 建立连接

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

在正式通信之前,需要先建立连接。客户端需要连接到连接。

 2.TCP编程的框架

 2.1 服务端 TcpServer的框架  tcp_server.hpp

class TcpServer
{
private:const static int gbacklog = 20;  //后面再说public:TcpServer(uint16_t port, std::string ip="0.0.0.0"):_port(port),_ip(ip),listensock(-1){}void initServer(){}void Start(){}~TcpServer(){}
private:uint16_t _port;std::string _ip;int listensock;//listensock套接字仅用于建立连接
};

 头文件在这里一次性给出

#pragma once#include <iostream>
#include <string>
#include <unordered_map>
#include <cstring>
#include <cerrno>
#include <cassert>
#include <signal.h>
#include <unistd.h>
#include <memory>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/wait.h>
#include <pthread.h>
#include <ctype.h>//这几个头文件在线程池里面  其中Task.hpp会在用时给出
#include "ThreadPool/log.hpp"
#include "ThreadPool/ThreadPool.hpp"
#include "ThreadPool/Task.hpp"

 2.1.2 TcpServer的调用 tcp_server.cc

#include "tcp_server.hpp"
#include <memory>static void usage(std::string proc)
{std::cout << "\nUsage: " << proc << " port\n" << std::endl;
}// ./tcp_server port
int main(int argc, char *argv[])
{if(argc != 2){usage(argv[0]);exit(1);}uint16_t port = atoi(argv[1]);std::unique_ptr<TcpServer> svr(new TcpServer(port));svr->initServer();svr->Start();return 0;
}

2.2 客户端 TcpClient的实现 tcp_client.cc

网络间通信首先需要 struct  sockaddr_in 结构体,这里定义一个对象 server

结构对象有了需要对他进行初始化(建议先全部置零,避免出现一些bug)

  • server 有三个地方需要做初始化,sin_familysin_portsin_addr.s_addr

server.sin_family=AF_INET; //跟申请套接字传入的参数一样即可

server.sin_port=htos(serverport); //记得从本地转为网络字节序

server.sin_addr.s_addr=inet_addr(serverip,c_str());

  • connect()接口  客户端需要连接到服务上

 connect(sock, (struct sockaddr *)&server, sizeof(server) );

  •  send() 向服务端发送消息

 ssize_t s = send(sock, line.c_str(), line.size(), 0);

  •  recv() 接收服务器发来的消息

 recv(sock, buffer, sizeof(buffer) - 1, 0);

  •  close() 关闭套接字

 close(sock);

#include <iostream>
#include <string>
#include <cstdio>
#include <cstring>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <netinet/in.h>void usage(std::string proc)
{std::cout << "\nUsage: " << proc << " serverIp serverPort\n"<< std::endl;
}// ./tcp_client targetIp targetPort
int main(int argc, char *argv[])
{if (argc != 3){usage(argv[0]);exit(1);}std::string serverip = argv[1];uint16_t serverport = atoi(argv[2]);int sock = 0;while (true) // TODO{sock = socket(AF_INET, SOCK_STREAM, 0);if (sock < 0){std::cerr << "socket error" << std::endl;exit(2);}// client 要不要bind呢?不需要显示的bind,但是一定是需要port// 需要让os自动进行port选择// 连接别人的能力!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 << "connect error" << std::endl;exit(3); // TODO}std::cout << "connect success" << std::endl;std::cout << "请输入# ";std::string line;std::getline(std::cin, line);if (line == "quit"){close(sock);break;}ssize_t s = send(sock, line.c_str(), line.size(), 0);if (s > 0){char buffer[1024];ssize_t s = recv(sock, buffer, sizeof(buffer) - 1, 0);if (s > 0){buffer[s] = 0;std::cout << "server 回显# " << buffer << std::endl;close(sock);}else if (s == 0){close(sock);}}else{close(sock);}}return 0;
}

3. 服务端的实现

3.1 TcpServer::initServer() 

  • 初始化,首先需要建立一个监听套接字listensock,这个监听套接字的作用是建立连接。
listensock=socket(AF_INET,SOCK_STREAM,0);

我们是网络之间进行通信,所以需要借助 struct sockaddr_in 类型的对象,local,创建出来之后最好先清空一下,保证不会出现一些奇怪的问题。

  • local 有三个地方需要做初始化,sin_familysin_portsin_addr.s_addr

local.sin_family=AF_INET; //跟申请套接字传入的参数一样即可

local.sin_port=htos(_port); //记得从本地转为网络字节序

local.sin_addr.s_addr=_ip=="0.0.0.0"?INADDR_ANY:inet_addr(_ip.c_str());

  •  接着和udp一样,进行绑定操作

//注意 虽然使用struct sockaddr_in 结构体类型 但是接口中的参数依然是 struct sockaddr*

bind(listensock, (struct sockaddr *)&local, sizeof(local);

  •  (tcp新增)建立连接

 listen(listensock, gbacklog);//这个gbacklog以后再谈

3.1.1 完整代码

class TcpServer
{
private:const static int gbacklog = 20;  //后面再说public:TcpServer(uint16_t port, std::string ip="0.0.0.0"):_port(port),_ip(ip),listensock(-1){}void initServer(){listensock=socket(AF_INET,SOCK_STREAM,0);if(listensock<0){logMessage(FATAL, "create socket error, %d:%s", errno, strerror(errno));exit(2);}logMessage(NORMAL, "create socket success, listensock: %d", listensock);struct sockaddr_in local;memset(&local,0,sizeof(local));local.sin_family=AF_INET;local.sin_port=htons(_port);//local.sin_addr.s_addr=_ip.empty()?INADDR_ANY:inet_addr(_ip.c_str());local.sin_addr.s_addr=_ip=="0.0.0.0"?INADDR_ANY:inet_addr(_ip.c_str());//inet_pton(AF_INET, _ip.c_str(), &local.sin_addr);if (bind(listensock, (struct sockaddr *)&local, sizeof(local)) < 0){logMessage(FATAL, "bind error, %d:%s", errno, strerror(errno));exit(3);}if(listen(listensock, gbacklog)<0){logMessage(FATAL, "listen error, %d:%s", errno, strerror(errno));exit(4);}logMessage(NORMAL, "init server success");}
}

3.2 TcpServer::Start()

 要想服务端程序开始运行,首先需要有人来提供服务,记得listensock的作用仅是建立连接吗,那谁来做具体的服务呢?

使用accept接口的返回值!

首先,网络通信需要struct sockaddr_in结构体,先创建一个,struct sockaddr_in src;,len就是这个结构体的长度。

int servicesock=accept(listensock,(struct sockaddr*)&src,&len);

连接成功时候,就可以从我们创建的src中获取端口号、ip信息了。

 uint16_t client_port=ntohs(src.sin_port);

 std::string client_ip=inet_ntoa(src.sin_addr);

 接下来就可以开始进行服务了,自定义让做什么。这里让服务端去做这个任务

service(servicesock, client_ip, client_port);

static void service(int sock, const std::string &clientip, const uint16_t &clientport)
{//echo serverchar buffer[1024];while(true){// read && write 可以直接被使用!ssize_t s = read(sock, buffer, sizeof(buffer)-1);if(s > 0){buffer[s] = 0; //将发过来的数据当做字符串std::cout << clientip << ":" << clientport << "# " << buffer << std::endl;}else if(s == 0) //对端关闭连接{logMessage(NORMAL, "%s:%d shutdown, me too!", clientip.c_str(), clientport);break;}else{ // logMessage(ERROR, "read socket error, %d:%s", errno, strerror(errno));break;}write(sock, buffer, strlen(buffer));}close(sock);
}

 3.2.1  完整代码——单进程阻塞循环版

     void Start(){while(true){struct sockaddr_in src;socklen_t len=sizeof(src);//建立连接int servicesock=accept(listensock,(struct sockaddr*)&src,&len);if(servicesock < 0){logMessage(ERROR, "accept error, %d:%s", errno, strerror(errno));continue;}uint16_t client_port=ntohs(src.sin_port);std::string client_ip=inet_ntoa(src.sin_addr);logMessage(NORMAL, "link success, servicesock: %d | %s : %d |\n",servicesock, client_ip.c_str(), client_port);//版本1————单进程阻塞循环版service(servicesock, client_ip, client_port);}

问题在于,一次只能够处理一个进程。因为我们调用的函数service是一个死循环函数,如果一个客户端没有终止访问,其他客户端都不能正常来使用。

 3.2.2 完整代码——多进程带信号屏蔽版

那我使用多进程来解决这个问题,可是使用多进程也有问题,我创建了子进程,那我是不是要等待子进程结束啊?如果我使用阻塞等待那和上面有什么本质区别呢?

注:使用非阻塞等待成本很大,可以但不建议。

所以我们还需要用到信号,当子进程结束后,会给父进程发SIGCHLD信号!

 如果我们主动忽略SIGCHLD信号,子进程退出的时候,会自动释放自己的僵尸状态。

      void Start(){        signal(SIGCHLD, SIG_IGN); while(true){struct sockaddr_in src;socklen_t len=sizeof(src);//建立连接int servicesock=accept(listensock,(struct sockaddr*)&src,&len);if(servicesock < 0){logMessage(ERROR, "accept error, %d:%s", errno, strerror(errno));continue;}uint16_t client_port=ntohs(src.sin_port);std::string client_ip=inet_ntoa(src.sin_addr);logMessage(NORMAL, "link success, servicesock: %d | %s : %d |\n",servicesock, client_ip.c_str(), client_port);pid_t id = fork();assert(id != -1);if(id == 0){close(listensock);//关闭不需要的文件描述符service(servicesock, client_ip, client_port);exit(0); }//关闭父进程不需要的文件描述符 不关闭会导致父进程可用的文件描述符越来越少close(servicesock);}}

 3.3.3 完整代码——多进程版

能不能不屏蔽SIGCHLD信号呢?那我们就需要有人等待子进程,让谁等呢?

让bash领养,让bash等!

    void Start(){while(true){//...跟上面一样//版本3————多进程孤儿进程版// 利用孤儿进程被系统回收pid_t id=fork();if(id==0){close(listensock);if(fork()>0) {//子进程本身 exit(0);}//子进程的子进程service(servicesock, client_ip, client_port);exit(0);}waitpid(id,nullptr,0);//由于子进程创建子进程后立即退出 所以父进程不会阻塞close(servicesock);}}

 3.3.4 完整代码——多线程版

相较于使用多进程,多线程的开销明显小。

class ThreadData
{
public:int _sock;std::string _ip;uint16_t _port;
};class TcpServer
{
private:const static int gbacklog = 20;  //后面再说//设置的回调函数 必须是static的 不然会多一个this指针static void *threadRoutine(void *args){pthread_detach(pthread_self());ThreadData *td = static_cast<ThreadData *>(args);service(td->_sock, td->_ip, td->_port);delete td;return nullptr;}void Start(){while(true){//...//版本4————多线程版本//因为创建一个进程的代价还是比较大的,创建一个线程相对简便//不使用在栈上创建是为了保证线程安全 不会被覆盖 发生拷贝ThreadData *td=new ThreadData(); td->_sock=servicesock;td->_ip=client_ip;td->_port=client_port;pthread_t tid ;//如果不join 一定会造成内存泄漏 可以在threadRoutine中设置等待pthread_create(&tid,nullptr,threadRoutine,td); }}};

 3.3.5 完整代码——线程池版本

 线程的创建也是一笔开销,能省就省

线程池版本的服务函数

//线程池版本的服务函数
static void service(int sock, const std::string &clientip,const uint16_t &clientport, const std::string &thread_name)
{// echo server//  同时在线10人//  所以,我们一般服务器进程业务处理,如果是从连上,到断开,要一直保持这个链接, 长连接char buffer[1024];while (true){// read && write 可以直接被使用!ssize_t s = read(sock, buffer, sizeof(buffer) - 1);if (s > 0){buffer[s] = 0; // 将发过来的数据当做字符串std::cout << thread_name << "|" << clientip << ":" << clientport << "# " << buffer << std::endl;}else if (s == 0) // 对端关闭连接{logMessage(NORMAL, "%s:%d shutdown, me too!", clientip.c_str(), clientport);break;}else{ //logMessage(ERROR, "read socket error, %d:%s", errno, strerror(errno));break;}write(sock, buffer, strlen(buffer));}close(sock);
}
class TcpServer
{
public:TcpServer(uint16_t port, std::string ip="0.0.0.0"):_port(port),_ip(ip),listensock(-1), _threadpool_ptr(ThreadPool<Task>::getThreadPool()){}void Start(){//引入线程池_threadpool_ptr->run();//signal(SIGCHLD, SIG_IGN); // 对SIGCHLD,主动忽略SIGCHLD信号,子进程退出的时候,会自动释放自己的僵尸状态while(true){//...略//线程池版本Task t(servicesock, client_ip, client_port, service);//Task t(servicesock, client_ip, client_port, dictOnline);_threadpool_ptr->pushTask(t);}}~TcpServer(){}
private:uint16_t _port;std::string _ip;int listensock;//仅用于建立连接//定义一个线程池先std::unique_ptr<ThreadPool<Task>> _threadpool_ptr;
};

pushTask

#pragma once#include <iostream>
#include <string>
#include <functional>
#include "log.hpp"//typedef std::function<void (int , const std::string &, const uint16_t &)> func_t;
using func_t = std::function<void (int , const std::string &, const uint16_t &, const std::string &)>;class Task
{
public:Task(){}Task(int sock, const std::string ip, uint16_t port, func_t func): _sock(sock), _ip(ip), _port(port), _func(func){}void operator ()(const std::string &name){_func(_sock, _ip, _port, name);}
public:int _sock;std::string _ip;uint16_t _port;// int type;func_t _func;
};

相关文章:

Linux--TCP编程--0216 17

观前提示&#xff1a;本篇博文的一些接口需要前几篇博文实现的 线程池的实现Liunx--线程池的实现--0208 09_Gosolo&#xff01;的博客-CSDN博客 线程池的单例模式Linux--线程安全的单例模式--自旋锁--0211_Gosolo&#xff01;的博客-CSDN博客 1.TCP编程需要用的接口 创建 sock…...

关于设计模式的记录

############### 先弄清楚类模型的关系 ############### 万物的抽象关系 ############### 1.组合 composition实菱形 实线 无填充箭头整体与部分的关系同生共死代码体现&#xff1a;成员变量如&#xff1a;生命体与器官&#xff0c;http请求&#xff08;请求行&#xff0c;请求…...

Lambda-常见的函数式接口

如果需要使用Lambda接口&#xff0c;就必须要有一个函数式接口 函数式接口是有且仅有一个抽象方法的接口, 对应的注解是FunctionalInterface Java中内置的常见函数式接口如下: 1.Runnable/ Callable /*** The <code>Runnable</code> interface should be implem…...

P1196 [NOI2002] 银河英雄传说 带权并查集

[NOI2002] 银河英雄传说 题目背景 公元 580158015801 年&#xff0c;地球居民迁至金牛座 α\alphaα 第二行星&#xff0c;在那里发表银河联邦创立宣言&#xff0c;同年改元为宇宙历元年&#xff0c;并开始向银河系深处拓展。 宇宙历 799799799 年&#xff0c;银河系的两大军…...

【项目实战】快来入门Groovy的基础语法吧

一、Groovy是什么? 1.1 与Java语言的关系 下一代的Java 语言,增强Java平台的唯一的脚本语言跟java一样,它也运行在 JVM 中。支持Java平台,无缝的集成了Java 的类和库;Groovy是一种运行在JVM上的动态语言,跑在JVM中的另一种语言编译后的.groovy也是以class的形式出现的。1…...

Mybatis中的动态SQL

Mybatis中的动态SQL 当存在多条件查询的SQL时&#xff0c;当用户某个条件的属性没有写时&#xff0c;就会存在问题&#xff0c;在test中则不能很好的运行 所以Mybatis提出了动态SQL。 即判断用户是否输入了某个属性 动态SQL中的一些问题 方法一 这个里的and是为了确保if条…...

VUE常用API

1.$set数据变了&#xff0c;视图没变 this.$set(targe&#xff0c;key&#xff0c;value)2.$nextTick:返回参数[函数]。是一个异步的&#xff0c;功能获得更新后DOM$nextTick(callback){return Promise.resolve().then(()>{callback();}) }3.$refs获取dom4.$el获取当前组件根…...

25 openEuler管理网络-使用nmcli命令配置ip

文章目录25 openEuler管理网络-使用nmcli命令配置ip25.1 nmcli介绍25.2 设备管理25.2.1 连接到设备25.2.2 断开设备连接25.3 设置网络连接25.3.1 配置动态IP连接25.3.1.1 配置IP25.3.1.2 激活连接并检查状态25.3.2 配置静态IP连接25.3.2.1 配置IP25.3.2.2 激活连接并检查状态25…...

如何安装和使用A-ops工具?

一、pip配置 1.配置信任域 ​ pip3 config set global.trusted-host mirrors.tools.huawei.com2.配置pip源的url地址pip3 config set global.index-url http://mirrors.tools.huawei.com/pypi/simple 二、npm安装及配置 npm -v检测系统有无安装npm,如果没有的话需要配置ope…...

MySql数据库环境部署

MySql基础与Sql数据库概述基础环境的建立MYSQL数据库的连接方法MySql的默认数据库数据库端口号数据库概述 数据库&#xff08;DataBase&#xff0c;DB)∶存储在磁带、磁盘、光盘或其他外存介质上、按定结构组织在一起的相关数据的集合。数据库管理系统〈DataBase Management S…...

极品笔记,阿里P7爆款《K8s+Jenkins》技术笔记,职场必备

前些日子从阿里的朋友那里取得这两份K8sJenkins的爆款技术笔记&#xff1a;《K8S(kubernetes)学习指南》《Jenkins持续集成从入门到精通》&#xff0c;非常高质量的干货&#xff0c;我立马收藏&#xff01; 而今天咱们文章的主角就是这非常之干货的技术笔记&#xff1a;K8SJenk…...

数据结构:各种排序方法的综合比较

排序方法的选用应视具体场合而定。一般情况下考虑的原则有:(1)待排序的记录个数 n;(2)记录本身的大小;(3)关键字的分布情况:(4)对排序稳定性的要求等。 1.时间性能 (1) 按平均的时间性能来分,有三类排序方法: 时间复杂度为 O(nlogn)的方法有:快速排序、堆排序和归并排序,其中…...

【设计模式】 策略模式介绍及C代码实现

【设计模式】 策略模式介绍及C代码实现 背景 在软件构建过程中&#xff0c;某些对象使用的算法可能多种多样&#xff0c;经常改变&#xff0c;如果将这些算法都编码到对象中&#xff0c;将会使对象变得异常复杂&#xff0c;而且有时候支持不使用的算法也是一个性能负担。 如何…...

【数据库】第二章 关系数据库

第二章 关系数据库 2.1关系数据结构及形式化定义 关系 域&#xff08;domain) :域是一组具有相同数据类型的值的集合&#xff0c;可以取值的个数叫基数 笛卡尔积 &#xff1a;一个记录叫做一个元组&#xff08;tuple),元组中每一个属性值&#xff0c;叫一个分量 基数&…...

oracle和mysql的分页

oracle的分页&#xff1a;rownum 注意:&#xff1a; 对 ROWNUM 只能使用 < 或 <, 用 、 >、 > 都不能返回任何数据。 rownum是对结果集的编序排列&#xff0c;始终是从1开始&#xff0c;所以rownum直接使用时不允许使用>、> 所以当查询中间部分的信息时&…...

深拷贝与浅拷贝的理解

浅拷贝的理解浅拷贝的话只会拷贝基本数据类型&#xff0c;例如像string、Number等这些&#xff0c;类似&#xff1a;Object、Array 这类的话拷贝的就是对象的一个指针(通俗来讲就是拷贝一个引用地址&#xff0c;指向的是一个内存同一份数据)&#xff0c;也就是说当拷贝的对象数…...

Shell变量

一、变量分类 根据作用域分三种 &#xff08;一&#xff09;只在函数内有效&#xff0c;叫局部变量 &#xff08;二&#xff09;只在当前shell进程中有效&#xff0c;叫做全局变量 &#xff08;三&#xff09;在当前shell进程与子进程中都有效&#xff0c;叫做环境变量 shell进…...

Android 8请求权限时弹窗BUG

弹窗BUG 应用使用requestPermissions申请权限时&#xff0c;系统会弹出一个选择窗口&#xff0c;可进行允许或拒绝&#xff0c; 此窗口中有一个”不再询问“的选择框&#xff0c; ”拒绝”及“允许”的按钮。 遇到一个Bug,单点击“不再询问”&#xff0c;“允许”这个按钮会变…...

路漫漫:网络空间的监管趋势

网络空间是“以相互依存的网络基础设施为基本架构&#xff0c;以代码、信息与数据的流动为环境&#xff0c;人类利用信息通讯技术与应用开展活动&#xff0c;并与其他空间高度融合与互动的空间”。随着信息化技术的发展&#xff0c;网络空间日益演绎成为与现实人类生存空间并存…...

洛谷 P1208 [USACO1.3]混合牛奶 Mixing Milk

最后水一篇水题题解&#xff08;实在太水了&#xff09; # [USACO1.3]混合牛奶 Mixing Milk ## 题目描述 由于乳制品产业利润很低&#xff0c;所以降低原材料&#xff08;牛奶&#xff09;价格就变得十分重要。帮助 Marry 乳业找到最优的牛奶采购方案。 Marry 乳业从一些奶农手…...

边缘计算医疗风险自查APP开发方案

核心目标:在便携设备(智能手表/家用检测仪)部署轻量化疾病预测模型,实现低延迟、隐私安全的实时健康风险评估。 一、技术架构设计 #mermaid-svg-iuNaeeLK2YoFKfao {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg…...

使用van-uploader 的UI组件,结合vue2如何实现图片上传组件的封装

以下是基于 vant-ui&#xff08;适配 Vue2 版本 &#xff09;实现截图中照片上传预览、删除功能&#xff0c;并封装成可复用组件的完整代码&#xff0c;包含样式和逻辑实现&#xff0c;可直接在 Vue2 项目中使用&#xff1a; 1. 封装的图片上传组件 ImageUploader.vue <te…...

什么是EULA和DPA

文章目录 EULA&#xff08;End User License Agreement&#xff09;DPA&#xff08;Data Protection Agreement&#xff09;一、定义与背景二、核心内容三、法律效力与责任四、实际应用与意义 EULA&#xff08;End User License Agreement&#xff09; 定义&#xff1a; EULA即…...

Maven 概述、安装、配置、仓库、私服详解

目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...

Typeerror: cannot read properties of undefined (reading ‘XXX‘)

最近需要在离线机器上运行软件&#xff0c;所以得把软件用docker打包起来&#xff0c;大部分功能都没问题&#xff0c;出了一个奇怪的事情。同样的代码&#xff0c;在本机上用vscode可以运行起来&#xff0c;但是打包之后在docker里出现了问题。使用的是dialog组件&#xff0c;…...

FFmpeg:Windows系统小白安装及其使用

一、安装 1.访问官网 Download FFmpeg 2.点击版本目录 3.选择版本点击安装 注意这里选择的是【release buids】&#xff0c;注意左上角标题 例如我安装在目录 F:\FFmpeg 4.解压 5.添加环境变量 把你解压后的bin目录&#xff08;即exe所在文件夹&#xff09;加入系统变量…...

关于uniapp展示PDF的解决方案

在 UniApp 的 H5 环境中使用 pdf-vue3 组件可以实现完整的 PDF 预览功能。以下是详细实现步骤和注意事项&#xff1a; 一、安装依赖 安装 pdf-vue3 和 PDF.js 核心库&#xff1a; npm install pdf-vue3 pdfjs-dist二、基本使用示例 <template><view class"con…...

关于easyexcel动态下拉选问题处理

前些日子突然碰到一个问题&#xff0c;说是客户的导入文件模版想支持部分导入内容的下拉选&#xff0c;于是我就找了easyexcel官网寻找解决方案&#xff0c;并没有找到合适的方案&#xff0c;没办法只能自己动手并分享出来&#xff0c;针对Java生成Excel下拉菜单时因选项过多导…...

Python竞赛环境搭建全攻略

Python环境搭建竞赛技术文章大纲 竞赛背景与意义 竞赛的目的与价值Python在竞赛中的应用场景环境搭建对竞赛效率的影响 竞赛环境需求分析 常见竞赛类型&#xff08;算法、数据分析、机器学习等&#xff09;不同竞赛对Python版本及库的要求硬件与操作系统的兼容性问题 Pyth…...

《Docker》架构

文章目录 架构模式单机架构应用数据分离架构应用服务器集群架构读写分离/主从分离架构冷热分离架构垂直分库架构微服务架构容器编排架构什么是容器&#xff0c;docker&#xff0c;镜像&#xff0c;k8s 架构模式 单机架构 单机架构其实就是应用服务器和单机服务器都部署在同一…...