frameworks 之Socket

frameworks 之Socket

  • Socket
    • 服务端
      • 1.创建Socket。
      • 2.绑定socket
      • 3.监听socket
      • 4.等待客户端连接
      • 5.读取或者写入给客户端
    • 客户端
      • 1.创建Socket。
      • 2.连接服务端Socket
      • 3.读取或者写入给客户端
      • 4.关闭socket
    • 演示代码
  • Epoll
    • 创建Epoll
    • 添加或删除Epoll
    • 等待消息返回Epoll
    • 演示代码
  • SocketPair
    • 创建socketPair
    • 设置对应的属性
    • 演示代码

Socket是应用层与TCP/IP协议族通信的中间软件抽象层。他作为Android 进程间的通信。
涉及到的类如下

  • frameworks/base/services/core/jni/com_android_server_input_InputManagerService.cpp

Socket

创建一个socket 需要几个步骤
服务端: socket -> bind -> listen -> accept -> read/write -> close
客户端: socket -> connect -> read/write -> close

服务端

1.创建Socket。

第一个参数为协议域,又称为协议族(family)。常用的协议族有,AF_INET、AF_INET6、AF_LOCAL**(或称AF_UNIX,Unix域socket,这个是我们的讲解重点)**、AF_ROUTE等等。协议族决定了socket的地址类型,在通信中必须采用对应的地址,如AF_INET决定了要用ipv4地址(32位的)与端口号(16位的)的组合、AF_UNIX决定了要用一个绝对路径名作为地址

第二个参数 指定socket类型。常用的socket类型有,SOCK_STREAM、SOCK_DGRAM、SOCK_RAW、SOCK_PACKET

第三个参数为 指定协议。常用的协议有,IPPROTO_TCP、IPPTOTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等,它们分别对应TCP传输协议、UDP传输协议、STCP传输协议、TIPC传输协议。

int socketFd = socket(SOCKET_DOMIAN, SOCKET_TYPE, SOCKET_PROTOCOL);

2.绑定socket

bind()函数就是将给这个描述字绑定一个名字。addr:一个const struct sockaddr *指针,指向要绑定给sockfd的协议地址。这个地址结构根据地址创建socket时的地址协议族的不同而不同。addrlen:对应的是地址的长度。

int bindResult = bind(socketFd,  (struct sockaddr *)&serviceAddr, serviceLen);

地址为抽象和文件类型。文件类型会生成一个文件在本地,抽象需要在地址前面加0,对应会生成@开头。可以用如下命令查询查看

netstat -an | grep 地址名称

3.监听socket

设置监听的最大连接个数

int listenerResult = listen(socketFd, SOCKET_NUMBER);

4.等待客户端连接

传入客户端的对象 和长度 由内核写入。等待客户端的连接。

int connectFd = accept(socketFd, (struct sockaddr *)&clientAddr, &clintLen);

5.读取或者写入给客户端

通过 read 或者 write 函数跟客户端通信

		   int dataSize = read(connectFd, &buffer, sizeof(buffer));if (dataSize == 0){printf("客户端关闭连接\n");} else if (dataSize == -1){printf("读取异常\n");exit(1);} else {printf("客户端发送了: %s\n" ,buffer);for (int i = 0; i < sizeof(buffer); i++){buffer[i] = toupper(buffer[i]);}write(connectFd, buffer, sizeof(buffer));}

客户端

1.创建Socket。

int serviceFd = socket(SOCKET_DOMIAN, SOCKET_TYPE, SOCKET_PROTOCOL);

2.连接服务端Socket

发起对服务端的连接,连接成功后 服务端 accept 方法将继续执行

int connectResult = connect(serviceFd, (struct sockaddr *)&clientAddr, clientLen);

3.读取或者写入给客户端

		int wirteResult = write(serviceFd, winBuffer, sizeof(winBuffer));printf("写入服务端数据 %i", wirteResult);// 阻塞读取服务端数据int readResult =read(serviceFd, winBuffer, sizeof(winBuffer));

4.关闭socket

close(serviceFd);

演示代码

服务端

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
// scoket相关
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/un.h>
#include <unistd.h>// 协议域
int SOCKET_DOMIAN = AF_UNIX;
// 类型
int SOCKET_TYPE = SOCK_STREAM;
// 协议,常用tcp udp
int SOCKET_PROTOCOL = 0;
// SOCKETD地址
#define SOCKET_SERVICE_ADDRESS "natvie_socket_test"
// 允许连接的长度
#define SOCKET_NUMBER 20
// 读取数据大小
#define BUFFER_SZIE 80/*** 主文件入口
*/
int main (void) {// socket 文件描述符int socketFd;// 客户端的socket 文件描述符int clintSocetFd;// socket 实体类struct sockaddr_un serviceAddr, clientAddr;// 地址长度socklen_t serviceLen; socklen_t clintLen;// 数据读取char buffer[BUFFER_SZIE];// 创建socketsocketFd = socket(SOCKET_DOMIAN, SOCKET_TYPE, SOCKET_PROTOCOL);if (socketFd == -1){printf("创建 socket 失败\n");exit(1);}// 清空memset(&serviceAddr, 0 ,sizeof(sockaddr_un));serviceAddr.sun_family = AF_UNIX;// 设置地址strncpy(serviceAddr.sun_path, SOCKET_SERVICE_ADDRESS, sizeof(serviceAddr.sun_path) - 1);// 计算长度serviceLen = sizeof(serviceAddr.sun_family) + sizeof(serviceAddr.sun_path);// 绑定地址int bindResult = bind(socketFd,  (struct sockaddr *)&serviceAddr, serviceLen);if (bindResult == -1){printf("绑定 socket 失败\n");exit(1);}// 绑定成功后 需要监听int listenerResult = listen(socketFd, SOCKET_NUMBER);if (listenerResult == -1){printf("监听 socket 失败\n");exit(1);}printf("服务端 socket建立 等待连接\n");// 死循环等待连接while (1){// 等待接收客户端连接, 同时客户端的信息会写入该类, 会阻塞在这里clintLen = sizeof(struct sockaddr_un);int connectFd = accept(socketFd, (struct sockaddr *)&clientAddr, &clintLen);if (connectFd == -1) {printf("监听 socket 失败\n");exit(1);}// 创建成功,循环读取消息while (1){// 0 为关闭 -1 为异常int dataSize = read(connectFd, &buffer, sizeof(buffer));if (dataSize == 0){printf("客户端关闭连接\n");} else if (dataSize == -1){printf("读取异常\n");exit(1);} else {printf("客户端发送了: %s\n" ,buffer);for (int i = 0; i < sizeof(buffer); i++){buffer[i] = toupper(buffer[i]);}write(connectFd, buffer, sizeof(buffer));}}}// 退出for循环 关闭socketprintf("服务端关闭连接\n");close(socketFd);return 0;
}

客户端

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// scoket相关
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/un.h>
#include <unistd.h>// 协议域
int SOCKET_DOMIAN = AF_UNIX;
// 类型
int SOCKET_TYPE = SOCK_STREAM;
// 协议,常用tcp udp
int SOCKET_PROTOCOL = 0;
// SOCKETD地址
#define SOCKET_SERVICE_ADDRESS "natvie_socket_test"
// 允许连接的长度
#define SOCKET_NUMBER 20
// 读取数据大小
#define BUFFER_SZIE 80
// 输入最大行
#define MAXLINE 80int main(void) {// 客户端操作符int serviceFd;sockaddr_un clientAddr;// 地址长度socklen_t clientLen;// 从输入框输入流char winBuffer[BUFFER_SZIE];serviceFd = socket(SOCKET_DOMIAN, SOCKET_TYPE, SOCKET_PROTOCOL);if (serviceFd == -1){printf("创建 socket 失败");exit(0);}// 清空数据memset(&clientAddr, 0, sizeof(struct sockaddr_un));// 赋值地址clientAddr.sun_family = AF_UNIX;strncpy(clientAddr.sun_path, SOCKET_SERVICE_ADDRESS, sizeof(clientAddr.sun_path) - 1);clientLen = sizeof(clientAddr);printf("socket地址: %s\n", clientAddr.sun_path);int connectResult = connect(serviceFd, (struct sockaddr *)&clientAddr, clientLen);if (connectResult == -1){perror("连接 socket 失败");exit(0);}printf("请输入对应的数据:");while (fgets(winBuffer, MAXLINE, stdin)){// 把读到的数据给服务端int wirteResult = write(serviceFd, winBuffer, sizeof(winBuffer));printf("写入服务端数据 %i", wirteResult);// 阻塞读取服务端数据int readResult =read(serviceFd, winBuffer, sizeof(winBuffer));if (readResult <= 0) {  printf("服务端异常或者已关闭\n");break;}else {  printf("接收到服务端的消息: %s \n",winBuffer);}printf("请输入对应的数据:");}close(serviceFd);return 0;   
}

Epoll

上面提到的socket 的 accept 和 read 等方法都是阻塞方法,等多客户端无法实现实时的读取和监听,如果每个客户端开对应的线程则比较耗资源。因此有了Epoll 等待唤醒。Epoll 有如下方法。

创建Epoll

需要传入一个数量,该数量大于0即可。并会返回一个文件描述符

// 创建 epoll, 参数size 实际没作用,但是要大于0 
int epollFd = epoll_create(EPOLL_SZIE);

添加或删除Epoll

第二参数控制 添加还是删除
第三个参数为需要监听的文件操作符
第四个参数为 epoll_event 类型
data.fd 可以存储对应的文件ID
events属性表示数据类型 EPOLLIN

 int addResult = epoll_ctl(epollFd, EPOLL_CTL_ADD, serviceFd, &epollEvent); 

等待消息返回Epoll

events参数传入事件数组,内核会对应写入的数组。可通过 events.data.fd 获取对应的客户端。
size 表示每次可以处理的最大事件数量
最后参数代表等待的事件,0为马上返回,-1为阻塞等待

 int eventNum = epoll_wait(epollFd, events, EPOLL_EVENT_MAX_SZIE, EPOLL_TIEM_OUT);

演示代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
// scoket相关
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/un.h>
#include <unistd.h>
// epoll 相关
#include <sys/epoll.h>// 协议域
int SOCKET_DOMIAN = AF_UNIX;
// 类型
int SOCKET_TYPE = SOCK_STREAM;
// 协议,常用tcp udp
int SOCKET_PROTOCOL = 0;
// SOCKETD地址
#define SOCKET_SERVICE_ADDRESS "epoll_socket_test"
// 允许连接的长度
#define SOCKET_NUMBER 20
// 读取数据大小
#define BUFFER_SZIE 80
// epoll大小
#define EPOLL_SZIE 80
// epoll消息大小
#define EPOLL_EVENT_MAX_SZIE 20
// epoll消息超时
#define EPOLL_TIEM_OUT -1// 采用抽象作用域
int main(void) {int serviceFd;sockaddr_un serviceAddr;socklen_t serviceLen, clientLen;char buffer[BUFFER_SZIE];serviceFd = socket(SOCKET_DOMIAN, SOCKET_TYPE, SOCKET_PROTOCOL);if (serviceFd == -1) {perror("创建 socket 失败");exit(0);}memset(&serviceAddr, 0 , sizeof(struct sockaddr) - 1);serviceAddr.sun_family = AF_UNIX;// 这里采用抽象的协议, 第一位要给0serviceAddr.sun_path[0] = 0;strcpy(serviceAddr.sun_path + 1, SOCKET_SERVICE_ADDRESS);// serviceLen = sizeof(serviceAddr.sun_family) + strlen(SOCKET_SERVICE_ADDRESS) + 1;serviceLen = sizeof(serviceAddr.sun_family) + sizeof(serviceAddr.sun_path);printf("绑定的地址为:%s \n", serviceAddr.sun_path);// 绑定int bindResult = bind(serviceFd, (struct sockaddr *)&serviceAddr, serviceLen);if (bindResult == -1) {perror("绑定 socket 失败");exit(0);}int listenResult = listen(serviceFd, SOCKET_NUMBER);if (listenResult == -1) {perror("监听 socket 失败");exit(0);}// 创建 epoll, 参数size 实际没作用,但是要大于0 int epollFd = epoll_create(EPOLL_SZIE);if (epollFd == -1){perror("创建 epoll 失败");exit(0);}// 将对应的文件夹 添加 epoll中epoll_event epollEvent;/* *** 注意****epoll_data 是一个联合体结构成员,所有的成员公用一段内存,因此实际上只能保留一个成员它只会保存最后一个被赋值的成员,所以不要data.fd,data.ptr都赋值*/epollEvent.data.fd = serviceFd;epollEvent.events = EPOLLIN;// 循环检测 委托内核去处理// 当内核检测到事件到来时会将事件写到这个结构体数组里struct epoll_event events[EPOLL_EVENT_MAX_SZIE];int addResult = epoll_ctl(epollFd, EPOLL_CTL_ADD, serviceFd, &epollEvent); if (addResult == -1){perror("添加到 epoll 失败");exit(0);}printf("开始接收消息\n");while (1){// 等待有没消息返回// tiemOut 为0 马上返回 为-1 代表阻塞// maxevents:表示每次能处理的最大事件数,告之内核这个events有多大;这个maxevents的值不能大于创建epoll_create()时的size;int eventNum = epoll_wait(epollFd, events, EPOLL_EVENT_MAX_SZIE, EPOLL_TIEM_OUT);printf("收到消息数量: %i \n", eventNum);for (int i = 0; i < eventNum; i++){// 有连接请求到来,走到这里if (events[i].data.fd == serviceFd){// 客户端信息填充struct sockaddr_un clientAddr;socklen_t clientLen = sizeof(clientAddr);int connectFd = accept(serviceFd, (struct sockaddr *)&clientAddr, &clientLen);if (connectFd == -1){perror("接收客户端失败");exit(0);}printf("接收到新的客户端\n");//将用于通信的文件描述符挂到epoll树上epollEvent.data.fd = connectFd;epollEvent.events = EPOLLIN;epoll_ctl(epollFd, EPOLL_CTL_ADD, connectFd, &epollEvent);} else{// 其他的为客户端发送的值// 通信也有可能是写事件if (events[i].events & EPOLLOUT){//这里先忽略写事件continue;}char buf[1024]={0};int count = read(events[i].data.fd, buf, sizeof(buf));if (count == 0){// 关闭了客户端要关闭printf("收到客户端关闭");close(events[i].data.fd);epoll_ctl(epollFd, EPOLL_CTL_DEL, events[i].data.fd, NULL);continue;}if (count == -1){perror("接收消息异常退出");exit(0);}printf("收到了消息事件:%s \n", buf);for (int i = 0; i < sizeof(buf); i++){buf[i] = toupper(buf[i]);}write(events[i].data.fd, buf, sizeof(buf));}}}close(serviceFd);return 0;
}

SocketPair

如果是单纯1对1通讯,可通过 socketPair 快速建立服务端和客户端

创建socketPair

创建一个数组存储对应的ID,创建的socket 会放在该数组里面

int socketFds[2];
// 创建socke 第一个作为服务端 第二个作为客户端
int result = socketpair(SOCKET_DOMIAN, SOCKET_TYPE, SOCKET_PROTOCOL, socketFds);

设置对应的属性

以下代码设置对应的缓冲区

socklen_t len = sizeof(BUFFER_SZIE);
setsockopt(socketFds[0], SOL_SOCKET, SO_SNDBUF, &BUFFER_SZIE, len);
setsockopt(socketFds[0], SOL_SOCKET, SO_RCVBUF, &BUFFER_SZIE, len);
setsockopt(socketFds[1], SOL_SOCKET, SO_SNDBUF, &BUFFER_SZIE, len);
setsockopt(socketFds[1], SOL_SOCKET, SO_RCVBUF, &BUFFER_SZIE, len);

演示代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>#include <sys/types.h>
#include <sys/socket.h>#include <unistd.h>// 协议域
int SOCKET_DOMIAN = AF_UNIX;
// 类型
int SOCKET_TYPE = SOCK_STREAM;
// 协议,常用tcp udp
int SOCKET_PROTOCOL = 0;
// 缓存区大小
int BUFFER_SZIE = 1024;int main(void) {int socketFds[2];// 创建socke 第一个作为服务端 第二个作为客户端int result = socketpair(SOCKET_DOMIAN, SOCKET_TYPE, SOCKET_PROTOCOL, socketFds);if (result == -1){perror("创建 socketPair 错误");exit(0);}/*** 第一个参数socket是套接字描述符。* 第二个参数level是被设置的选项的级别,如果想要在套接字级别上设置选项,就必须把level设置为 SOL_SOCKET。* option_name指定准备设置的选项,option_name可以有哪些取值,这取决于level,在套接字级别上(SOL_SOCKET)* 主要设置缓存区大小*/socklen_t len = sizeof(BUFFER_SZIE);setsockopt(socketFds[0], SOL_SOCKET, SO_SNDBUF, &BUFFER_SZIE, len);setsockopt(socketFds[0], SOL_SOCKET, SO_RCVBUF, &BUFFER_SZIE, len);setsockopt(socketFds[1], SOL_SOCKET, SO_SNDBUF, &BUFFER_SZIE, len);setsockopt(socketFds[1], SOL_SOCKET, SO_RCVBUF, &BUFFER_SZIE, len);int pid = fork();printf("孵化出来的进程号为 %i \n", pid);// 在子进程为0if (!pid){printf("执行子进程\n");// 为子进程close(socketFds[0]);int count = 0;while (1){// 休眠一秒sleep(1);// 发送给服务端端write(socketFds[1], &count, sizeof(count));int size = read(socketFds[1], &count, sizeof(count));printf("收到服务端数据 %i \n", count);++count;}} else {printf("执行父进程\n");// 为父进程close(socketFds[1]);int count = 0;while (1){   // 读取客户端数据int size = read(socketFds[0], &count, sizeof(count));if (read <= 0){perror("客户端异常退出 \n");exit(0);}printf("收到客户端数据 %i \n", count);++count;// 发送给客户端write(socketFds[0], &count, sizeof(count));}}
}

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

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

相关文章

数据结构系列-插入排序和希尔排序

&#x1f308;个人主页&#xff1a;羽晨同学 &#x1f4ab;个人格言:“成为自己未来的主人~” 排序的概念 常见的排序算法&#xff1a; 插入排序 直接插入排序是一种简单的插入排序法&#xff0c;其基本思想是&#xff1a; 把待排序的记录按其关键码值的大小逐个插入到…

API 技术开发分享:连接电商平台数据获取的桥梁

在当今数字化的时代&#xff0c;API&#xff08;Application Programming Interface&#xff0c;应用程序编程接口&#xff09;技术成为了实现不同系统之间通信和数据交换的关键。它就像是一座无形的桥梁&#xff0c;使得各种应用能够相互协作&#xff0c;共享资源&#xff0c;…

react.16+

1、函数式组件 在vite脚手架中执行&#xff1a; app.jsx: import { useState } from react import reactLogo from ./assets/react.svg import viteLogo from /vite.svg import ./App.cssfunction App() {console.log(this)return <h2>我是函数式组件</h2> }exp…

请你谈谈:vue的渲染机制(render)- 1 原理讲解

Vue 是如何将一份模板转换为真实的 DOM 节点的&#xff0c;又是如何高效地更新这些节点的呢&#xff1f;我们接下来就将尝试通过深入研究 Vue 的内部渲染机制来解释这些问题。 1 虚拟 DOM <template><div id"app">this is son component</div> &…

《javaEE篇》--阻塞队列详解

阻塞队列 阻塞队列概述 阻塞队列也是一种队列&#xff0c;和普通队列一样遵循先进先出的原则&#xff0c;但是阻塞队列相较于普通队列多了两项功能阻塞添加和阻塞移除&#xff0c;使得阻塞队列成为一种线程安全的数据结构 阻塞添加&#xff1a;当队列满的时候继续入队就会阻…

UE4 UnrealPak加密功能(配置AES encrypt key)

本文的重点在于如何使用UnrealPak的加密功能&#xff0c;以及相关的UE4源代码学习。本文参考了&#xff1a;https://www.cnblogs.com/shiroe/p/14803859.html 。 设置密钥 在编辑、项目设置中找到下面栏目&#xff0c;并点击“生成新的加密密钥”&#xff0c;就可以为Unreal P…

unity2D游戏开发10生命条脚本

HitPoints 在ScriptableObjects文件夹中创建新的脚本,叫HitPoint using System.Collections; using System.Collections.Generic; using UnityEngine;//创建条目,方便轻松创建HitPoints的实例 [CreateAssetMenu(menuName ="HitPoints")] public class HitPoints :…

锅总介绍CNCF主要目标、全景图及发展历史

一、CNCF简介 云原生计算基金会&#xff08;Cloud Native Computing Foundation&#xff0c;简称 CNCF&#xff09;是一个成立于 2015 年的非营利性组织&#xff0c;隶属于 Linux 基金会。CNCF 的主要目标是通过开源软件推动云原生计算技术的发展和普及&#xff0c;帮助企业更…

四、使用renren-generator生成基本代码

1、打开generator.properties配置文件&#xff0c;修改配置 主要修改包名、模块名、前缀信息 2、修改application.yml配置文件中的数据库信息 3、启动项目 直接访问代码生成器 http://localhost/#generator选择表&#xff0c;点击生成代码即可

怎么使用github上传XXX内所有文件

要将 目录中的所有文件上传到 GitHub&#xff0c;你可以按照以下步骤进行&#xff1a; 创建一个新的 GitHub 仓库 登录到你的 GitHub 账户。 点击右上角的加号&#xff08;&#xff09;&#xff0c;选择 “New repository”。 输入仓库名称&#xff08;例如&#xff1a;202407…

滑动窗口练习6-找到字符串中所有字母异位词

题目链接&#xff1a;**. - 力扣&#xff08;LeetCode&#xff09;** 题目描述&#xff1a; 给定两个字符串 s 和 p&#xff0c;找到 s 中所有 p 的 异位词 的子串&#xff0c;返回这些子串的起始索引。不考虑答案输出的顺序。 异位词 指由相同字母重排列形成的字符串&#…

《程序猿入职必会(6) · 返回结果统一封装》

&#x1f4e2; 大家好&#xff0c;我是 【战神刘玉栋】&#xff0c;有10多年的研发经验&#xff0c;致力于前后端技术栈的知识沉淀和传播。 &#x1f497; &#x1f33b; CSDN入驻不久&#xff0c;希望大家多多支持&#xff0c;后续会继续提升文章质量&#xff0c;绝不滥竽充数…

Profinet从站转TCP/IP协议转化网关(功能与配置)

如何将Profinet和TCP/IP网络连接通讯起来呢?近来几天有几个朋友问到这个问题&#xff0c;那么作者在这里统一说明一下。其实有一个不错的设备产品可以很轻易地解决这个问题&#xff0c;名为JM-DNT-PN。接下来作者就从该设备的功能及配置详细说明一下。 一&#xff0c;设备主要…

el-table合计行更新问题

说明&#xff1a;在使用el-table自带的底部合计功能时&#xff0c;初始界面不会显示合计内容 解决方案&#xff1a;使用 doLayout()方法 updated() {this.$nextTick(() > {this.$refs[inventorySumTable].doLayout();});},完整代码&#xff1a; // show-summary&#xff1a…

Bugku的web题目get,post

1.web基础$_GET http://114.67.175.224:17587/ OK明显的代码审计题目。 让我们看看代码&#xff0c;先get获取what参数变量&#xff0c;如果what变量‘flag’&#xff0c;输出flag。 该题为GET传参&#xff0c;可直接在url后面加参数 在url后加上?whatflag 即可获得flag 2…

科普文:科普文:springcloud之-Hystrix服务容错

Hystrix概念 Hystrix 服务容错保护 的概念和说明 这就是大名鼎鼎的&#xff1a;豪猪 豪猪的英文就是&#xff1a;Hystrix&#xff0c;国外一些大牛的程序员在给自己的架构起名字的时候&#xff0c;往往就这么特别。哪天咱们中国人自己也能写出些架构&#xff0c;咱们就按照中…

2024后端开发面试题总结

一、前言 上一篇离职贴发布之后仿佛登上了热门&#xff0c;就连曾经阿里的师兄都看到了我的分享&#xff0c;这波流量真是受宠若惊&#xff01; 回到正题&#xff0c;文章火之后&#xff0c;一些同学急切想要让我分享一下面试内容&#xff0c;回忆了几个晚上顺便总结一下&#…

【VS2019安装+QT配置】

【VS2019安装QT配置】 1. 前言2. 下载visual studio20193. visual studio2019安装4. 环境配置4.1 系统环境变量配置4.2 qt插件开发 5. Visual Studio导入QT项目6. 总结 1. 前言 前期安装了qt&#xff0c;发现creator编辑器并不好用&#xff0c;一点都不时髦。在李大师的指导下&…

C++画蜡烛图

GPT-4o (OpenAI) 在 C 中绘制蜡烛图通常不像在高级语言&#xff08;如 Python&#xff09;中那么简单&#xff0c;因为 C 并没有内置的图形绘制库。然而&#xff0c;您可以使用一些第三方库来完成这项任务&#xff0c;比如使用 Qt 或者 SFML 等图形库。这里我们以 Qt 库为例&a…

PM2 快速上手指南

PM2是 Node.js 的优秀运行时管理工具&#xff0c;专为简化和优化 Node.js 应用程序的生产部署与运行而设计。 PM2 官网链接: https://pm2.keymetrics.io/ 1.PM2 的优势 持续运行&#xff1a;即使应用出错或崩溃&#xff0c;也能自动重启。负载均衡&#xff1a;智能地自动分…