【网络编程】实现一个简单多线程版本TCP服务器(附源码)

TCP多线程
- 🌵预备知识
- 🎄 Accept函数
- 🌲字节序转换函数
- 🌳listen函数
- 🌴代码
- 🌱Log.hpp
- 🌿Makefile
- ☘️TCPClient.cc
- 🍀TCPServer.cc
- 🎍 util.hpp
🌵预备知识
🎄 Accept函数
accept 函数是在服务器端用于接受客户端连接请求的函数,它在监听套接字上等待客户端的连接,并在有新的连接请求到来时创建一个新的套接字用于与该客户端通信。
- 下面是 accept 函数的详细介绍以及各个参数的意义:
#include <sys/socket.h>int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
sockfd: 是服务器监听套接字的文件描述符,通常是使用 socket 函数创建的套接字。accept 函数在该套接字上等待连接请求。
addr: 是一个指向 struct sockaddr 类型的指针,用于存储客户端的地址信息。当新连接建立成功后,客户端的地址信息将会被填充到这个结构体中。
addrlen: 是一个指向 socklen_t 类型的指针,它指示 addr 结构体的长度。在调用 accept 函数之前,需要将其初始化为 addr 结构体的大小,函数执行后会更新为实际的客户端地址长度。
返回值:如果连接成功建立,accept 函数将返回一个新的文件描述符,该文件描述符用于与客户端进行通信。如果连接失败,函数将返回 -1,并设置 errno 以指示错误原因。
- accept 函数的工作原理如下:
当服务器的监听套接字接收到一个新的连接请求时,accept 函数会创建一个新的套接字用于与该客户端通信。
新的套接字会继承监听套接字的监听属性,包括 IP 地址、端口等。
accept 函数会填充 addr 结构体,以便获取客户端的地址信息。
服务器可以使用返回的新套接字与客户端进行通信。
- 注意事项:
accept 函数在没有连接请求时会阻塞,直到有新的连接请求到来。
如果希望设置非阻塞模式,可以使用 fcntl 函数设置 O_NONBLOCK 属性。
在多线程或多进程环境下,需要注意 accept 函数的线程安全性,可以使用互斥锁等机制来保护。
综上所述,accept 函数在构建服务器程序时非常重要,它使服务器能够接受客户端的连接请求并创建新的套接字与客户端进行通信。
🌲字节序转换函数
在网络编程中,字节序问题很重要,因为不同的计算机体系结构可能使用不同的字节序,这可能导致在通信过程中的数据解释错误。为了在不同体系结构之间正确传递数据,需要进行字节序的转换。
- 以下是一些常用的字节序转换函数:
ntohl 和 htonl: 这些函数用于 32 位整数的字节序转换。ntohl 用于将网络字节序转换为主机字节序,htonl 则相反,将主机字节序转换为网络字节序。
ntohs 和 htons: 这些函数用于 16 位整数的字节序转换。ntohs 用于将网络字节序转换为主机字节序,htons 则相反,将主机字节序转换为网络字节序。
这些函数通常用于在网络编程中处理套接字通信中的数据转换,以确保在不同平台上的正确数据交换。
- 示例
#include <arpa/inet.h>int main() {uint32_t networkValue = 0x12345678;uint32_t hostValue = ntohl(networkValue); // 0x78563412 on a little-endian hostuint32_t convertedValue = htonl(hostValue); // 0x12345678 on a little-endian hostuint16_t networkPort = 0x1234;uint16_t hostPort = ntohs(networkPort); // 0x3412 on a little-endian hostuint16_t convertedPort = htons(hostPort); // 0x1234 on a little-endian hostreturn 0;
}
请注意,在使用这些函数时,需要包含 <arpa/inet.h> 头文件。这些函数通常在网络编程中用于正确处理字节序问题,以确保不同平台之间的数据传输正确。
🌳listen函数
在TCP通信中,服务端需要使用 listen 函数来监听连接请求。这是因为TCP是一种面向连接的协议,它采用客户端-服务端模型进行通信,通信双方需要先建立连接,然后进行数据的传输。监听的过程是为了等待客户端发起连接请求。
- 具体原因如下:
建立连接: 在TCP通信中,通信双方需要通过三次握手建立连接。客户端通过 connect 函数向服务器发起连接请求,而服务端则需要通过 listen 函数来准备接收连接请求。
处理并发连接: 服务端可能会同时接收多个客户端的连接请求,而每个连接都需要为其分配一个独立的套接字。通过监听连接请求,服务端可以在一个循环中接受多个连接,为每个连接创建对应的套接字,从而实现并发处理多个客户端。
连接队列: listen 函数将连接请求存储在一个队列中,等待服务端逐个接受。这个队列称为“未完成连接队列”(backlog queue)。如果连接请求过多,超出了队列的长度,那么新的连接请求可能会被拒绝或被丢弃。
连接参数: listen 函数还可以指定一个参数,表示在未完成连接队列中可以容纳的连接请求数量。这个参数可以影响服务端处理并发连接的能力。
总之,TCP监听是为了等待客户端发起连接请求,建立连接,然后实现双方的数据传输。这种机制允许服务器处理多个客户端连接,实现高并发的网络服务。
- 函数原型:
int listen(int sockfd, int backlog);
- 参数说明:
sockfd:要进行监听的套接字描述符。
backlog:表示在未完成连接队列中可以容纳的连接请求数量。这个参数可以影响服务器处理并发连接的能力。通常情况下,系统会为这个值设置一个默认的最大值,但你也可以根据你的需求进行适当调整。
返回值:
如果函数调用成功,返回 0。
如果出现错误,返回 -1,并设置全局变量 errno 来指示错误类型。
使用步骤:
创建套接字并绑定地址。
调用 listen 函数将套接字标记为被动套接字,开始监听连接请求。
使用 accept 函数接受客户端连接请求,建立实际的连接。
- 示例用法
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>int main() {int listen_sock = socket(AF_INET, SOCK_STREAM, 0);if (listen_sock == -1) {perror("socket");exit(EXIT_FAILURE);}struct sockaddr_in server_addr;memset(&server_addr, 0, sizeof(server_addr));server_addr.sin_family = AF_INET;server_addr.sin_port = htons(8080);server_addr.sin_addr.s_addr = INADDR_ANY;if (bind(listen_sock, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {perror("bind");exit(EXIT_FAILURE);}if (listen(listen_sock, 5) == -1) { // 开始监听,最多允许5个未完成连接perror("listen");exit(EXIT_FAILURE);}// 现在可以使用 accept 函数接受连接请求并建立连接close(listen_sock);return 0;
}
注意:listen 后的套接字仅能用于接受连接请求,不能用于读写数据。接收到的连接请求将在一个队列中等待,直到使用 accept 函数从队列中取出并建立连接。
🌴代码
🌱Log.hpp
#pragma once#include <cstdio>
#include <ctime>
#include <cstdarg>
#include <cassert>
#include <cstring>
#include <cerrno>
#include <stdlib.h>#define DEBUG 0
#define NOTICE 1
#define WARINING 2
#define FATAL 3const char *log_level[]={"DEBUG", "NOTICE", "WARINING", "FATAL"};// logMessage(DEBUG, "%d", 10);
void logMessage(int level, const char *format, ...)
{assert(level >= DEBUG);assert(level <= FATAL);char *name = getenv("USER");char logInfo[1024];va_list ap; // ap -> char*va_start(ap, format);vsnprintf(logInfo, sizeof(logInfo)-1, format, ap);va_end(ap); // ap = NULLFILE *out = (level == FATAL) ? stderr:stdout;fprintf(out, "%s | %u | %s | %s\n", \log_level[level], \(unsigned int)time(nullptr),\name == nullptr ? "unknow":name,\logInfo);// char *s = format;// while(s){// case '%':// if(*(s+1) == 'd') int x = va_arg(ap, int);// break;// }
}
🌿Makefile
.PHONY:all
all:TCPClient TCPServerTCPClient: TCPClient.ccg++ -o $@ $^ -std=c++11 -lpthread
TCPServer:TCPServer.ccg++ -o $@ $^ -std=c++11 -lpthread.PHONY:clean
clean:rm -f TCPClient TCPServer
☘️TCPClient.cc
#include"util.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;
}int main(int argc,char *argv[])
{if(argc!=3){Usage(argv[0]);exit(USAGE_ERR);}std::string serverip=argv[1];uint16_t serverport=atoi(argv[2]);//1.创建socket SOCK_STREAMint sock=socket(AF_INET,SOCK_STREAM,0);if(sock<0){std::cerr<<"socket :"<<strerror(errno)<<std::endl;exit(SOCKET_ERR);}//2.链接 //向服务器发起链接请求struct sockaddr_in server;memset(&server,0,sizeof(server));server.sin_family=AF_INET;server.sin_port=htons(server.sin_port);inet_aton(serverip.c_str(),&server.sin_addr);//2.2发起请求 connect自动会进行bindif(connect(sock,(const struct sockaddr*)&server,sizeof(server))!=0){//链接失败std::cerr<<"connect :"<<strerror(errno)<<std::endl;exit(CONN_ERR);}//链接成功std::cout<<" info :connect success :"<<sock<<std::endl;std::string message;while(!quit){message.clear();std::cout<<"请输入您的消息>>>>"<<std::endl;std::getline(std::cin,message);if(strcasecmp(message.c_str(),"quit")==0){//如果输入的是quit 直接退出程序quit=true; //设置成true 会把当前信息先执行发送到服务器 再进入while循环时条件不满直接退出}//从服务器接收到的消息ssize_t s=write(sock,message.c_str(),message.size());if(s>0){message.resize(1024);ssize_t s=read(sock,(char *)(message.c_str()),1024);if(s>0)message[s]=0;std::cout<<"Server Echo>>>"<<"message"<<std::endl;}else if (s <= 0){break;}}close(sock);return 0;
}
🍀TCPServer.cc
#include "util.hpp"
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <pthread.h>
class ServerTcp;//先声明class ThreadData
{public:uint16_t clientPort_;//客户端端口号std::string clientip_;//客户端ipint sock_;ServerTcp *this_;ThreadData(uint16_t port, std::string ip, int sock, ServerTcp *ts): clientPort_(port), clientip_(ip), sock_(sock),this_(ts){}};class ServerTcp
{public://构造和和析构函数ServerTcp(uint16_t port,const std::string &ip=""):port_(port),ip_(ip),listenSock_(-1){}~ServerTcp(){}public://初始化函数void init(){//第一步:创建套接字listenSock_=socket(PF_INET,SOCK_STREAM,0);if(listenSock_<0){//创建失败logMessage(FATAL,"socket:%s",strerror(errno)); //用日志打印错误信息exit(SOCKET_ERR);}//创建成功logMessage(DEBUG,"sockt:%s,%d",strerror(errno),listenSock_);//第二步 bind绑定//2.1填充服务器信息struct sockaddr_in local;memset(&local,0,sizeof(local));//设置0?/*可以确保将所有这些字段初始化为零,以避免在实际使用过程中出现未定义行为或不可预测的结果。*/local.sin_family=AF_INET; /*如果 ip_ 为空,服务器将绑定到任意可用的本地IP地址。如果 ip_ 不为空,服务器将绑定到 ip_ 所代表的具体IP地址。*/ip_.empty()?(local.sin_addr.s_addr)=htons(INADDR_ANY):(inet_aton(ip_.c_str(),&local.sin_addr));//2.2if(bind(listenSock_,(const struct sockaddr*)&local,sizeof local)<0)//{//bind绑定失败logMessage(FATAL,"bind:%s",strerror(errno));exit(BIND_ERR);}//绑定成功logMessage(DEBUG,"bind:%S,%d",strerror(errno),listenSock_);//3.监听socketif(listen(listenSock_,5)<0){logMessage(FATAL,"listen:%s",strerror(errno));exit(LISTEN_ERR);}//监听成功logMessage(DEBUG,"listen:%S,%d",strerror(errno),listenSock_);//到这一步就等待运行 等待客户端链接}static void *threadRoutine(void *args){pthread_detach(pthread_self()); //设置线程分离ThreadData *td = static_cast<ThreadData*>(args);td->this_->tranService(td->sock_, td->clientip_, td->clientPort_);delete td;return nullptr;}//加载void loop(){while(true){struct sockaddr_in peer;socklen_t len=sizeof(peer);//获取链接 accept返回值??int serviceSock=accept(listenSock_,(struct sockaddr*)&peer,&len);if(serviceSock<0){//获取连接失败logMessage(WARINING,"Accept :%S[%d]",strerror(errno),serviceSock);continue;//获取失败 继续接收....}//获取客户端的基本信息 存储起来uint16_t peerPort=ntohs(peer.sin_port);std::string peerip=inet_ntoa(peer.sin_addr);//打印一下获取的客户端信息logMessage(DEBUG,"Aceept :%s|%s[%d],socket fd :%d",strerror(errno),peerip.c_str(),peerPort,serviceSock);// 5 提供服务, echo -> 小写 -> 大写// 5.0 v0 版本 -- 单进程 -- 一旦进入transService,主执行流,就无法进行向后执行,只能提供完毕服务之后才能进行accept// transService(serviceSock, peerIp, peerPort);// 5.1 v1 版本 -- 多进程版本 -- 父进程打开的文件会被子进程继承吗?会的// pid_t id = fork();// assert(id != -1);// if(id == 0)// {// close(listenSock_); //建议// //子进程// transService(serviceSock, peerIp, peerPort);// exit(0); // 进入僵尸// }// // 父进程// close(serviceSock); //这一步是一定要做的!// 5.1 v1.1 版本 -- 多进程版本 -- 也是可以的// 爷爷进程// pid_t id = fork();// if(id == 0)// {// // 爸爸进程// close(listenSock_);//建议// // 又进行了一次fork,让 爸爸进程// if(fork() > 0) exit(0);// // 孙子进程 -- 就没有爸爸 -- 孤儿进程 -- 被系统领养 -- 回收问题就交给了系统来回收// transService(serviceSock, peerIp, peerPort);// exit(0);// }// // 父进程// close(serviceSock); //这一步是一定要做的!// // 爸爸进程直接终止,立马得到退出码,释放僵尸进程状态// pid_t ret = waitpid(id, nullptr, 0); //就用阻塞式// assert(ret > 0);// (void)ret;// 5.2 v2 版本 -- 多线程// 这里不需要进行关闭文件描述符吗??不需要啦// 多线程是会共享文件描述符表的!ThreadData *td = new ThreadData(peerPort, peerip, serviceSock, this);pthread_t tid;pthread_create(&tid, nullptr, threadRoutine, (void*)td);// waitpid(); 默认是阻塞等待!WNOHANG// 方案1// logMessage(DEBUG, "server 提供 service start ...");// sleep(1);}}//提供服务函数 -----> 大小写转换void tranService(int sock,const std::string &clientip,uint16_t clientPort){assert(sock>=0);assert(!clientip.empty());assert(clientPort>=1024); //1~~1024端口为系统端口 不可轻易更改char inbuffer[BUFFER_SIZE];while(true){ssize_t s=read(sock,inbuffer,sizeof(inbuffer)-1); //-1是给\0留出一个位置if(s>0){inbuffer[s]='0';if(strcasecmp(inbuffer,"quit")==0){logMessage(DEBUG,"client quit----------%s[%d]",clientip.c_str(),clientPort);break;}logMessage(DEBUG,"Treans Before:%s[%d]>>>%s",clientip.c_str(),clientPort,inbuffer);//进行大小写转换for(int i=0;i<s;i++){if(isalpha(inbuffer[i])&&islower(inbuffer[i])){inbuffer[i]=toupper(inbuffer[i]);}}logMessage(DEBUG,"Trans after:%s[%d]>>>>%s",clientip.c_str(),clientPort,inbuffer);write(sock,inbuffer,strlen(inbuffer));//给客户端发送回去}else if(s==0){// pipe: 读端一直在读,写端不写了,并且关闭了写端,读端会如何?s == 0,代表对端关闭// s == 0: 代表对方关闭,client 退出logMessage(DEBUG, "client quit -- %s[%d]", clientip.c_str(), clientPort);break;}else{logMessage(DEBUG, "%s[%d] - read: %s", clientip.c_str(), clientPort, strerror(errno));break;}}// 只要走到这里,一定是client退出了,服务到此结束close(sock); // 如果一个进程对应的文件fd,打开了没有被归还,文件描述符泄漏!logMessage(DEBUG, "server close %d done", sock);}private:// sockint listenSock_;// portuint16_t port_;// ipstd::string ip_;
};static void Usage(std::string proc)
{std::cerr << "Usage:\n\t" << proc << " port ip" << std::endl;std::cerr << "example:\n\t" << proc << " 8080 127.0.0.1\n" << std::endl;}// ./ServerTcp local_port local_ip
int main(int argc, char *argv[])
{if(argc != 2 && argc != 3 ){Usage(argv[0]);exit(USAGE_ERR);}uint16_t port = atoi(argv[1]);std::string ip;if(argc == 3) ip = argv[2];ServerTcp svr(port, ip);svr.init();svr.loop();return 0;
}
🎍 util.hpp
#pragma once#include <iostream>
#include <string>
#include <cstring>
#include <cstdlib>
#include <cassert>
#include <ctype.h>
#include <unistd.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "Log.hpp"#define SOCKET_ERR 1
#define BIND_ERR 2
#define LISTEN_ERR 3
#define USAGE_ERR 4
#define CONN_ERR 5#define BUFFER_SIZE 1024
大家可以拉下来自行测试…
🎋 🍃 🍂 🍁 🍄 🐚 💐 🌷 🌹 🥀 🌺 🌸 🌼 🌻
相关文章:
【网络编程】实现一个简单多线程版本TCP服务器(附源码)
TCP多线程 🌵预备知识🎄 Accept函数🌲字节序转换函数🌳listen函数 🌴代码🌱Log.hpp🌿Makefile☘️TCPClient.cc🍀TCPServer.cc🎍 util.hpp 🌵预备知识 &…...
centos离线部署docker
有些内部环境需要离线部署,以下做一些备忘。 环境:centos7.9 准备文件: docker-20.10.9.tgz,下载地址 https://download.docker.com/linux/static/stable/x86_64/docker.service,内容见下文daemon.json,内…...
ffmpeg使用滤镜对视频进行处理播放
一、前言 在现代的多媒体处理中,视频和音频滤镜起着至关重要的作用。可以帮助开发者对视频和音频进行各种处理,如色彩校正、尺寸调整、去噪、特效添加等。而FFmpeg作为一个功能强大的开源多媒体框架,提供了丰富的滤镜库,使我们能够轻松地对多媒体文件进行处理和转换。 本…...
Ansible Handlers模块详解,深入理解Ansible Handlers 自动化中的关键组件
深入理解Ansible Handlers 自动化中的关键组件 在现代的IT环境中,自动化已经成为提高效率和减少错误的关键。Ansible作为一款流行的自动化工具,通过使用Playbooks来定义和执行任务。而Handlers作为Ansible的组件之一,在自动化过程中发挥着重要…...
threejs点击模型实现模型边缘高亮的选中效果--更改后提高帧率
先来个效果图 之前写的那个稍微有点问题,帧率只有30,参照官方代码修改后,帧率可以达到50了,在不全屏的状态下,帧率60 1.首先需要导入库 // 用于模型边缘高亮 import { EffectComposer } from "three/examples/js…...
RocketMQ 主备自动切换模式部署
目录 主备自动切换模式部署 Controller 部署 Controller 嵌入 NameServer 部署 Controller 独立部署 Broker 部署 兼容性 升级注意事项 主备自动切换模式部署 该文档主要介绍如何部署支持自动主从切换的 RocketMQ 集群,其架构如上图所示ÿ…...
【MySQL】select相关
文章目录 迭代器distinct 关键字limit offset 关键字order by 列名 asc\descselect语句的执行顺序几点注意 迭代器 指向第一个元素 使用hasNext()进行判断后才进行取元素 resultSet:指向第一个元素前一个 distinct 关键字 去除一列中的重复元素 可以进行多行的去重…...
在Python中应用RSA算法实现图像加密:基于Jupyter环境的详细步骤和示例代码
一、引言 在当今的数字化社会中,信息安全问题备受关注。随着数字图像在生活中的应用越来越广泛,图像的安全性和隐私性也成为人们关心的焦点。如何在网络上安全地传输和存储图像已经成为一项重要的挑战。RSA(Rivest-Shamir-Adleman)算法作为一种被广泛应用的公钥密码体系,…...
Prometheus Blackbox Exporter 的 HTTP 探测指标中各个阶段的时间统计信息
在 Prometheus Blackbox Exporter 的 HTTP 探测指标中,probe_http_duration_seconds 指标包含各个阶段的时间统计信息。这些阶段代表了 HTTP 探测的不同阶段和指标。以下是各个阶段的含义: phase"dns_lookup":这是指进行 DNS 查找…...
数据结构之时间复杂度-空间复杂度
大家好,我是深鱼~ 目录 1.数据结构前言 1.1什么是数据结构 1.2什么是算法 1.3数据结构和算法的重要性 1.4如何学好数据结构和算法 2.算法的效率 3.时间复杂度 3.1时间复杂度的概念 3.2大O的渐进表示法 【实例1】:双重循环的时间复杂度…...
新一代构建工具 maven-mvnd
新一代构建工具 maven-mvnd mvnd的前世今生下载安装 mvndIDEA集成 mvnd的前世今生 maven 作为一代经典的构建工具,流行了很多年,知道现在依然是大部分Java项目的构建工具的首选;但随着项目复杂度提高,代码量及依赖库的增多使得ma…...
构建Docker容器监控系统(2)(Cadvisor +Prometheus+Grafana)
Cadvisor产品简介 Cadvisor是Google开源的一款用于展示和分析容器运行状态的可视化工具。通过在主机上运行Cadvisor用户可以轻松的获取到当前主机上容器的运行统计信息,并以图表的形式向用户展示。 接着上一篇来继续 部署Cadvisor 被监控主机上部署Cadvisor容器…...
Leetcode.995 K 连续位的最小翻转次数
题目链接 Leetcode.995 K 连续位的最小翻转次数 rating : 1835 题目描述 给定一个二进制数组 n u m s nums nums 和一个整数 k k k 。 k k k位翻转 就是从 n u m s nums nums 中选择一个长度为 k k k 的 子数组 ,同时把子数组中的每一个 0 0 0 都改成 1 1 1 …...
PHP8的跳转语句-PHP8知识详解
如果循环条件满足的时候,则程序会一直执行下去。如果需要强制跳出循环,则需要使用跳转语句来完成。PHP8的跳转语句包括break语句、continue语句和goto语句。 1、break语句 break语句的作用是完全终止循环,包括while、do…while、for、switch…...
Idea中maven无法下载源码
今天在解决问题的时候想要下载源码,突然发现idea无法下载,这是真的蛋疼,没办法查看原因,最后发现问题的原因居然是因为Maven,由于我使用的idea的内置的Bundle3的Maven,之前没有研究过本地安装和内置的区别&…...
【linux-keepalive】keepalive避免单点故障,高可用配置
keepalive: [rootproxy ~]# yum install -y keepalived [rootproxy ~]# vim /etc/keepalived/keepalived.conf global_defs {router_id proxy1 //设置路由ID号vrrp_iptables //不添加任何防火墙规则 } vrrp_instance V…...
测试网络模型的FLOPs和params
概念 FLOPS:注意全大写,是floating point operations per second的缩写,意指每秒浮点运算次数,理解为计算速度。是一个衡量硬件性能的指标。 FLOPs:注意s小写,是floating point operations的缩写…...
《树莓派项目实战》第十五节 使用L298N驱动板模块驱动双极42步进电机
目录 15.1 双极步进电机引脚介绍 15.2 连接到树莓派 15.3 编写代码驱动步进电机 在本节,我们将学习如何使用L298N驱动板驱动一个双极42步进电机。该项目涉及到的材料有: 树莓派...
基于短信宝API零代码实现短信自动化业务
场景描述: 基于短信宝开放的API能力,实现在特定事件(如天气预警)或定时自动发送短信(本文以定时群发短信为例)。通过Aboter平台如何实现呢? 使用方法: 首先创建一个IPaaS流程&…...
Qt应用开发(基础篇)——信号槽 Signals and Slots
一、前言 Qt成为我们今天拥有的灵活而舒适的工具,除了友好和能够快速开发设计师界面,信号槽机制是最大的核心特征,也是区别于其他开发框架最大的优势。 Qt的信号槽作用于两个对象之间的通信。当一个对象发生了改变,它希望其他关心…...
浏览器访问 AWS ECS 上部署的 Docker 容器(监听 80 端口)
✅ 一、ECS 服务配置 Dockerfile 确保监听 80 端口 EXPOSE 80 CMD ["nginx", "-g", "daemon off;"]或 EXPOSE 80 CMD ["python3", "-m", "http.server", "80"]任务定义(Task Definition&…...
云计算——弹性云计算器(ECS)
弹性云服务器:ECS 概述 云计算重构了ICT系统,云计算平台厂商推出使得厂家能够主要关注应用管理而非平台管理的云平台,包含如下主要概念。 ECS(Elastic Cloud Server):即弹性云服务器,是云计算…...
汽车生产虚拟实训中的技能提升与生产优化
在制造业蓬勃发展的大背景下,虚拟教学实训宛如一颗璀璨的新星,正发挥着不可或缺且日益凸显的关键作用,源源不断地为企业的稳健前行与创新发展注入磅礴强大的动力。就以汽车制造企业这一极具代表性的行业主体为例,汽车生产线上各类…...
智能在线客服平台:数字化时代企业连接用户的 AI 中枢
随着互联网技术的飞速发展,消费者期望能够随时随地与企业进行交流。在线客服平台作为连接企业与客户的重要桥梁,不仅优化了客户体验,还提升了企业的服务效率和市场竞争力。本文将探讨在线客服平台的重要性、技术进展、实际应用,并…...
linux 错误码总结
1,错误码的概念与作用 在Linux系统中,错误码是系统调用或库函数在执行失败时返回的特定数值,用于指示具体的错误类型。这些错误码通过全局变量errno来存储和传递,errno由操作系统维护,保存最近一次发生的错误信息。值得注意的是,errno的值在每次系统调用或函数调用失败时…...
C++.OpenGL (10/64)基础光照(Basic Lighting)
基础光照(Basic Lighting) 冯氏光照模型(Phong Lighting Model) #mermaid-svg-GLdskXwWINxNGHso {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-GLdskXwWINxNGHso .error-icon{fill:#552222;}#mermaid-svg-GLd…...
中医有效性探讨
文章目录 西医是如何发展到以生物化学为药理基础的现代医学?传统医学奠基期(远古 - 17 世纪)近代医学转型期(17 世纪 - 19 世纪末)现代医学成熟期(20世纪至今) 中医的源远流长和一脉相承远古至…...
QT3D学习笔记——圆台、圆锥
类名作用Qt3DWindow3D渲染窗口容器QEntity场景中的实体(对象或容器)QCamera控制观察视角QPointLight点光源QConeMesh圆锥几何网格QTransform控制实体的位置/旋转/缩放QPhongMaterialPhong光照材质(定义颜色、反光等)QFirstPersonC…...
腾讯云V3签名
想要接入腾讯云的Api,必然先按其文档计算出所要求的签名。 之前也调用过腾讯云的接口,但总是卡在签名这一步,最后放弃选择SDK,这次终于自己代码实现。 可能腾讯云翻新了接口文档,现在阅读起来,清晰了很多&…...
LOOI机器人的技术实现解析:从手势识别到边缘检测
LOOI机器人作为一款创新的AI硬件产品,通过将智能手机转变为具有情感交互能力的桌面机器人,展示了前沿AI技术与传统硬件设计的完美结合。作为AI与玩具领域的专家,我将全面解析LOOI的技术实现架构,特别是其手势识别、物体识别和环境…...
