计划一个跨平台的即时通讯体系(采用华为云ECS服务器作为服务端 )

[复制链接]
online_admin LZK798897 发表于 2022-7-5 09:11:49 | 显示全部楼层 |阅读模式
avatarLZK798897
2022-7-5 09:11:49 217 0 看全部
【择要】 这篇文章介绍了ECS服务器从购买、设置,部署、计划代码的整个流程。以即时谈天体系的项目案例计划,团体介绍了ECS弹性云服务器的一个运用场景。 选择云服务器ECS的上风在于:无需自建机房,无需采购以及设置硬件办法,分钟级交付,快速部署,紧缩应用上线周期。使用也很简单,一个长途终端毗连上去就行。

即时通讯软件的出现使人与人之间的交流相处变得更加便捷。好友即使远隔千里,仍然可以互相通讯,使得交情得以长存;亲人即便因工作相隔很远,依然可以多多接洽,以便家人安心,本身舒心;正是即时通讯体系使得信息走向便捷化的道路,人与人之间的交流沟通更是方便。反过来也是信息交流的不可或缺,使得即时通讯体系更加具备研究价值,互联网对其的器重从不减少,反倒是与日俱增,人们对其的功能构想更是丰富多样,更是推动了互联网大环境的发展。
这篇文章就计划一个简单的即时通讯软件,也就是类似于QQ这种谈天软件,通过这个软件计划实现过程来相识TCP网络编程知识点、客户端计划思绪、公网服务器部署方式等知识;本身软件并不复杂,实现的都是一些基本功能,紧张是通过这套软件的计划过程来对应介绍干系的知识点。
软件团体包含了一个客户端、一个服务器。客户端采用QT计划,支持跨平台运行,服务器采用Linux体系运行,为了方便实现公网谈天,不范围于局域网,服务器采用了华为云的ECS弹性服务器,体系选择了ubuntu18.04 64位 。 服务器端存储数据的数据库采用华为云的mySQL云数据,华为云MySQL数据的使用方式在上篇文章里讲过了。客户端的数据库采用SQLITE,数据都是存储在本地的,存储一些谈天记录等信息。
下面是客户端的界面结果:
(1)这是客户端登录界面。客户端可以无穷运行,一个客户端就登录一个账号。

(2)这个截图清晰些

(3)这是登录之后的窗口

(4)这是两个好友登录之后,谈天的结果。登录必要登录服务器。


接下来就从服务器的购买、部署、开始介绍团体软件的计划。

华为云的ECS云服务器现在购买也挺便宜的,刚好赶上618活动。
官网地址: https://www.huaweicloud.com/
鼠标光标放在左上角的产品字样上,会弹出一个列表,介绍可以使用购买的产品。
可以看到,ECS服务器挺火的,HOT字样都打上了。

云服务器本身就类似一台长途电脑,配上公网IP,可以随时长途上去操作。有了这个服务器就很方便了,可以24小时不关机,也不用关心耗电标题,不用关心维护标题,只要本身代码没标题,就能不停稳固运行的跑。
假如初次使用服务器,也可以去免费领取一个的使用权,这个还是很方便的。
领取地址: https://www.huaweicloud.com/
就在华为云的首页,可以看到免费试用产品。

现在有点晚了,服务器已经被领取完了,天天早上9点半会发放。

我之前已经领取过了,以是这里就不管了。
领取也很简单,下面贴一下当初领取时的过程记录。
(1)起首点击你想要的设置和范例领取

(2)选择设置。假如有选择,当然选择最高设置。

(3)选择操作体系,这里截图上选择的是ubuntu20.04 64位,也有ubuntu18.04可以选择,还有其他体系,详细看本身的需求。

(4)这里设置服务器的root登录暗码,最好在这里就一次设置好,后面也不用再去修改。

(5)接下来就是按照流程点击下一步即可。

(6)在订单详情页面会看到资源正在创建,必要等待一段时间就会成功。

服务器买了之后,可以点击右上角的控制台按钮,去查看本身的服务器了。
在控制台页面,点击资源管理,可以看到本身有哪些实例可以使用,分别在什么地区。

点击进去本身的服务器详情页面。

服务器创建之后已经绑定了公网IP地址,这些就不用本身再次设置了。

接下来还必要设置一下安全组规则,开放一下外网访问的端口,假如不设置,不开放端口,外网无法访问服务器里的服务,这也是防火墙安全的控制本领。
点击添加规则按钮,设置本身必要开放的IP地址网段,端口范围就行了。

接下来点击页面上的长途登录按钮,登录一下服务器。

登录方式有多种,先选择一个默认方式,先体验一下,看看本身的服务器。

输入密登录即可。

登录之后进入下令行终端,熟悉的下令行出现了。

到此,一台全新的服务器已经搞定了,接下来就可以编写代码运行了。

在网页上直接登录,操作起来始终不方便,我在windows下一样寻常使用SecureCRT 登录服务器。
服务器支持SSH协议登录,登录是很方便。
只必要知道服务器的公网IP地址、体系的用户名、体系暗码即可。
公网地址就在服务器详情页面可以看到。用户名就是root,暗码就是本身创建服务器时设置的暗码。
下面就看一下登录结果:

点击接受并保存。

输入用户名、暗码即可登录上去。


到此,采用SSH协议、使用终端软件成功登录了服务器,接下来就可以正常的开发了。

(1) 服务器计划:
登录认证: 服务器从客户端接收账户和暗码,创建线程比对数据库中该用户账号、暗码,比对成功,则返回用户信息给客户端,确承认进行登录。
客户端毗连信息保存: 用户在客户端成功登录后,其所属账号、IP以及端标语被记录在数据库的毗连信息表中以便管理。此时用户状态被置为1。反之,用户在客户端退出时,其账号、IP被毗连信息表扫除,此时用户状态被置为0.
用户注册: 当新用户使用时,用户必要填写用户名和暗码。此时用户注册信息将存入数据库中,以备下次登录所需[10]。
增长好友: 服务触发增长好友的变乱哀求后,会即时将其账号信息存录数据库中进行管理。
删除好友: 服务器触发删除好友的变乱哀求后,会即时扫除数据库中该用户的账户信息。
查找好友: 服务器一旦触发查找好友的变乱哀求时,会即时比对数据库中信息表的好友信息,该账号信息存在就可成功找到。
修改好友: 服务器触发好友信息修改的变乱哀求后,会即时更新该用户的账户信息。
好友通讯: 服务器触发触发通讯的变乱哀求时,会即时查询数据库中好友毗连信息,再由此信息返回好友的ip地址及端标语给用户。
(2) 客户端计划:
用户注册: 用户在通讯体系的客户端登录界面上输入账号、暗码。接着,用户可单击按钮进行注册操作。此时,注册信息会即时发送给服务器,录入数据库进行管理。注册完,关闭客户端。下次登录时,用户通过客户端与服务器毗连,查找账户信息,服务器判断账户信息准确,即表明注册成功。
用户登录: 用户点击“登录”按钮,根据客户端输入的账号信息判断其是否为空。若为空,程序运行不实验下述操作:向服务器发送登录哀求。若不为空,程序运行即刻实验下述操作:向服务器发送登录哀求。
好友在线状态: 客户端收到来自服务器的好友在线信息,好友接洽人列表会发现此用户信息,表明该用户处于在线状态。用户可双击任一在线好友,双方即可进行交流。
一对一谈天: 用户双击接洽人列表中任一在线友人。这时,客户端会给服务器发哀求。服务器收到哀求后,即时给客户端发送有关好友ip地址和端标语的毗连信息。终极,双方实现信息交互。双方在谈天框中交流,客户端还含有好友谈天记录功能即消息管理。
群组谈天: 用户点击“群组”图标,客户端就会发送变乱哀求给服务器。服务器触发变乱哀求后,即时给客户端发送群聊的毗连信息,此毗连信息与群聊ip地址和端标语有关。终极,多人通讯的用户通过客户端谈天界面实现信息交互。

为了方便拷贝代码到服务器上去,我这里采用NFS服务器的方式拷贝,在服务器上搭建NFS服务器,共享一个目次出来,本地linux体系挂载,将代码这些文件拷贝过去。
(1)服务器端必要先安装服务器。root@ecs-348470:~# sudo apt-get install nfs-kernel-server(2)创建一个work目次方便当做共享目次使用root@ecs-348470:~# mkdir work(3)编写NFS设置文件再编写NFS服务的设置文件/etc/exports, 填入设置信息。/home/work *(rw,no_root_squash,sync,no_subtree_check,insecure)  (4)然后启动服务器即可/etc/init.d/nfs-kernel-server start #启动 NFS 服务本地我使用的是ubuntu18.04体系,挂载服务器的路径,将群聊代码拷贝上去。
wbyq@wbyq:~$ sudo mount -t nfs -o nolock 122.112.212.171:/home/work /home/wbyq/mnt/[sudo] wbyq 的暗码: wbyq@wbyq:~$ cd mnt/wbyq@wbyq:~/mnt$ lswbyq@wbyq:~/mnt$ sudo cp /mnt/hgfs/linux-share-dir/socket_app/* ./wbyq@wbyq:~/mnt$ lsclient_app.c  server_app.cwbyq@wbyq:~/mnt$这是服务器的第一版群聊代码,采用多线程方式处理客户端的毗连哀求。群聊模式下,服务器上紧张是毗连客户端之后,将消息转发给别的的客户端。
**代码已经拷贝上去: **

群聊服务器版本代码实现如下:
#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>#include <sys/types.h>          /* See NOTES */#include <sys/socket.h>#include <netinet/in.h>#include <arpa/inet.h>#include <stdio.h>#include <poll.h>#include <stdlib.h>#include <signal.h>#include <pthread.h>/*创建服务器:1. 创建socket套接字2. 绑定端标语(给进程固定端标语和IP地址)--65535之内。3. 设置监听的队列4. 等待客户端毗连./app 8080*/int server_fd;/*发出的消息结构*/struct send_pack{        char name[50];        char data[100];        char type; //0x1 表示上线,0x2表示下线 0x0表示正常谈天数据};/*保存毗连上服务器的客户端信息*/struct client_info{        int client_fd;        struct client_info *next;};struct client_info *LIST_HEAD=NULL;pthread_rwlock_t rwlock;struct client_info *List_CreateHead(struct client_info *head);void List_del(struct client_info *head,int client_fd);void List_add(struct client_info *head,int client_fd);/*信号处理函数*/void sighandler_func(int sig){        /*4. 完毕套接字*/        close(server_fd);        printf("进程正常退出....");        exit(0);}/*函数功能:线程处理子函数*/void *func_start(void *arg){        int client_fd=*(int*)arg;        free(arg);        //写加锁        pthread_rwlock_wrlock(&rwlock);        //添加节点        List_add(LIST_HEAD,client_fd);        //解锁        pthread_rwlock_unlock(&rwlock);                int cnt;        struct send_pack data_pack;        struct client_info *tmp_head;        int poll_stat;        struct pollfd poll_fd;        poll_fd.fd=client_fd;        poll_fd.events=POLLIN;        while(1)        {                poll_stat=poll(&poll_fd,1,-1);                if(poll_stat>0)                {                        cnt=read(client_fd,&data_pack,sizeof(struct send_pack));                        if(cnt==0)                        {                                //写加锁                                pthread_rwlock_wrlock(&rwlock);                                //删除节点                                List_del(LIST_HEAD,client_fd);                                //解锁                                pthread_rwlock_unlock(&rwlock);                                                                data_pack.type=2;//下线提示                                                                pthread_rwlock_rdlock(&rwlock);                                tmp_head=LIST_HEAD;                                while(tmp_head->next)                                {                                        tmp_head=tmp_head->next;                                        if(tmp_head->client_fd!=client_fd)                                        {                                                write(tmp_head->client_fd,&data_pack,sizeof(struct send_pack));                                        }                                }                                //解锁                                pthread_rwlock_unlock(&rwlock);                                break;                        }                        //加锁                        pthread_rwlock_rdlock(&rwlock);                        tmp_head=LIST_HEAD;                        while(tmp_head->next)                        {                                tmp_head=tmp_head->next;                                if(tmp_head->client_fd!=client_fd)                                {                                        write(tmp_head->client_fd,&data_pack,sizeof(struct send_pack));                                }                        }                        //解锁                        pthread_rwlock_unlock(&rwlock);                }                else if(poll_stat<0)                {                        perror("poll函数监听失败.");                        break;                }        }                /*6. 关闭毗连*/        close(client_fd);        pthread_exit(NULL);}int main(int argc,char **argv){        if(argc!=2)        {                printf("./app <创建绑定的服务器端标语>");                return 0;        }        /*初始化读写锁*/        pthread_rwlock_init(&rwlock,NULL);                /*绑定必要捕获的信号*/        signal(SIGINT,sighandler_func);        /*创建链表头*/        LIST_HEAD=List_CreateHead(LIST_HEAD);                /*1. 创建socket套接字*/        server_fd=socket(AF_INET,SOCK_STREAM,0);        if(server_fd<0)        {                printf("server:创建socket套接字失败.");                return 0;        }        /*2. 绑定端标语*/        struct sockaddr_in server_addr;        server_addr.sin_family=AF_INET;        server_addr.sin_port=htons(atoi(argv[1]));//把 16 位值从主机字节序转换成网络字节序        server_addr.sin_addr.s_addr=INADDR_ANY;//表示本地全部IP地址        if(bind(server_fd,(const struct sockaddr *)&server_addr,sizeof(struct sockaddr_in)))        {                printf("server:绑定端标语失败.");                return 0;        }        /*3. 设置监听队列*/        listen(server_fd,100);                /*4. 等待客户端毗连*/        struct sockaddr_in client_addr;        socklen_t addrlen;        pthread_t thread_id;        int *client_fd;        while(1)        {                addrlen=sizeof(struct sockaddr_in);                client_fd=malloc(sizeof(int));                *client_fd=accept(server_fd,(struct sockaddr *)&client_addr,&addrlen);                if(*client_fd<0)                {                        printf("server:处理客户毗连失败.");                        return 0;                }                printf("客户端的IP地址:%s",inet_ntoa(client_addr.sin_addr));                printf("客户端的端标语:%d",ntohs(client_addr.sin_port));                /*创建新的线程*/                if(pthread_create(&thread_id,NULL,func_start,client_fd))                {                        printf("线程创建失败.");                        break;                }                /*设置线程的分离属性*/                pthread_detach(thread_id);        }        close(server_fd);        return 0;}/*创建链表头*/struct client_info *List_CreateHead(struct client_info *head){        if(head==NULL)        {                head=malloc(sizeof(struct client_info));        }        return head;}/*添加节点*/void List_add(struct client_info *head,int client_fd){        struct client_info *p=head;        struct client_info *new_node;        while(p->next)        {                p=p->next;        }        new_node=malloc(sizeof(struct client_info));        if(new_node==NULL)printf("内存空间申请失败.");        new_node->client_fd=client_fd;        new_node->next=NULL;        p->next=new_node;}/*删除链表节点*/void List_del(struct client_info *head,int client_fd){        struct client_info *p=head;        struct client_info *old;        while(p->next)        {                old=p;                p=p->next;                if(p->client_fd==client_fd)                {                        old->next=p->next;                        free(p);                        break;                }        }}在服务器上编译运行代码:
root@ecs-348470:~/work# gcc server_app.c -o tcp_server -lpthreadroot@ecs-348470:~/work# lsserver_app.c  tcp_serverroot@ecs-348470:~/work# ./tcp_server 8899运行服务器群聊代码,在本地ubuntu体系再运行客户端代码,测试通讯结果:


在计划过程中,为了测试,代码写了两份,一份下令的行的谈天客户端,一份QT计划带界面的完备版本。

**下面这是下令行版本的群聊客户端代码: **
#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>#include <netinet/in.h>#include <arpa/inet.h>#include <sys/types.h>          /* See NOTES */#include <sys/socket.h>#include <stdio.h>#include <poll.h>#include <signal.h>#include <stdlib.h>#include <string.h>#include <pthread.h>/*客户端:1. 创建socket套接字2. 毗连指定的服务器*/int client_fd;/*信号处理函数*/void sighandler_func(int sig){        /*4. 完毕套接字*/        close(client_fd);        printf("进程正常退出....");        exit(0);}/*发出的消息结构*/struct send_pack{        char name[50];        char data[100];        char type; //0x1 表示上线,0x2表示下线 0x0表示正常谈天数据};void *func_start(void *arg){        /*1. 通讯*/        int client_fd=*(int*)arg;        struct send_pack data;        int cnt;        int poll_stat;        struct pollfd poll_fd;        poll_fd.fd=client_fd;        poll_fd.events=POLLIN;                while(1)        {                poll_stat=poll(&poll_fd,1,-1);                if(poll_stat>0)                {                        cnt=read(client_fd,&data,sizeof(struct send_pack));                        if(cnt==sizeof(struct send_pack))                        {                                if(data.type==0)                                {                                        printf("%s:%s",data.name,data.data);                                }                                else if(data.type==1)                                {                                        printf("%s 用户上线.",data.name);                                }                                else if(data.type==2)                                {                                        printf("%s 用户下线.",data.name);                                }                        }                        else if(cnt==0)                        {                                printf("服务器断开毗连.");                                break;                        }                }                else if(poll_stat<0)                {                        perror("poll函数实验错误.");                        break;                }        }        pthread_exit(NULL);}int main(int argc,char **argv){        if(argc!=4)        {                printf("./app <服务器IP地址> <服务器端标语> <用户名>");                return 0;        }                /*绑定必要捕获的信号*/        signal(SIGINT,sighandler_func);                /*1. 创建socket套接字*/        client_fd=socket(AF_INET,SOCK_STREAM,0);        if(client_fd<0)        {                printf("client:创建socket套接字失败.");                return 0;        }        /*2. 毗连服务器*/        struct sockaddr_in server_addr;        server_addr.sin_family=AF_INET;        server_addr.sin_port=htons(atoi(argv[2]));//把 16 位值从主机字节序转换成网络字节序        server_addr.sin_addr.s_addr=inet_addr(argv[1]);//服务器IP地址        if(connect(client_fd,(const struct sockaddr *)&server_addr,sizeof(struct sockaddr_in)))        {                printf("client: 毗连服务器失败.");                return 0;        }                /*3. 创建子线程*/        pthread_t thread_id;        if(pthread_create(&thread_id,NULL,func_start,&client_fd))        {                printf("线程创建失败.");                return 0;        }        /*4. 设置线程的分离属性*/        pthread_detach(thread_id);                struct send_pack data_pack;        strcpy(data_pack.name,argv[3]); //赋值用户名        data_pack.type=1;//上线提示        /*上线提示*/        write(client_fd,&data_pack,sizeof(struct send_pack));                /*进行正常谈天*/        data_pack.type=0;//正常谈天        while(1)        {                gets(data_pack.data);                write(client_fd,&data_pack,sizeof(struct send_pack));        }                /*5. 完毕套接字*/        close(client_fd);        return 0;}这是谈天过程运行结果:
(1) 左边是华为云ECS服务器,右边是本地ubuntu体系运行客户端

(2)群谈天过程演示



这是QT计划的带私聊、群聊的客户端版本。界体面文件、资源文件、代码文件都较多,就不全部贴出了。和上面的区别就是加了界面,加了一些功能,核心实现思绪差不多。
界面结果在文章首页已经截图了。
#include "qconnectobject.h"#include "mainwidget.h"QConnectObject::QConnectObject(QObject *parent) : QObject(parent){    tcpClient = new QTcpSocket(this);    connect(tcpClient,SIGNAL(readyRead()),this,SLOT(tcpreadData()));    connect(tcpClient,SIGNAL(error(QAbstractSocket::SocketError)),this,SLOT(ReadError(QAbstractSocket::SocketError)));    connectstate = false;}void QConnectObject::acceptConfig(QString stra, QString strb, QString strc){    m_ip = stra;    mc_port = strb;       if(connectstate == false){        QString ipAdd(stra), portd(strb);        if (ipAdd.isEmpty() || portd.isEmpty())        {            return;        }        tcpClient->connectToHost(ipAdd,portd.toInt());        if (tcpClient->waitForConnected(1000))        {            qDebug()<<"connectstate";            connectstate = true;        }    }    mtimer = new QTimer();    connect(mtimer,SIGNAL(timeout()),this,SLOT(mtimeout()));    mtimer->start(1000);}void QConnectObject::acceptLogin(QString stra, QString strb){    tcpClient->write(QString("LOGIN:%1:%2").arg(stra).arg(strb).toLocal8Bit());}void QConnectObject::acceptResiger(QString stra, QString strb){    tcpClient->write(QString("RESIGER:%1:%2").arg(stra).arg(strb).toLocal8Bit());}void QConnectObject::acceptMsg(QString str){    tcpClient->write(str.toLocal8Bit());}void QConnectObject::ReadError(QAbstractSocket::SocketError){    qDebug()<<"ReadError";    tcpClient->disconnectFromHost();    connectstate = false;}void QConnectObject::tcpreadData(){    QByteArray datagram =  tcpClient->readAll();    QString accbuf=QString::fromLocal8Bit(datagram);    qDebug()<<accbuf;    QStringList strlistA = accbuf.split(":");    if(strlistA.size() >= 1){        if(strlistA.at(0) == "LOGIN"){//登录            if(strlistA.size()>=2){                if(strlistA.at(1) == "true"){                    emit sendLogin(true);                    MainWidget::myport = strlistA.at(2);                }else if(strlistA.at(1) == "false"){                    emit sendLogin(false);                }            }        }        if(strlistA.at(0) == "RESIGER"){//登录            if(strlistA.size()>=2){                if(strlistA.at(1) == "true"){                    emit sendResiger(true);                }else if(strlistA.at(1) == "false"){                    emit sendResiger(false);                }            }        }        if(strlistA.at(0) == "CONNECT_CLIENT"){//毗连用户            emit sendClientInfo(accbuf);        }        if(strlistA.at(0) == "MSG"){//消息            emit sendMSG(accbuf);        } if(strlistA.at(0) == "GROUP"){//群            emit sendMSG(accbuf);        }    }}void QConnectObject::acceptProgress(double){}void QConnectObject::mtimeout(){    if(connectstate == false){        QString ipAdd(m_ip), portd(mc_port);        if (ipAdd.isEmpty() || portd.isEmpty())        {            qDebug()<<"return"<<connectstate<<m_ip;            return;        }        tcpClient->connectToHost(ipAdd,portd.toInt());        if (tcpClient->waitForConnected(1000))        {            connectstate = true;            qDebug()<<"connectstate";        }    }else{    }}QString getHostIpAddress(){    QString strIpAddress;    QList<QHostAddress> ipAddressesList = QNetworkInterface::allAddresses();    // 获取第一个本主机的IPv4地址    int nListSize = ipAddressesList.size();    for (int i = 0; i < nListSize; ++i)    {        if (ipAddressesList.at(i) != QHostAddress::LocalHost &&                ipAddressesList.at(i).toIPv4Address()) {            strIpAddress = ipAddressesList.at(i).toString();            break;        }    }    // 假如没有找到,则以本地IP地址为IP    if (strIpAddress.isEmpty())        strIpAddress = QHostAddress(QHostAddress::LocalHost).toString();    return strIpAddress;}void QConnectObject::sendFileClicked(){    fileName = QFileDialog::getOpenFileName();    qDebug()<<fileName;    m_port = qrand()%30 + 300 + qrand()%50 + qrand()%10;    QString filstr = "FileTran:" + getHostIpAddress() + ":" + QString::number(m_port);    tcpClient->write(filstr.toLocal8Bit());}
这篇文章介绍了ECS服务器从购买、设置,部署、计划代码的整个流程。以即时谈天体系的项目案例计划,团体介绍了ECS弹性云服务器的一个运用场景。
末了的总结阐明:选择云服务器ECS的一些上风,为什么要选择ECS云服务器?
【1】无需自建机房,无需采购以及设置硬件办法。
【2】分钟级交付,快速部署,紧缩应用上线周期。
【3】成本透明,按需使用,支持根据业务波动随时扩展和开释资源。
假如中小型公司本身搭建服务器,成本相对购买现成的云服务器而言,会高很多很多,而且稳固性都是标题。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则 返回列表

avatar
LZK798897当前离线
管理员

查看:217 | 回复:0

图文推荐更多
linux搭建FTP服务
linux搭建FTP服务
FTP 是File Transfer Protocol(文件传输协议)的英文简称,用于Internet上的控制文件
华为又开源一个操作体系,这次是服务器操作体系
华为又开源一个操作体系,这次是服务器操作
9 月 19 日,在华为 CONNECT 大会上,华为 Cloud & AI 产品与服务总裁侯金龙公布,将
不相识塔式服务器?老司机来帮你
不相识塔式服务器?老司机来帮你
互联网的遍及,使得越来越多的企业和用户开始关注线上业务。作为企业it办法最紧张的保
快速回复 返回顶部 返回列表