关于服务器开发:用服务器代理IP有哪些优点和弊端

长处: 暗藏实在IP地址:应用服务器代理IP能够暗藏实在IP地址,爱护用户隐衷和平安。 进步访问速度:服务器代理IP能够缓存曾经拜访过的网页,进步访问速度。 混同流量:服务器代理IP能够混同流量,使得网络流量不易被辨认和跟踪。 冲破网络限度:有些网络可能设置了IP地址限度或屏蔽规定,应用服务器代理IP能够冲破这些限度,拜访被屏蔽的内容。 弊病: 升高访问速度:应用服务器代理IP可能会升高访问速度,因为须要通过屡次直达和加密解密的过程。 危险较高:应用不牢靠的服务器流冠代理IP可能会带来平安危险,例如数据被窃取或篡改等。 办法不非法:在某些状况下,应用服务器代理IP可能守法,例如用于黑客攻击或破解明码等流动。 升高稳定性:应用服务器代理IP可能会升高稳定性,因为代理服务器可能会因为各种起因而呈现故障或不可用。

September 19, 2023 · 1 min · jiezi

游戏服务器和Web服务器的区别

用Go语言写游戏服务器也有一个多月了,也能够明显的感受到两者的区别。这篇文章就是想具体的聊聊其中的区别。当然,在了解区别之间,我们先简单的了解一下Go语言本身。 1. Go语言的特点Go语言跟其他的语言例如Java比起来,算得上一门很年轻的语言。Go语言是由Robert Griesemer、Rob Pike和Ken Thompson于2007年在Google开发。并于2009年正式发布。 Go语言的设计理念围绕着简洁这两个字,认为少即是多。如果你熟悉Java,用Java那一套语法命名跟Go做对比,可以很明显的体会到这种感觉。 Go的特点可以简单的概括成以下几个点。 1.1 静态类型和编译型首先Go是静态类型,静态类型就是编译时就知道每一个变量的类型,得益于此,在编译的阶段就能够发现很多问题。而如果是动态语言,例如JavaScript,有些问题直到运行时才能发现。 Go是编译型语言,看到编译型大家脑子里可能会想到另外一个词解释型。两者的区别从字面上来理解其实已经可以看出来,我用一个简单的例子来类比一下。 编译型 去餐馆吃饭,点了菜之后,饭店会等所有的菜做好了再上解释型 去餐馆吃饭,点了菜之后,陆陆续续的边吃边上1.2 跨平台顾名思义,你写的Go源码在所有的系统都能够运行。 这点其实很好理解,例如Java的口号是"Write once, run anywhere"。我们都知道Java是编译型的语言,但是Java在编译的时候生成的是字节码,这个字节码与当前的操作系统无关,与CPU也无关。 这种字节码必须依赖Java虚拟机才能运行,而虚拟机会将操作系统和CPU之间的差异与用户屏蔽。对于编程的人来说这个过程其实无感知的。而对Java来说,语言本身的跨平台并不能代表代码可以跨平台。 Go的跨平台从某种方面来说,与Java类型,我们需要安装与当前操作系统相对应版本的Go。编译出来的可执行文件会根据操作系统的不同而有所不同。 1.3 自动垃圾回收与JVM一样,Go在运行时的内存管理(GC)由Go语言本身来管理,不需要程序员的参与,但是我们可以干预。 1.4 原生的并发编程何为原生?我们都知道,在Java中如果要实现并发, 需要外部的类库支持(Thread),而Go不需要从外部再引入任何依赖。支持使用关键字go即可。而且Java中是通过共享内存进行通信的,熟悉Go的应该都看过一句话“不要通过共享内存来通信,而应该通过通信来共享内存” 1.5 完善的构建工具从获取、编译、测试、安装、运行和分析等一系列流程都有自己的内置工具。例如获取可以使用go get命令来下载更新指定的代码包,并且对它们进行编译和安装,可以使用go build 对源码进行编译,用go run命令来运行Go的程序,用go fmt来快速格式化代码,统一代码风格。 1.6 多范式编程目前主流的编程范式有命令式编程、函数式编程和我们最熟悉的面向对象编程。在编写Go的代码的时候,我们可以选择使用面向对象的方法,也可以使用函数式编程的思想,相互结合,相辅相成。 例如,在Go里面也可以用接口来描述行为,也可以使用纯函数来避免出现副作用。因此,多范式编程就是指这个语言支持多种编程范式的。 1.7 代码风格强统一使用Go的内置工具go fmt即可快速的将代码格式化成官方统一的标准,以此来达到代码风格统一的目的。甚至可以用golangci-lint来检测你的语法跟内置的标准语法是否有冲突,完全可以将这个检测工具挂在git的钩子上,以此来达到强制的代码风格统一的目的。 1.8 活跃的社区还有一个很重要的特点是,国内的Go的社区十分的活跃,这对于Go在国内的普及起到了很大的作用。 2. 用Go的优势先说一下我对Go语言的看法,我认为Go在服务器这块是非常有优势的。以后如果有高并发的应用场景,那么大概率这个服务就是用Go写的。不知道大家有没有发现,摩尔定律正在失效。近十年内,硬件的原始处理能力都没有太大的提升。显然,一味的增加晶体管的数量已经不是解决问题最好的方法。 NASA前不久发布到官网然后又迅速删掉的文章透露了,Google可能已经实现了量子霸权,通俗一点说就是拥有超越所有传统计算机的计算能力。而放置更多的晶体管的代价也越来越高,所以现在厂商都在向处理器中添加更多的内核来提升性能。 就像大家熟悉的Java,虽然Java本身支持多线程,但是在Java上使用多线程编程代码算是比较昂贵的。在Java中创建一个新的线程就会消耗接近1M左右的内存。假如你真的需要支持运行上千个线程,那么服务很可能运行着就OOM了。除了内存消耗外,还会存在由于支持多线程带来的并发和死锁等问题。 而Go中,使用协程来代替线程。而且一个协程所消耗的内存比线程少了很多倍。同样的物理设备限制,你可能只能启动最多几千个线程,而协程能够启动上百万个。而且不同的Goroutine可以通过信channel进行安全的通信。 3. 游戏服务器和Web服务器的区别有些对游戏服务器的介绍可能会说,游戏服务器是一个需要长期运行的程序,然后怎么怎么样。我个人认为Web服务器一样的需要长期运行,也需要响应不定点不定时来自用户的请求。两者从宏观上来看其实没有本质的区别。同时Web服务器也会对于稳定性和性能有要求,游戏服一般分为大小服,我们这里都按照小服举例子。 3.1 状态首先要提到的就是状态。可能你会听说过一个概念,游戏服务器是有状态的,而Web服务器是无状态的。什么意思呢?Web服务器的数据流大多直接会到数据库中。而游戏服务器的数据流首先会到内存中,然后定期的写入数据库(落地)。 换句话说,游戏服务器本身的数据与数据库中的数据在运行期间会存在一个数据不一致的窗口。如果此时游戏服务器宕机了,那么就会造成数据首先到的内存数据与数据库存的数据不一致。 而Web服务器则不会有这样的问题,Web所有的数据状态都会落地,而且可以针对操作加上事务,不用担心因为操作失败而引入脏数据。正因为有了状态的约束,游戏服务器就会很慎重的使用内存、CPU。以求在资源有限的情况下,最大化的提高的承载量,并且降低服务延迟。当然,Web服务器会为了降低某个接口的响应时间而去做对应的优化。 3.2 扩容在Web服务器中,如果你不能评估一个服务所面临的压力,又不想因为瞬时的热点访问导致服务直接不可用的话,完全可以设置成自动扩容,因为每个服务只是单纯的接收请求,然后处理请求、返回结果,不会将数据保存在服务器的内存中。要有数据存到内存,那也是在Redis中。而Redis数据丢失对数据的一致性基本没有影响。 但是在游戏服务器这边很难做到像Web那样灵活。首先,数据的流向不是数据库,而是内存。 举个很简单的例子,玩家的主城被攻打着火了,如果有了自动扩容,很有可能在落地的窗口内,玩家再请求一次,请求到了另一个实例。主城又没有着火了。因为数据都会先存在内存中。 再举一个例子,玩家氪金买了一个礼包。然后退出游戏,落地窗口内再次上线没了。这就不是单纯的数据问题了,玩家这是花了真金白银买的道具,突然就没了,一两个还好处理,如果多个玩家都出现这样的问题,那这就属于严重的线上事故了。修复数据的工作量十分的大。 所以,对于一个游戏服务器,所能使用的内存和CPU的资源是非常有限的,不像Web服务器可以不用花很大的代价做到横向扩展。这也就是为什么游戏服务器会十分十分的注重代码的性能以及稳定性。 3.3 稳定就像上面说的例子,如果游戏服务器运行中出了BUG,导致服务直接不可用,或者说通过这个BUG刷到了大量的道具,将是一个非常严重的线上事故。 而对于Web服务器来说,如果是管理系统之类的,有可能会有脏数据值得一提的是,脏数据对于Web来说,排查起来也是一件很头疼的事情。如果没有脏数据,只是服务暂且不可用,而且如果用的是微服务架构,重启服务的代价是相对来说比较小的,只有正在重启的服务的业务是不可用的,其余的部分则可以正常的访问。 而对于游戏服务器来说,服务器重启影响的是全服的玩家。玩家在停服期间,甚至连游戏都进不了,特别的影响玩家体验。而且,如果停服之前服务器的数据落地出现了问题,服务重启之后会将数据从数据库load到内存中,此时同样会造成数据不一致的问题。 3.4 性能从我的经验来看,在做Web服务器的时候,没有为了减少GC的压力,为了少占用内存去做过多的优化。当然这是因为项目本身的体量不大,如果QPS很高的话,Web服务器同样很需要注重性能,只不过游戏服务器需要一直特别注意这个方面。 不过在Web,如果访问量很大的话导致单个服务不能扛住压力,大部分人首先想到的解决方案应该就是搞多个实例,毕竟可以做到很轻松的横向扩展。 在游戏服务器里,会把服务器的资源看的相当的宝贵。例如,能不落地的字段就绝对不要落地,某个字段的值可以通过已知的条件算出来的,就尽量不要定义在代码里。不过这也要看具体情况权衡运算量和调用的频率。因为上线之后,如果遇到了数据不一致,维护的数据越少,修复数据的难度就越小。 ...

October 15, 2019 · 1 min · jiezi

springboot四EnableConfigurationProperties是如何起作用的你知道吗

前言用springboot开发的过程中,我们会用到@ConfigurationProperties注解,主要是用来把properties或者yml配置文件转化为bean来使用的,而@EnableConfigurationProperties注解的作用是@ConfigurationProperties注解生效。如果只配置@ConfigurationProperties注解,在IOC容器中是获取不到properties配置文件转化的bean的,当然在@ConfigurationProperties加入注解的类上加@Component也可以使交于springboot管理。 举个栗子第一步:创建一个类TestConfigurationProperties @ConfigurationProperties(prefix = "properties")public class TestConfigurationProperties { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; }}注意:得加上set和get方法第二步:创建TestAutoConfiguration类 @Configuration@EnableConfigurationProperties(TestConfigurationProperties.class)public class TestAutoConfiguration { private TestConfigurationProperties testConfigurationProperties; public TestAutoConfiguration(TestConfigurationProperties testConfigurationProperties) { this.testConfigurationProperties = testConfigurationProperties; } @Bean public User user(){ User user = new User(); user.setName(testConfigurationProperties.getName()); return user; }}注意:得创建一个有参构造方法第三步:配置文件加入属性 properties.name=test第四步:跑一下,打印出User这个类 @RestController@RequestMapping("/api/test")@Slf4jpublic class TestController { @Autowired TestConfigurationProperties testConfigurationProperties; @Autowired User user; @RequestMapping(value = "/testConfigurationProperties") public String testConfigurationProperties() { log.info("test testConfigurationProperties.............{}", testConfigurationProperties.getName()); log.info("user:{}", user); return "SUCCESS"; }}控制台输出: ...

April 25, 2019 · 2 min · jiezi

与go邂逅一go环境搭建和Helloworld开发工具GOLAND

环境搭建学习语言,搭建环境必不可少,虽然网上教程也很多,但是我也记录下我的mac上面环境配置 从网上下载安装文件,下载地址:https://golang.google.cn/dl/mac 有自带的pkg文件,但是我选择的是go1.12.4.linux-amd64.tar.gz下载下来解压即可,会有个命名为go的文件夹配置环境变量,修改/etc/profile(全局的)或者.bash_profile(私有的)即可,配置完执行下source,我的配置如下:export GOROOT=$HOME/go_dev/goexport PATH=$PATH:$GOROOT/binexport GOPATH=$HOME/code/personal/code/go_project配置完,执行go version查看安装是否成功 开发工具没有做过比较,只是之前写java的时候用的idea,所以用了goland,它们都是一家公司的,挺好用 创建项目新建一个项目,配置好路径如图所以创建三个目录创建以.go为结尾的文件,命名随意写一个hello world 执行下: package mainimport "fmt"func main(){ fmt.Println("Hello World")}写到这里,其实go语言环境就跑通了,其实我们在写java项目的时候,都会有单元测试类,其实go当中也有,只要遵循一定的写法就可以了,我们首先建一个目录:文件名称需要以_test结尾,代码如下: package testimport "testing"//方法名以Test开头,加入参数,*testing.T tfunc TestPrint(t *testing.T) { t.Log("test hello world")}更多文章可以关注公众号:stonezplxjj

April 25, 2019 · 1 min · jiezi

Matchvs多节点功能上线

为了满足不同区域的用户需求,Matchvs现已正式上线多节点功能(正式版),本次上线的服务器节点包括北京、上海、广州三个国内节点。部分游戏类型(如fps、格斗及竞速类等)对于延迟容忍极低,就近节点连接可以有效降低延迟。Matchvs开放了自选多节点服务,开发者可以根据游戏要求给游戏开通该服务。开通多节点服务后,可以实现:1.玩家根据节点延迟情况,手动切换节点进行游戏。2.游戏里可以根据匹配情况,自动做节点切换策略。需注意是,玩家会在各节点分别匹配。若游戏内玩家数量过少,可以让游戏内自动做节点切换策略,以兼顾匹配成功率与低延时。若游戏对延时要求不高(如棋牌,回合制游戏等),则不建议您开启多节点服务。1. 多节点开通教程如需开启多节点服务,可以前往控制台 - 游戏列表 - 设置:成功启用后,即可对接 SDK 多节点功能,gameServer 无变化。在 Matchvs SDK中 使用接口获取节点信息,并切换到指定的节点。2. 多节点接口说明initinit 接口和之前的 init 接口是同一个,这里只是在 init 接口中新增了一个参数 threshold,只有传了该参数,才能获取节点列表和使用指定节点登录。getNodeList获取节点列表信息。在 init 成功后才能使用,并且init必须传入 threshold参数。否则返回值为 null。engine.getNodeList()无请求参数返回值说明 登录接口和前面 API文档描述的登录接口是同一个。只是加了一个 nodeID 参数,如果不传这个参数或者传入的参数为0,login 则使用默认节点登录。否则会使用指定的 nodeID登录,nodeID 必须是从 getNodeList 接口获取的有效ID。changeNode切换到指定节点中,切换节点只能在拥有多个节点的情况下使用,并且只能切换到 getNodeList 获取到的节点中。所有在 init 的时候设置好 threshold 参数。切换节点是指在使用 login 接口登录了默认节点后,想换一个节点就可以使用 changNode 接口切换到指定节点,所以,要使用 changeNode 接口必须是在登录后。engine.changeNode(args)返回值说明开发者如需体验过节点功能,需要下载SDK v3.7.9及以上版本,后续其它国内与国外的区域节点也将陆续上线。返回值说明开发者如需体验过节点功能,需要下载SDK v3.7.9及以上版本,后续其它国内与国外的区域节点也将陆续上线。Matchvs,24H轻松打造标准多人实时在线游戏,一个SDK解决服务器购买、联网&数据库开发、后期运维、高并发稳定问题。

March 13, 2019 · 1 min · jiezi

使用pm2快速将项目部署到远程服务器

使用背景当我们需要将项目部署到远程线上服务器时;传统的方法可能就是:将本地代码通过ssh、ftp等方式上传到服务器;然后通过ssh登入到服务器,配置好环境;手动启动应用。太过手动化,麻烦,操作繁琐。现代自动化部署环境:本地(Mac);远程服务器(CentOS)使用工具:Git、pm2、node;需知概念:ssh秘钥登陆;Github添加Deploy Keys1、服务器环境部署基本工具安装:git、pm2、node2、ssh服务器免密登陆服务器生成秘钥对ssh-keygen -t rsa -C ‘1285227393@qq.com’-t 指定密钥类型,默认即 rsa ,可以省略-C 设置注释文字,比如邮箱,可以省略由于使用的是百度云服务器,里面可以直接界面生成秘钥对,然后下载到本地的是一个xxx.txt文件. 此时登陆可以使用ssh -i xxx.txt[下载的公钥路径] name@domain报错:@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ WARNING: UNPROTECTED PRIVATE KEY FILE! @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@Permissions 0644 for ‘server-key.txt’ are too open.It is required that your private key files are NOT accessible by others.This private key will be ignored.Load key “server-key.txt”: bad permissions大概意思就是,私钥文件不能被其他人所访问。可能考虑到如果被别人获取到,就可能对服务器安全造成影响,所以需要从新设置下秘钥文件的权限重新设置秘钥文件权限:chmod 600 server-key.txt,取消其他用户Read权限但是,使用ssh name@domain形式还是没法直接登入;追其原因,发现因为不是本地直接生成的秘钥对;所以需要使用ssh-add -K ~/.ssh/xxx.txt[下载公钥文件](-K表示永久存储式,如果不使用者每次开机后需要重新ssh-add),就像是本地生成秘钥对然后部署到服务器需要将秘钥追加到ssh认证文件一个道理;ssh name@domain可以正常免密登陆啦!(配置这种形式登陆后面pm2需要使用)配置快捷登录(附加) 1. 进入ssh目录:cd ~/.ssh 2. 创建config文件: touch config 3. 进入config配置文件配置:vi config Host lwh #快捷别名 HostName host #ssh服务器ip或domain Port port #ssh服务器端口,默认为22 User root #ssh服务器用户名 IdentityFile ~/.ssh/server-key.txt #下载的私钥文件 4. :wq!保存退出 5. 完成后可以直接使用:ssh lwh 登陆在Github上添加Deploy Keys服务器生成秘钥# 生成ssh keyssh-keygen -t rsa# 查看公钥内容cat /.ssh/id_rsa.pub复制秘钥内容,添加到Github上对应的项目仓库Settings下的Deploy keys中配置Deploy keys,使得服务器可以通过ssh拉取项目仓库;配置pm2配置ecosystem.config.js;具体pm2配置及基本使用介绍,戳~使用pm2配置生产环境module.exports = { apps: [ { name: ‘back-Api’, //应用名 script: ‘./server/start.js’, //应用文件位置 env: { //PM2_SERVE_PATH: “./apidoc”, //静态服务路径 PM2_SERVE_PORT: 8080, //静态服务器访问端口 NODE_ENV: ‘development’ //启动默认模式 }, env_production : { PM2_SERVE_PORT: 8080, NODE_ENV: ‘production’ //使用production模式 pm2 start ecosystem.config.js –env production }, instances:“max”, //将应用程序分布在所有CPU核心上,可以是整数或负数 instance_var: “INSTANCE_ID”, exec_mode: “cluster”, min_uptime: “30s”, max_restarts: 10, //cron_restart: “40”, watch:[ “server”, ], //监听模式,不能单纯的设置为true,易导致无限重启,因为日志文件在变化,需要排除对其的监听 merge_logs: true, //集群情况下,可以合并日志 } ], deploy: { production : { //配置没法提供密码,所以前面需要配置ssh免密码登录服务器 user: ‘root’, //ssh 登陆服务器用户名 host: ‘100.12.102.198’, //ssh 地址服务器domain/IP ref: ‘origin/master’, //Git远程/分支 repo: ‘git@github.com’, //git地址使用ssh地址 path: ‘/liwenhui/www’, //项目存放服务器文件路径 “post-deploy”: ’npm install && pm2 reload ecosystem.config.js –env production’ //部署后的动作 } }};开始部署开始部署pm2 deploy ecosystem.config.js production报错appledeMBP:back-server-api apple$ pm2 deploy ecosystem.config.js production–> Deploying to production environment–> on host 106.12.132.188 ○ deploying origin/master ○ executing pre-deploy-local ○ hook pre-deploybash: 第 0 行💿 /lwh/www/source: 没有那个文件或目录 ○ fetching updates ○ full fetchbash: 第 0 行💿 /lwh/www/source: 没有那个文件或目录 fetch failedDeploy failed1需要先初始化服务器应用:pm2 deploy ecosystem.config.js production setup,然后:pm2 deploy ecosystem.config.js production其他pm2日志配置使用详情使用pm2配置生产环境本地连接远程mongodb配置服务器(CentOS)安装配置mongodb“积跬步、行千里”—— 持续更新中,喜欢的话留下个赞和关注哦!往期经典好文:团队合作必备的Git操作谈谈Js前端模块化规范Koa日志中间件封装开发(log4js) ...

March 11, 2019 · 2 min · jiezi

Go 语言编译过程概述

Golang 是一门需要编译才能运行的编程语言,也就说代码在运行之前需要通过编译器生成二进制机器码,随后二进制文件才能在目标机器上运行,如果我们想要了解 Go 语言的实现原理,理解它的编译过程就是一个没有办法绕过的事情。这一节会先对 Go 语言编译的过程进行概述,从顶层介绍编译器执行的几个步骤,随后的章节会分别剖析各个步骤完成的工作和实现原理,同时也会对一些需要预先掌握的知识进行介绍和准备,确保后面的章节能够被更好的理解。目录编译原理概述词法和语法分析器类型检查中间代码生成机器码生成预备知识想要深入了解 Go 语言的编译过程,需要提前了解一下编译过程中涉及的一些术语和专业知识。这些知识其实在我们的日常工作和学习中比较难用到,但是对于理解编译的过程和原理还是非常重要的。这一小节会简单挑选几个常见并且重要的概念提前进行介绍,减少后面章节的理解压力。抽象语法树抽象语法树(AST)是源代码语法的结构的一种抽象表示,它用树状的方式表示编程语言的语法结构。抽象语法树中的每一个节点都表示源代码中的一个元素,每一颗子树都表示一个语法元素,例如一个 if else 语句,我们可以从 2 * 3 + 7 这一表达式中解析出下图所示的抽象语法树。作为编译器常用的数据结构,抽象语法树抹去了源代码中不重要的一些字符 - 空格、分号或者括号等等。编译器在执行完语法分析之后会输出一个抽象语法树,这棵树会辅助编译器进行语义分析,我们可以用它来确定结构正确的程序是否存在一些类型不匹配或不一致的问题。静态单赋值静态单赋值(SSA)是中间代码的一个特性,如果一个中间代码具有静态单赋值的特性,那么每个变量就只会被赋值一次,在实践中我们通常会用添加下标的方式实现每个变量只能被赋值一次的特性,这里以下面的代码举一个简单的例子:x := 1x := 2y := x根据分析,我们其实能够发现上述的代码其实并不需要第一个将 1 赋值给 x 的表达式,也就是这一表达式在整个代码片段中是没有作用的:x1 := 1x2 := 2y1 := x2从使用 SSA 的『中间代码』我们就可以非常清晰地看出变量 y1 的值和 x1 是完全没有任何关系的,所以在机器码生成时其实就可以省略第一步,这样就能减少需要执行的指令来优化这一段代码。根据 Wikipedia 对 SSA 的介绍来看,在中间代码中使用 SSA 的特性能够为整个程序实现以下的优化:常数传播(constant propagation)值域传播(value range propagation)稀疏有条件的常数传播(sparse conditional constant propagation)消除无用的程式码(dead code elimination)全域数值编号(global value numbering)消除部分的冗余(partial redundancy elimination)强度折减(strength reduction)寄存器分配(register allocation)从 SSA 的作用我们就能看出,因为它的主要作用就是代码的优化,所以是编译器后端(主要负责目标代码的优化和生成)的一部分;当然,除了 SSA 之外代码编译领域还有非常多的中间代码优化方法,优化编译器生成的代码是一个非常古老并且复杂的领域,这里就不会展开介绍了。指令集架构最后要介绍的一个预备知识就是指令集的架构了,很多开发者都会遇到在生产环境运行的结果和本地不同的问题,导致这种情况的原因其实非常复杂,不同机器使用不同的指令也是可能的原因之一。我们大多数开发者都会使用 x86_64 的 Macbook 作为工作上主要使用的硬件,在命令行中输入 uname -m 就能够获得当前机器上硬件的信息:$ uname -mx86_64x86_64 是目前比较常见的指令集架构之一,除了 x86_64 之外,还包含其他类型的指令集架构,例如 amd64、arm64 以及 mips 等等,不同的处理器使用了大不相同的机器语言,所以很多编程语言为了在不同的机器上运行需要将源代码根据架构翻译成不同的机器代码。复杂指令集计算机(CISC)和精简指令集计算机(RISC)是目前的两种 CPU 区别,它们的在设计理念上会有一些不同,从名字我们就能看出来这两种不同的设计有什么区别,复杂指令集通过增加指令的数量减少需要执行的质量数,而精简指令集能使用更少的指令完成目标的计算任务;早期的 CPU 为了减少机器语言指令的数量使用复杂指令集完成计算任务,这两者之前的区别其实就是设计上的权衡,我们会在后面的章节 机器码生成 中详细介绍指令集架构,当然各位读者也可以自行搜索和学习。编译原理Go 语言编译器的源代码在 cmd/compile 目录中,目录下的文件共同构成了 Go 语言的编译器,学过编译原理的人可能听说过编译器的前端和后端,编译器的前端一般承担着词法分析、语法分析、类型检查和中间代码生成几部分工作,而编译器后端主要负责目标代码的生成和优化,也就是将中间代码翻译成目标『机器』能够运行的机器码。Go 的编译器在逻辑上可以被分成四个阶段:词法与语法分析、类型检查和 AST 转换、通用 SSA 生成和最后的机器代码生成,在这一节我们会使用比较少的篇幅分别介绍这四个阶段做的工作,后面的章节会具体介绍每一个阶段的具体内容。词法与语法分析所有的编译过程其实都是从解析代码的源文件开始的,词法分析的作用就是解析源代码文件,它将文件中的字符串序列转换成 Token 序列,方便后面的处理和解析,我们一般会把执行词法分析的程序称为词法解析器(lexer)。而语法分析的输入就是词法分析器输出的 Token 序列,这些序列会按照顺序被语法分析器进行解析,语法的解析过程就是将词法分析生成的 Token 按照语言定义好的文法(Grammar)自下而上或者自上而下的进行规约,每一个 Go 的源代码文件最终会被归纳成一个 SourceFile 结构:SourceFile = PackageClause “;” { ImportDecl “;” } { TopLevelDecl “;” } .标准的 Golang 语法解析器使用的就是 LALR(1) 的文法,语法解析的结果其实就是上面介绍过的抽象语法树(AST),每一个 AST 都对应着一个单独的 Go 语言文件,这个抽象语法树中包括当前文件属于的包名、定义的常量、结构体和函数等。如果在语法解析的过程中发生了任何语法错误,都会被语法解析器发现并将消息打印到标准输出上,整个编译过程也会随着错误的出现而被中止。我们会在这一章后面的小节 词法与语法分析 中介绍 Go 语言的文法和它的词法与语法解析过程。类型检查当拿到一组文件的抽象语法树 AST 之后,Go 语言的编译器会对语法树中定义和使用的类型进行检查,类型检查分别会按照顺序对不同类型的节点进行验证,按照以下的顺序进行处理:常量、类型和函数名及类型;变量的赋值和初始化;函数和闭包的主体;哈希键值对的类型;导入函数体;外部的声明;通过对每一棵抽象节点树的遍历,我们在每一个节点上都会对当前子树的类型进行验证保证当前节点上不会出现类型错误的问题,所有的类型错误和不匹配都会在这一个阶段被发现和暴露出来。类型检查的阶段不止会对树状结构的节点进行验证,同时也会对一些内建的函数进行展开和改写,例如 make 关键字在这个阶段会根据子树的结构被替换成 makeslice 或者 makechan 等函数。我们其实能够看出类型检查不止做了验证类型的工作,还做了对 AST 进行改写,处理 Go 语言内置关键字的活,所以,这一过程在整个编译流程中还是非常重要的,没有这个步骤很多关键字其实就没有办法工作,后面的章节 类型检查 会介绍这一步骤。中间代码生成当我们将源文件转换成了抽象语法树、对整棵树的语法进行解析并进行类型检查之后,就可以认为当前文件中的代码基本上不存在无法编译或者语法错误的问题了,Go 语言的编译器就会将输入的 AST 转换成中间代码。Go 语言编译器的中间代码使用了 SSA(Static Single Assignment Form) 的特性,如果我们在中间代码生成的过程中使用这种特性,就能够比较容易的分析出代码中的无用变量和片段并对代码进行优化。在类型检查之后,就会通过一个名为 compileFunctions 的函数开始对整个 Go 语言项目中的全部函数进行编译,这些函数会在一个编译队列中等待几个后端工作协程的消费,这些 Goroutine 会将所有函数对应的 AST 转换成使用 SSA 特性的中间代码。中间代码生成 这一章节会详细介绍中间代码的生成过程并简单介绍 Golang 是如何在中间代码中使用 SSA 的特性的,在这里就不展开介绍其他的内容了。机器码生成Go 语言源代码的 cmd/compile/internal 中包含了非常多机器码生成相关的包,不同类型的 CPU 分别使用了不同的包进行生成 amd64、arm、arm64、mips、mips64、ppc64、s390x、x86 和 wasm,也就是说 Go 语言能够在上述的 CPU 指令集类型上运行,其中比较有趣的就是 WebAssembly 了。作为一种在栈虚拟机上使用的二进制指令格式,它的设计的主要目标就是在 Web 浏览器上提供一种具有高可移植性的目标语言。Go 语言的编译器既然能够生成 WASM 格式的指令,那么就能够运行在常见的主流浏览器中。$ GOARCH=wasm GOOS=js go build -o lib.wasm main.go我们可以使用上述的命令将 Go 的源代码编译成能够在浏览器上运行的『汇编语言』,除了这种新兴的指令之外,Go 语言还支持了几乎全部常见的 CPU 指令集类型,也就是说它编译出的机器码能够在使用上述指令集的机器上运行。机器码生成 一节会详细介绍将中间代码翻译到不同目标机器的过程,在这个章节中也会简单介绍不同的指令集架构的区别。编译器入口Go 语言的编译器入口在 src/cmd/compile/internal/pc 包中的 main.go 文件,这个 600 多行的 Main 函数就是 Go 语言编译器的主程序,这个函数会先获取命令行传入的参数并更新编译的选项和配置,随后就会开始运行 parseFiles 函数对输入的所有文件进行词法与语法分析得到文件对应的抽象语法树:func Main(archInit func(*Arch)) { // … lines := parseFiles(flag.Args())接下来就会分九个阶段对抽象语法树进行更新和编译,就像我们在上面介绍的,整个过程会经历类型检查、SSA 中间代码生成以及机器码生成三个部分:检查常量、类型和函数的类型;处理变量的赋值;对函数的主体进行类型检查;决定如何捕获变量;检查内联函数的类型;进行逃逸分析;将闭包的主体转换成引用的捕获变量;编译顶层函数;检查外部依赖的声明;了解了剩下的编译过程之后,我们重新回到词法和语法分析后的具体流程,在这里编译器会对生成语法树中的节点执行类型检查,除了常量、类型和函数这些顶层声明之外,它还会对变量的赋值语句、函数主体等结构进行检查: for i := 0; i < len(xtop); i++ { n := xtop[i] if op := n.Op; op != ODCL && op != OAS && op != OAS2 && (op != ODCLTYPE || !n.Left.Name.Param.Alias) { xtop[i] = typecheck(n, ctxStmt) } } for i := 0; i < len(xtop); i++ { n := xtop[i] if op := n.Op; op == ODCL || op == OAS || op == OAS2 || op == ODCLTYPE && n.Left.Name.Param.Alias { xtop[i] = typecheck(n, ctxStmt) } } for i := 0; i < len(xtop); i++ { n := xtop[i] if op := n.Op; op == ODCLFUNC || op == OCLOSURE { typecheckslice(Curfn.Nbody.Slice(), ctxStmt) } } checkMapKeys() for _, n := range xtop { if n.Op == ODCLFUNC && n.Func.Closure != nil { capturevars(n) } } escapes(xtop) for _, n := range xtop { if n.Op == ODCLFUNC && n.Func.Closure != nil { transformclosure(n) } }类型检查会对传入节点的子节点进行遍历,这个过程会对 make 等关键字进行展开和重写,类型检查结束之后并没有输出新的数据结构,只是改变了语法树中的一些节点,同时这个过程的结束也意味着源代码中已经不存在语法错误和类型错误,中间代码和机器码也都可以正常的生成了。 initssaconfig() peekitabs() for i := 0; i < len(xtop); i++ { n := xtop[i] if n.Op == ODCLFUNC { funccompile(n) } } compileFunctions() for i, n := range externdcl { if n.Op == ONAME { externdcl[i] = typecheck(externdcl[i], ctxExpr) } } checkMapKeys()}在主程序运行的最后,会将顶层的函数编译成中间代码并根据目标的 CPU 架构生成机器码,不过这里其实也可能会再次对外部依赖进行类型检查以验证正确性。总结Go 语言的编译过程其实是非常有趣并且值得学习的,通过对 Go 语言四个编译阶段的分析和对编译器主函数的梳理,我们能够对 Golang 的实现有一些基本的理解,掌握编译的过程之后,Go 语言对于我们来讲也不再是一个黑盒,所以学习其编译原理的过程还是非常让人着迷的。相关文章编译原理概述词法和语法分析器类型检查中间代码生成机器码生成ReferenceIntroduction to the Go compilerGo 1.5 Bootstrap PlanGo grammar questiowhat type of grammar GO programming language? ...

February 11, 2019 · 3 min · jiezi

Cocos Creator快速开通联网服务教程

继集成Egret编辑器工作流后,在最新的Cocos Creator v2.0.7 版本中, Creator服务面板也集成了游戏服务器引擎Matchvs的联网服务。现附上开通教程,方便大家更快上手。1、打开 Cocos Creator,选择菜单栏 -> 面板 -> 服务,打开服务面板,在列表中点击Matchvs 项,进入 Matchvs 服务设置面板。2、在使用 Matchvs 服务功能之前,你需要先为当前的游戏工程设定 Cocos AppID。如果您还没有 Cocos AppID,点击创建按钮即可跳转至 Cocos 账户中心创建游戏。(注:游戏归属需为公司,才可以开通 Matchvs 服务)3、游戏创建完成后,返回 Cocos Creator 服务面板,完成设定 Cocos AppID,这样你的本地游戏代码就与你账户中的游戏建立关联了。再次点击 Matchvs 项,进入 Matchvs 面板,点击右上角开启服务,Matchvs SDK 将会自动集成到您的游戏项目的 assets/scripts/matchvs 目录中,最后参考[使用指南]直接调用相关功能 API 即可。

January 17, 2019 · 1 min · jiezi