关于后端:设计一个跨平台的即时通讯系统采用华为云ECS服务器作为服务端-华为云至简致远

4次阅读

共计 13535 个字符,预计需要花费 34 分钟才能阅读完成。

【摘要】这篇文章介绍了 ECS 服务器从购买、配置,部署、设计代码的整个流程。以即时聊天零碎的我的项目案例设计,整体介绍了 ECS 弹性云服务器的一个使用场景。抉择云服务器 ECS 的劣势在于:无需自建机房,无需洽购以及配置硬件设施,分钟级交付,疾速部署,缩短利用上线周期。应用也很简略,一个近程终端连贯下来就行。

  1. 前言即时通信软件的呈现使人与人之间的交换相处变得更加便捷。好友即便远隔千里,仍旧能够相互通信,使得友情得以长存;亲人即使因工作相隔很远,仍然能够多多分割,以便家人安心,本人舒心;正是即时通信零碎使得信息走向便捷化的路线,人与人之间的交换沟通更是不便。反过来也是信息交换的不可或缺,使得即时通信零碎更加具备钻研价值,互联网对其的器重从不缩小,反倒是一劳永逸,人们对其的性能构想更是丰盛多样,更是推动了互联网大环境的倒退。这篇文章就设计一个简略的即时通讯软件,也就是相似于 QQ 这种聊天软件,通过这个软件设计实现过程来理解 TCP 网络编程知识点、客户端设计思路、公网服务器部署形式等常识;自身软件并不简单,实现的都是一些基本功能,次要是通过这套软件的设计过程来对应介绍相干的知识点。软件整体蕴含了一个客户端、一个服务器。客户端采纳 QT 设计,反对跨平台运行,服务器采纳 Linux 零碎运行,为了不便实现公网聊天,不局限于局域网,服务器采纳了华为云的 ECS 弹性服务器,零碎抉择了 ubuntu18.04 64 位。服务器端存储数据的数据库采纳华为云的 mySQL 云数据,华为云 MySQL 数据的应用形式在上篇文章里讲过了。客户端的数据库采纳 SQLITE,数据都是存储在本地的,存储一些聊天记录等信息。上面是客户端的界面成果:
    (1)这是客户端登录界面。客户端能够有限运行,一个客户端就登录一个账号。

(2)这个截图清晰些

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

(4)这是两个好友登录之后,聊天的成果。登录须要登录服务器。

接下来就从服务器的购买、部署、开始介绍整体软件的设计。2. 部署 ECS 云服务器华为云的 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 地址网段,端口范畴就行了。

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

登录形式有多种,先抉择一个默认形式,先体验一下,看看本人的服务器。

输出密登录即可。

登录之后进入命令行终端,相熟的命令行呈现了。

到此,一台全新的服务器曾经搞定了,接下来就能够编写代码运行了。3. 应用 SSH 协定登录服务器在网页上间接登录,操作起来始终不不便,我在 windows 下个别应用 SecureCRT 登录服务器。
服务器反对 SSH 协定登录,登录是很不便。
只须要晓得服务器的公网 IP 地址、零碎的用户名、零碎明码即可。
公网地址就在服务器详情页面能够看到。用户名就是 root,明码就是本人创立服务器时设置的明码。上面就看一下登录成果:

点击承受并保留。

输出用户名、明码即可登录下来。

到此,采纳 SSH 协定、利用终端软件胜利登录了服务器,接下来就能够失常的开发了。4. 即时零碎整体设计思路 (1) 服务器设计: 登录认证:服务器从客户端接管账户和明码,创立线程比对数据库中该用户账号、明码,比对胜利,则返回用户信息给客户端,确认可进行登录。客户端连贯信息保留:用户在客户端胜利登录后,其所属账号、IP 以及端口号被记录在数据库的连贯信息表中以便治理。此时用户状态被置为 1。反之,用户在客户端退出时,其账号、IP 被连贯信息表革除,此时用户状态被置为 0. 用户注册:当新用户应用时,用户须要填写用户名和明码。此时用户注册信息将存入数据库中,以备下次登录所需[10]。减少好友:服务触发减少好友的事件申请后,会即时将其账号信息存录数据库中进行治理。删除好友:服务器触发删除好友的事件申请后,会即时革除数据库中该用户的账户信息。查找好友:服务器一旦触发查找好友的事件申请时,会即时比对数据库中信息表的好友信息,该账号信息存在就可胜利找到。批改好友:服务器触发好友信息批改的事件申请后,会即时更新该用户的账户信息。好友通信:服务器触发触发通信的事件申请时,会即时查询数据库中好友连贯信息,再由此信息返回好友的 ip 地址及端口号给用户。(2) 客户端设计: 用户注册:用户在通信零碎的客户端登录界面上输出账号、明码。接着,用户可单击按钮进行注册操作。此时,注册信息会即时发送给服务器,录入数据库进行治理。注册完,敞开客户端。下次登录时,用户通过客户端与服务器连贯,查找账户信息,服务器断定账户信息正确,即表明注册胜利。用户登录:用户点击“登录”按钮,依据客户端输出的账号信息断定其是否为空。若为空,程序运行不执行下述操作:向服务器发送登录申请。若不为空,程序运行即刻执行下述操作:向服务器发送登录申请。好友在线情况:客户端收到来自服务器的好友在线信息,好友联系人列表会发现此用户信息,表明该用户处于在线状态。用户可双击任一在线好友,单方即可进行交换。一对一聊天:用户双击联系人列表中任一在线友人。这时,客户端会给服务器发申请。服务器收到申请后,即时给客户端发送无关好友 ip 地址和端口号的连贯信息。最终,单方实现信息交互。单方在聊天框中交换,客户端还含有好友聊天记录性能即音讯治理。群组聊天:用户点击“群组”图标,客户端就会发送事件申请给服务器。服务器触发事件申请后,即时给客户端发送群聊的连贯信息,此连贯信息与群聊 ip 地址和端口号无关。最终,多人通信的用户通过客户端聊天界面实现信息交互。5. 服务器代码为了不便拷贝代码到服务器下来,我这里采纳 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$ ls
wbyq@wbyq:~/mnt$ sudo cp /mnt/hgfs/linux-share-dir/socket_app/* ./
wbyq@wbyq:~/mnt$ ls
client_app.c server_app.c
wbyq@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("过程失常退出....\n");
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 函数监听失败.\n");
        break;
    }
}

/*6. 敞开连贯 */
close(client_fd);
pthread_exit(NULL);

}

int main(int argc,char **argv)
{

if(argc!=2)
{printf("./app < 创立绑定的服务器端口号 >\n");
    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 套接字失败.\n");
    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: 绑定端口号失败.\n");
    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: 解决客户连贯失败.\n");
        return 0;
    }
    printf("客户端的 IP 地址:%s\n",inet_ntoa(client_addr.sin_addr));
    printf("客户端的端口号:%d\n",ntohs(client_addr.sin_port));
    /* 创立新的线程 */
    if(pthread_create(&thread_id,NULL,func_start,client_fd))
    {printf("线程创立失败.\n");
        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("内存空间申请失败.\n");
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 -lpthread
root@ecs-348470:~/work# ls
server_app.c tcp_server
root@ecs-348470:~/work# ./tcp_server 8899

运行服务器群聊代码,在本地 ubuntu 零碎再运行客户端代码,测试通信成果:

  1. 客户端代码设计在设计过程中,为了测试,代码写了两份,一份命令的行的聊天客户端,一份 QT 设计带界面的残缺版本。6.1 Linux 命令行客户端 上面这是命令行版本的群聊客户端代码: #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>

    /*
    客户端:

  2. 创立 socket 套接字
  3. 连贯指定的服务器
    */
    int client_fd;

/信号处理函数/
void sighandler_func(int sig)
{

/*4. 结束套接字 */
close(client_fd);
printf("过程失常退出....\n");
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\n",data.name,data.data);
            }
            else if(data.type==1)
            {printf("%s 用户上线.\n",data.name);
            }
            else if(data.type==2)
            {printf("%s 用户下线.\n",data.name);
            }
        }
        else if(cnt==0)
        {printf("服务器断开连接.\n");
            break;
        }
    }
    else if(poll_stat<0)
    {perror("poll 函数执行谬误.\n");
        break;
    }
}
pthread_exit(NULL);

}

int main(int argc,char **argv)
{

if(argc!=4)
{printf("./app < 服务器 IP 地址 > < 服务器端口号 > < 用户名 >\n");
    return 0;
}

/* 绑定须要捕捉的信号 */
signal(SIGINT,sighandler_func);

/*1. 创立 socket 套接字 */
client_fd=socket(AF_INET,SOCK_STREAM,0);
if(client_fd<0)
{printf("client: 创立 socket 套接字失败.\n");
    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: 连贯服务器失败.\n");
    return 0;
}

/*3. 创立子线程 */
pthread_t thread_id;
if(pthread_create(&thread_id,NULL,func_start,&client_fd))
{printf("线程创立失败.\n");
    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)群聊天过程演示

6.2 QT 残缺版本客户端这是 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());

}

  1. 总结这篇文章介绍了 ECS 服务器从购买、配置,部署、设计代码的整个流程。以即时聊天零碎的我的项目案例设计,整体介绍了 ECS 弹性云服务器的一个使用场景。最初的总结阐明:抉择云服务器 ECS 的一些劣势,为什么要抉择 ECS 云服务器?
    【1】无需自建机房,无需洽购以及配置硬件设施。
    【2】分钟级交付,疾速部署,缩短利用上线周期。
    【3】老本通明,按需应用,反对依据业务稳定随时扩大和开释资源。
    如果中小型公司本人搭建服务器,老本绝对购买现成的云服务器而言,会高很多很多,而且稳定性都是问题。
    【华为云至简致远】有奖征文炽热进行中:https://bbs.huaweicloud.com/b…
    【版权申明】本文为华为云社区用户原创内容,转载时必须标注文章的起源(华为云社区),文章链接,文章作者等根本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌剽窃的内容,欢送发送邮件至:cloudbbs@huaweicloud.com 进行举报,并提供相干证据,一经查实,本社区将立即删除涉嫌侵权内容。
    想理解更多的华为云产品相干信息,请分割咱们:

电话:950808 按 0 转 1

正文完
 0