C++实现集群聊天服务器
C++实现集群聊天服务器
JSON
Json是一种轻量级的数据交换模式(也叫做数据序列化方式)。Json采用完全独立于编程语言的文本格式来存储和表示数据。见解和清晰的层次结构使得Json称为理想的数据交换语言。易于阅读和编写。同时也易于支持机器解析和生成,有效地提升网络传出效率。
这里讲的网络传输,就涉及到序列化和反序列化。以客户端和服务器端通信为例,一般情况下,客户端给服务器端发送信息,发送的信息可能是字符串、整型等信息,需要先转化为字节流数据,这就是序列化;同样,服务器接收到客户端发来的字节流信息,需要转化成原始的数据格式,这就是反序列化。
JSON for Modern C++是一个C++下的JSON库,具有以下特点:直观的语法、仅需使用头文件json.hpp
依赖、C++11标准编写、类似于STL使用json、STL和json可以互相转化、严谨的测试(所有类都经过严格的单元测试)。
数据序列化
在网络中,常见的数据传输序列化格式有XML、Json、ProtoBuf,其中ProtoBuf最为常用,其数据压缩编码传输占用带宽小,同样的数据信息,是Json的1/10,XML的1/20,但是使用起来稍微比Json复杂。
Json使用
头文件引入和重命名#include"json.hpp" using json = nlohmann::json;
。然后就可以是使用json类似于对象的使用方法使用json了。
#include"json.hpp"
using json = nlohmann::json;
#include <iostream>
#include <string>
using namespace std;void func1()
{json js;js["from"] = "zhangsan";js["message_type"] = 2;js["to"] = "lisi";js["message"] = "Hi, what are you doing?";cout << js << endl;string str=js.dump();//序列化,转化成字符串格式cout<<str.c_str()<<endl;// 模拟从网络接收到json字符串,通过json::parse函数把json字符串字节流转化为json对象json js2 = json::parse(temp);cout << js2 << endl;
}
void func2()
{json js;js["id"] = {1, 2, 3, 4, 5};js["name"] = "zhang san";js["msg"]["zhang san"] = "hello world";js["msg"]["liu shuo"] = "hello china";js["msg"] = {{"zhang san", "hello world"}, {"liu shuo", "hello china"}};cout << js << endl;
}
int main()
{func1();func2();return 0;
}
key采用哈希表,是无顺序的结构。
json的序列化——json_obj.dump()
。json反序列化——json::parse(json_str)
。
cmake常规使用
首先给出一个代码样例,通过代码样例基本上可以看懂一些常用的cmake命令:
cmake_minimum_required(VERSION 3.0) #CMake最小版本project(main)#定义当前工程的名字# set表示创建一个变量,并初始化对应的值
set(CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS} -g) #配置编译选项# include_directories()#头文件搜索目录# link_directories() #库文件搜索目录# 设置需要编译的源文件列表,其实也就是定义一个SRC_LIST变量名
set(SRC_LIST ./muduo_server.cpp)# 设置可执行文件最终存储的目录
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)# 把指定目录下的所有源文件名字放入变量名SRC_LIST里面
# aux_source_directory(./ SRC_LIST)# 表示生成可执行文件server,由SRC_LIST变量所定义的源文件编译而来
add_executable(server ${SRC_LIST})# 表示这个server目标程序,需要连接 muduo_net muduo_base pthread 等库文件
target_link_libraries(server muduo_net muduo_base pthread)
一般C++开源项目标准目录结构如下图所示:
一般在build目录下进行cmake ..
进行编译,然后会在build目录下生成编译过程中的中间文件,其中会存在一个Makefile
文件,在执行make
命令来生成最终的可执行文件。
PROJECT_NAME
: 通过 project() 指定项目名称
PROJECT_SOURCE_DIR
: 工程的根目录
PROJECT_BINARY_DIR
: 执行 cmake 命令的目录
CMAKE_CURRENT_SOURCE_DIR
: 当前 CMakeList.txt 文件所在的目录
CMAKE_CURRENT_BINARY_DIR
: 编译目录,可使用 add subdirectory 来修改
EXECUTABLE_OUTPUT_PATH
: 二进制可执行文件输出位置
LIBRARY_OUTPUT_PATH
: 库文件输出位置
BUILD_SHARED_LIBS
: 默认的库编译方式 ( shared 或 static ) ,默认为 static
CMAKE_C_FLAGS
: 设置 C 编译选项
CMAKE_CXX_FLAGS
: 设置 C++ 编译选项
CMAKE_CXX_FLAGS_DEBUG
: 设置编译类型 Debug 时的编译选项
CMAKE_CXX_FLAGS_RELEASE
: 设置编译类型 Release 时的编译选项
CMAKE_GENERATOR
: 编译器名称
CMAKE_COMMAND
: CMake 可执行文件本身的全路径
CMAKE_BUILD_TYPE
: 工程编译生成的版本, Debug / Release
Muduo
muduo网络库给用户提供了两个主要的类:
- TcpServer:用于编写服务器程序的
- TcpClient:用于编写客户端程序的
epoll+线程池:
优点:能够把网络I/O的代码和业务代码区分开、用户的断开和连接,用户可读写事件
Muduo服务端
下面提供了muduo进行服务器I/O和worker线程分离的代码示例:
#include <muduo/net/TcpServer.h>
#include <muduo/net/EventLoop.h>
#include <iostream>
#include <functional>
#include<string>
using namespace std;
using namespace muduo;
using namespace muduo::net;// 通用模板
class ChatServer
{
public:ChatServer(EventLoop *loop,const InetAddress &listenAddr,const string &nameArg) : _tcpserver(loop, listenAddr, nameArg), _loop(loop){// 给服务器注册用户连接的创建和断开的回调_tcpserver.setConnectionCallback(bind(&ChatServer::onConnection, this, placeholders::_1));// 给服务器注册用户读写事件回调_tcpserver.setMessageCallback(bind(&ChatServer::onMessage, this, placeholders::_1, placeholders::_2, placeholders::_3));// 设置服务器线程数量,1个I/O线程,3个worker线程_tcpserver.setThreadNum(4);}//开启事件循环void start(){_tcpserver.start();}private:// 专门处理用户的连接创建和断开 epoll、listenfd、acceptvoid onConnection(const TcpConnectionPtr &conn){if(conn->connected()){cout << conn->peerAddress().toIpPort() << " -> " << conn->localAddress().toIpPort() << " state online;"<<endl;}else{cout << conn->peerAddress().toIpPort() << " -> " << conn->localAddress().toIpPort() << " state offline;" << endl;conn->shutdown();//关闭连接}}// 专门处理用户的读写事件void onMessage(const TcpConnectionPtr &conn, // 连接Buffer *buffer, // 缓冲区Timestamp time) // 接收到信息的时间信息{string buf = buffer->retrieveAllAsString();cout << "recv data: " << buf << " time: " << time.toString() << endl;conn->send(buf);}TcpServer _tcpserver;EventLoop *_loop;
};
int main()
{EventLoop loop;InetAddress addr("127.0.0.1", 6000);//本机地址ChatServer server(&loop,addr,"ChatServer");//将listenfd通过epoll_ctl ->(传递给) epollserver.start();//按照epoll_wait以阻塞方式等待新用户连接,已连接用户的读写事件等loop.loop();return 0;
}
编译命令:
server:muduo_server.cppg++ $+ -o $@ -lmuduo_net -lmuduo_base -lpthread -std=c++11
$+
:表示依赖项,这里表示muduo_server.cpp 。$@
:表示目标项,这里表示server。
基于muduo网络库开发服务器程序,大概步骤如下:
1、组合TcpServer对象
2、创建EventLoop事件循环对象的指针
3、明确TcpServer构造函数需要什么参数(TcpServer无默认构造函数),输出ChatServer构造函数
4、在当前服务器的构造函数中,注册处理连接的回调函数和处理事件的连接函数
5、设置合适的服务器端线程数量,muduo会自己分配I/O线程和worker线程(一般设置1个I/O,n-1个worker线程)
负载均衡器
一台服务器在32位Linx系统环境下,大致的并发量sockfd大约是1024个,大约支持20000个人进行同时聊天。使用ulimit -n
命令查看系统允许当前用户进程打开的文件数限制,一般情况下每个进程最多允许打开1024个文件,还需要去除给当前用户进程必然打开的标准输入、标准输出、标准错误、服务器监听、进程通信等文件,剩下可以给客户端socket连接的文件数大概只有1014个左右,也就是说,基于Linux的通信程序最多运行同时1024个TCP并发连接。Linux下高并发socket最大连接数所受的各种限制点击查看更多。
在实际环境中可能会存在多个服务器同时在后台运行,当开始通信时,需要选定聊天的服务器。
LVS:负载均衡器常使用的设备。
nginx负载均衡器:相当于把服务器串联起来,在用户连接服务器后,ngnix负载均衡器将对client分配服务器,如果一台服务器时支持2W用户的连接,那么三台服务器就支持6W用户的连接。
聊天服务器属于长连接的业务。
redis是基于发布-订阅模式,类似于设计模式的观察者模式。
nginx安装
nginx在1.9版本之前只支持HTTP协议的web服务器的负载均衡,之后的版本开始支持TCP长连接的负载均衡。但是,nginx默认情况下没有编译TCP负载均衡模块,需要使用--with-stream
进行激活。
进入nginx官网下载对应的nginx的压缩包。
我们使用的ubuntu系统,所以下载第二列的nginx-1.25.2
版本,下载后得到安装包。使用tar -zxvf nginx-1.25.2.tar.gz
进行解压。解压后目录里面存在auto CHANGES CHANGES.ru conf configure contrib html LICENSE man README src
等文件夹和文件。在执行./configure --with-stream
开启基于TCP的负载均衡。
安装过程中可能存在库文件的丢失,这里我遇到了zlib,PCRE等缺失。可以按照下方命令进行安装:
sudo apt install zlib1g
sudo apt install zlib1g-dev
sudo apt-get install libpcre3 libpcre3-dev
然后再执行命令(可能需要管理员权限):
./configure --with-stream
make
make install
nginx -s reload #重新加载配置文件,例如添加服务器配置
nginx -s stop #停止nginx服务
需要在nginx的配置文件加入以下内容(nginx配置文件在/usr/local/nginx/conf/nginx.conf
,可执行文件在/usr/local/nginx/sbin/nginx
)
stream {upstream MyServer {server 127.0.0.1:6000 weight=1 max_fails=3 fail_timeout=30s;server 127.0.0.1:6002 weight=1 max_fails=3 fail_timeout=30s;}server {proxy_connect_timeout 1s;listen 8000;proxy_pass MyServer;tcp_nodelay on;}
}
上图显示了一个客户端连接服务器(连接的是nginx提供的ip和port),nginx将会给每个服务器按照配置进行分发客户端相应,间接等于客户端直连服务器(还是需要通过负载均衡器nginx),不影响用户之间的通信(非跨服务器通信)。
配置之后需要进行重新加载配置nginx -s reload
。
redis安装
ubuntu安装redis非常简单:
sudo apt-get install redis-server
查看redis的运行
ps -ef | grep redis
netstat -tanp
默认运行在6379端口tcp 0 0 127.0.0.1:6379 0.0.0.0:* LISTEN 144028/redis-server
。
redis是一个强大的缓存服务器,支持多种数据结构,如字符串、list列表、set集合、map映射表等结构,支持数据的持久化存储(存储在硬盘中),经常被用于高并发的服务器环境设计中。
redis其实类似于mysql,是client/server设计的。redis本身支持事务处理,多线程对key自增自减是线程安全的。
redis-cli #启动redis客户端
key-value
root@xiehou--ubuntu:~# redis-cli
127.0.0.1:6379> get "abc"
(nil)
127.0.0.1:6379> set "abc" 122
OK
127.0.0.1:6379> get "abc"
"122"
redis的发布-订阅机制:发布-订阅模式包含了两种角色,分别是消息的发布者和消息的订阅者。订阅者可以定义一个或者多个频道channel,发布者可以指向向某个频道channel发送消息,所有订阅此频道的订阅者都会收到此消息。
订阅的命令是subscribe。进入订阅模式后,处于此状态的客户端不能使用除subscribe、unsubscribe、psubscribe和punsubscribe这四个属于发布订阅的命令之外,否则就会报错。
进入订阅状态后客户端可能收到3种类型的回复。每种类型的回复都包含3个值,第一个值是消息的
类型,根据消类型的不同,第二个和第三个参数的含义可能不同。消息类型的取值可能是以下3个:
- subscribe:表示订阅成功的反馈信息。第二个值是订阅成功的频道名称,第三个是当前客户端订阅的频道数量。
- message:表示接收到的消息,第二个值表示产生消息的频道名称,第三个值是消息的内容。
- unsubscribe:表示成功取消订阅某个频道。第二个值是对应的频道名称,第三个值是当前客户端订阅的频道数量,当此值为0时客户端会退出订阅状态,之后就可以执行其他非"发布/订阅"模式的命令了。
带输入参数的调试gdb --args ./chatserver 127.0.0.1 8000
。或者先运行gdb ./chatserver
,然后在run 127.0.0.1 8000
。在某个cpp文件中打断点break chatservice.cpp:23
。
项目地址:https://gitee.com/xiehou-design/ChatServer
相关文章:
C++实现集群聊天服务器
C实现集群聊天服务器 JSON Json是一种轻量级的数据交换模式(也叫做数据序列化方式)。Json采用完全独立于编程语言的文本格式来存储和表示数据。见解和清晰的层次结构使得Json称为理想的数据交换语言。易于阅读和编写。同时也易于支持机器解析和生成&am…...
40 二叉树的直径
二叉树的直径 总结:两个节点之间最长路径 路径的结点数 - 1题解1 递归——DFS 给你一棵二叉树的根节点,返回该树的 直径。 二叉树的直径是指树中任意两个节点之间最长路径的长度。这条路径可能经过也可能不经过根节点 root 。 两节点之间路径的长度由…...
Thread.sleep(0)的作用是什么?
Thread.sleep(0) 的作用是让当前线程放弃剩余的时间片,允许其他具有相同优先级的线程运行。这种操作有时被称为“主动让出CPU时间片”或“线程主动让步”。 通常情况下,当一个线程执行到一段代码时,它会占用CPU的时间片,直到时间…...
浏览器指定DNS
edge--设置 https://dns.alidns.com/dns-query...
虚拟机安装 centos
title: 虚拟机安装 centos createTime: 2020-12-13 12:00:27 updateTime: 2020-12-13 12:00:27 categories: linux tags: 虚拟机安装 centos 路线图 主机(宿主机) —> centos --> docker --> docker 镜像 --> docker 容器 — docker 服务 1.前期准备 一台 主机 或…...
【计算机网络笔记九】I/O 多路复用
阻塞 IO 和 非阻塞 IO 阻塞 I/O 和 非阻塞 I/O 的主要区别: 阻塞 I/O 执行用户程序操作是同步的,调用线程会被阻塞挂起,会一直等待内核的 I/O 操作完成才返回用户进程,唤醒挂起线程非阻塞 I/O 执行用户程序操作是异步的…...
踩坑日记 《正确的使用Vuex》基于 uniapp Vue3 setup 语法糖 vuex4 项目 太多坑了要吐了
踩坑日记 《正确的使用Vuex》基于 uniapp Vue3 setup 语法糖 vuex4 项目 太多坑了要吐了 完美解决页面数据不刷新 或者数据慢一步刷新 页面使用html <template><view><template v-if"cartData.data.length>0"><!-- 自定义导航栏 --><…...
Python无废话-办公自动化Excel修改数据
如何修改Excel 符合条件的数据?用Python 几行代码搞定。 需求:将销售明细表的产品名称为PG手机、HW手机、HW电脑的零售价格分别修改为4500、5500、7500,并保存Excel文件。如下图 Python 修改Excel 数据,常见步骤: 1&…...
MySQL系统架构设计
MySQL 一、MySQL整体架构1.1 SQL接口1.2 解析器 Parser1.3 查询优化器 Optimizer1.3.1 逻辑优化1.3.2 物理优化1.3.3 explain1.4 缓存 Cache1.5 存储引擎 Stroage Management1.6 一条查询SQL的执行流程二、缓存池(Buffer Pool)2.1 Buffer Pool 预读机制2.2 Buffer Pool free链…...
Google vs IBM vs Microsoft: 哪个在线数据分析师证书最好
Google vs IBM vs Microsoft: 哪个在线数据分析师证书最好? 对目前市场上前三个数据分析师证书进行审查和比较|Madison Hunter 似乎每个重要的公司都推出了自己版本的同一事物:专业数据分析师认证,旨在使您成为雇主的下一个热门商品。 随着…...
数据链路层 MTU 对 IP 协议的影响
在介绍主要内容之前,我们先来了解一下数据链路层中的"以太网" 。 “以太网”不是一种具体的网络,而是一种技术标准;既包含了数据链路层的内容,也包含了一些物理层的内容。 下面我们再来了解一下以太网数据帧ÿ…...
一文拿捏基于redis的分布式锁、lua、分布式性能提升
1.分布式锁 jdk的锁: 1、显示锁:Lock 2、隐式锁:synchronized 使用jdk锁保证线程的安全性要求:要求多个线程必须运行在同一个jvm中 但现在的系统基本都是分布式部署的,一个应用会被部署到多台服务器上,s…...
机器学习必修课 - 如何处理缺失数据
运行环境:Google Colab 处理缺失数据可简单分为两种方法:1. 删除具有缺失值的列 2. 填充 !git clone https://github.com/JeffereyWu/Housing-prices-data.git下载数据集 import pandas as pd from sklearn.model_selection import train_test_split导…...
阿里云服务器方升架构、自研硬件、AliFlash技术创新
阿里云服务器技术创新:服务器方升架构及自研硬件、自研存储硬件AliFlash和阿里云异构计算加速平台,阿里云百科分享阿里云服务器有哪些技术创新: 目录 服务器技术创新 服务器方升架构及自研硬件 自研存储硬件AliFlash 阿里云异构计算加速…...
知识工程---neo4j 5.12.0+GDS2.4.6安装
(已安装好neo4j community 5.12.0) 一. GDS下载 jar包下载地址:https://neo4j.com/graph-data-science-software/ 下载得到一个zip压缩包,解压后得到jar包。 二. GDS安装及配置 将解压得到的jar包放入neo4j安装目录下的plugi…...
BUUCTF reverse wp 81 - 85
[SCTF2019]babyre 反编译失败, 有花指令 有一个无用字节, 阻止反编译, patch成0x90 所有标红的地方nop掉之后按p重申函数main和loc_C22, F5成功 int __cdecl main(int argc, const char **argv, const char **envp) {char v4; // [rspFh] [rbp-151h]int v5; // [rsp10h] [rb…...
数据结构-哈希表
系列文章目录 1.集合-Collection-CSDN博客 2.集合-List集合-CSDN博客 3.集合-ArrayList源码分析(面试)_喜欢吃animal milk的博客-CSDN博客 4.数据结构-哈希表_喜欢吃animal milk的博客-CSDN博客 文章目录 目录 系列文章目录 文章目录 前言 一 . 什么是哈希表&a…...
深度学习在图像识别领域还有哪些应用?
深度学习在图像识别领域的应用非常广泛,除了之前提到的图像分类、目标检测、语义分割和图像生成,还有其他一些应用。 图像超分辨率重建:深度学习技术可以用于提高图像的分辨率,例如通过使用生成对抗网络(GANÿ…...
前端项目练习(练习-005-webpack-03)
学习前,首先,创建一个web-005项目,内容和web-004一样。(注意将package.json中的name改为web-005) 前面的代码中,打包工作已经基本完成了,下面开始在本地启动项目。这里需要用到webpack-dev-serv…...
『力扣每日一题10』:字符串中的单词数
因为身体原因,再加上学校的 DeadLine 比较多,太忙太累,拖更了半个月。现在开始重拾日更,期待我们一起遇见更好的自己! 一、题目 统计字符串中的单词个数,这里的单词指的是连续的不是空格的字符。 请注意&a…...
初级篇—第三章多表查询
文章目录 为什么需要多表查询一个案例引发的多表连接初代查询笛卡尔积(或交叉连接)的理解 多表查询分类等值连接 vs 非等值连接自连接 vs 非自连接内连接VS外连接 SQL99语法实现多表查询内连接的实现外连接的实现左外连接右外连接满外连接 UNION的使用7种…...
<Xcode> Xcode IOS无开发者账号打包和分发
关于flutter我们前边聊到的初入门、数据解析、适配、安卓打包、ios端的开发和黑苹果环境部署,但是对于苹果的打包和分发,我只是给大家了一个链接,作为一个顶级好男人,我认为这样是对大家的不负责任,那么这篇就主要是针…...
vertx的学习总结2
一、什么是verticle verticle是vertx的基本单元,其作用就是封装用于处理事件的技术功能单元 (如果不能理解,到后面的实战就可以理解了) 二、写一个verticle 1. 引入依赖(这里用的是gradle,不会吧&#…...
网络安全内网渗透之DNS隧道实验--dnscat2直连模式
目录 一、DNS隧道攻击原理 二、DNS隧道工具 (一)安装dnscat2服务端 (二)启动服务器端 (三)在目标机器上安装客户端 (四)反弹shell 一、DNS隧道攻击原理 在进行DNS查询时&#x…...
探索ClickHouse——连接Kafka和Clickhouse
安装Kafka 新增用户 sudo adduser kafka sudo adduser kafka sudo su -l kafka安装JDK sudo apt-get install openjdk-8-jre下载解压kafka 可以从https://downloads.apache.org/kafka/下找到希望安装的版本。需要注意的是,不要下载路径包含src的包,否…...
基于监督学习的多模态MRI脑肿瘤分割,使用来自超体素的纹理特征(Matlab代码实现)
💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭&a…...
【RocketMQ】(八)Rebalance负载均衡
消费者负载均衡,是指为消费组下的每个消费者分配订阅主题下的消费队列,分配了消费队列消费者就可以知道去消费哪个消费队列上面的消息,这里针对集群模式,因为广播模式,所有的消息队列可以被消费组下的每个消费者消费不…...
线性筛和埃氏筛
线性筛: #define _CRT_SECURE_NO_WARNINGS #include<iostream> #include<cstdio> #include<cstdlib> #include<string> #include<cstring> #include<cmath> #include<ctime> #include<algorithm> #include<ut…...
【Java 进阶篇】JDBC ResultSet 类详解
在Java应用程序中,与数据库交互通常涉及执行SQL查询以检索数据。一旦执行查询,您将获得一个ResultSet对象,该对象包含查询结果的数据。本文将深入介绍ResultSet类,它是Java JDBC编程中的一个核心类,用于处理查询结果。…...
Centos7常用服务脚本(.service)
Centos7常用服务脚本(.service) 注意:[Service]中配置路径必须使用绝对路径。 启停: systemctl { start | stop | restart | reload } xxx.service 自启动: systemctl { enable | disable } xxx.service nginx.se…...
河北网站制作公司地址/seo短期课程
目录 ThreadPoolExecutor 源码阅读Executor 框架ExecutorExecutorServiceAbstractExecutorService构造器状态Worker 与任务调度提交任务线程池关闭ThreadPoolExecutor 源码阅读 读了一下 ThreadPoolExecutor 的源码(JDK 11), 简单的做个笔记. Executor 框架 Executor Executor …...
wordpress css3/有什么平台可以推广
//PS2键盘测试程序,可换行,按shift不放接着输入//可输出大写,按下CAPS输出大写,再次按下输出小写//此程序只用来测试,代码冗余,仅供参考,可根据需要自行删减//PA13->PS2.CLK PA15->PS2.D…...
.net做网站/竞价推广代运营
Excel表格中的迷你图表能够直观地向我们展示出数据的变化趋势。本文将介绍C#如何实现为表格数据生成迷你图表,以及修改和删除迷你图表的方法。下面将详细讲述。原Excel图表:一、添加迷你图表(折线图、柱形图、盈亏图)1.添加命名空间using System;using S…...
北斗导航2022最新版手机版/seo推广小分享
http://blog.csdn.net/u012926924/article/details/50606195 最简android之wifi调试 做android开发的时候,经常遇到的一个问题就是真机调试次数多了,会导致usb口,损坏,而且长期给手机充电也会损坏手机,所以我想了想是…...
mac做网站改html文件/seo关键字优化教程
文章目录一.决策树1. 乳腺癌数据集-分类2. 可视化1. 树的可视化:2. 特征重要性(feature importance)3. 回归树4. 优缺点与主要参数二. 集成的树1. 随机森林(random forest)示例特征重要性优缺点与关键参数2. 梯度提升决…...
做网站总结作文/广州seo教程
In this article, we will learn how to integrate and play a video using YouTube API in Android with Kotlin.在本文中,我们将学习如何在带有Kotlin的Android中使用YouTube API集成和播放视频。 The YouTube Android Player API enables you to incorporate vi…...