项目的由来
我的工作是开发网页游戏。在实际工作中,调试游戏需要用到一台服务器,由于只是我个人测试用,不需要性能多么的好,但也不能太差。在爱板网看到了这个活动,发现树莓派3b的配置和它的低功耗,正好满足我的需求。所以这次就拿树莓派3b来配置成我的游戏服务器。
设计与分析:
树莓派非常精致小巧,外设接口丰富,拿到手后,我就查阅了相关资料,根据我的需求进行了如下设计。
系统选择:通过官网资料的查询,我得知树莓派3b可以选择Raspbian、ubuntu、winodes10等,我的选择是Raspbian Stretch Lite,因为我是拿来做服务器,所以不需要桌面的显示,远程ssh连接过去管理就行了,所以没有选择desktop版本。
网络连接方式:因为我是用来做服务器,稳定肯定是第一需求,所以我先不用wifi模式,直接接网线吧。
外壳:我需要树莓派长时间的运行,肯定不能就这样裸奔了,从淘宝上看了下,发现目前有2种外壳感觉还不错,就一并买了回来。
这两种外壳一种是亚克力的散热需要风扇,另外一种是铝合金的,导热柱直接贴在芯片上,是外壳又同时是散热器。经过我满负荷运行测试,发现这种铝合金的外壳无论外观和散热上都比亚克力的要强,所以就最终使用了铝合金的外壳。
存储设备:树莓派需要把系统安装到sd卡中,最低需求8g空间,我这次就选用了一张class10的8g高速卡和一个u盘来做为存储设备,之所以没有选择大sd卡的原因就是,我想sd卡只安装系统,平时我的开发资源全部放到u盘中,这样也方便我随时把u盘拿到其他地方,对其中的文件进行操作。
其他扩展:因为我选择了Lite版本的系统,并且没有外接显示器。但是我在测试过程中,有时候需要知道一些系统信息,比如:IP地址、内存使用情况、cpu使用情况、硬盘使用情况等。所以我就萌生了一种想法,那就是给树莓派加个小的显示器,可以自定义的显示一些我需求的信息。由于我对arduino比较熟,所以我选择了arduino pro mini 外加一块 0.96寸的oled的小显示屏。这里要说明下,为什么我没有用树莓派来直接驱动oled而是外加了一块arduino来做驱动,是因为我想树莓派就存粹的做服务器,不想再让它额外做其他事情,当然你也可以直接选择树莓派来驱动oled屏幕。
开始行动吧
系统安装和配置环境:
系统安装就不详细说明了,因为网上一找一大把,我只是说说,安装中和安装后我遇到的一些问题和解决办法吧。
初始用户名:pi 密码:raspberry
安装后ssh不能远程登录:是因为新版的系统,默认是关闭远程ssh的。开启方法:
把树莓派接上显示器和键盘,输入命令:
1 |
sudo passwd root |
设置时间为北京时间
1 |
cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime |
安装nodejs
因为我的游戏服务器就是基于nodejs开发的所以必须安装nodejs
我使用的是nvm来管理nodejs
安装nvm
1 |
wget -qO- https://raw.githubusercontent.com/creationix/nvm/v0.33.6/install.sh | bash |
切换淘宝源(为什么切换你懂得)
1 |
export NVM_NODEJS_ORG_MIRROR=http://npm.taobao.org/mirrors/node |
安装最新版本的nodejs
1 |
nvm install node |
查看当前使用nodejs信息
1 |
nvm list |
会显示类似信息说明安装成功了。
npm使用淘宝源
1 |
npm config set registry https://registry.npm.taobao.org |
安装好用的文本编辑器 nano
1 |
sudo apt-get install nano |
挂载u盘
1.创建挂载文件夹
1 2 3 4 |
pi@raspberrypi:/ $ sudo mkdir data pi@raspberrypi:/ $ ls bin data etc lib media opt root sbin sys usr boot dev home lost+found mnt proc run srv tmp var |
查找 sudo fdisk -l
1 2 3 4 5 6 7 8 9 10 |
Disk /dev/sda: 15.1 GiB, 16231956480 bytes, 31703040 sectors Units: sectors of 1 * 512 = 512 bytes Sector size (logical/physical): 512 bytes / 512 bytes I/O size (minimum/optimal): 512 bytes / 512 bytes Disklabel type: dos Disk identifier: 0x00000000 Device Boot Start End Sectors Size Id Type /dev/sda1 2048 31703039 31700992 15.1G 5 Extended /dev/sda5 4096 31703039 31698944 15.1G 0 Empty |
2. 设置开机自动挂载硬盘!
# nano /etc/fstab
添加
/dev/sda5 /data ext4 defaults 0 0
输出并显示系统信息:
先看效果图:
树莓派 —USB—》ch340 —串口—》arduino —SPI—》oled
我这里没有使用树莓派的串口直接连接arduino,而是使用了一个usb转串口的模块。
是因为:
1.3b的串口默认分配给蓝牙模块了,如果直接连串口,蓝牙就不能使用。
2.usb即插即拔方便扩展,不需要拆装外壳。
反正3b有4个usb接口,所以我就拿了个ch340 扩展了串口出了。
arduino端程序:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
#include <SSD1306.h> #define LCD_DC 9 #define LCD_CS 10 #define LCD_CLK 13 #define LCD_MOSI 11 #define LCD_RESET 8 SSD1306 lcd(LCD_MOSI,LCD_CLK,LCD_DC,LCD_RESET,LCD_CS); String inputString = ""; boolean cmdComplete=false; boolean hide=false; unsigned long last_refresh_time=0; void setup() { // put your setup code here, to run once: Serial.begin(9600); lcd.SSD1306_init(); lcd.clear_display(); lcd.drawLogo(); lcd.drawstring(7,3,"SERVER"); lcd.drawstring(7,12," BOOTING"); } void loop() { // 判断是否休眠 lcd if(millis()-last_refresh_time>30000){ // lcd.hide(); // hide=true; } if(cmdComplete){ while(inputString.length()<23){ inputString +=" "; } char b[inputString.length()]; inputString.toCharArray(b,inputString.length()+1); char*temp = strtok(b,"@"); if(temp==NULL) return; int l = atoi(temp); temp = strtok(NULL,"@"); if(temp==NULL) return; //Serial.println(temp); last_refresh_time=millis(); if(hide)lcd.display(); lcd.drawstring(l,0,temp); inputString = ""; cmdComplete=false; Serial.print("ok"); } } void serialEvent() { while (Serial.available()) { char inChar = (char)Serial.read(); if (inChar == '\n' || inChar == '\r') { cmdComplete = true; }else{ inputString += inChar; } } } |
其中的ssd1306 这个库 我上传github了 可以去下载
https://github.com/flashria/SSD1306
树莓派端程序:
因为我nodejs比较熟悉,所以树莓派端的程序也用nodejs来写了。
需要导入库 serialport os request 其中获取远程ip 是通过我远程一个服务器来实现的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 |
var SerialPort = require('serialport'); var os = require("os"); var request = require('request'); // callback approach var disk="/dev/sda5"; var num=0; SerialPort.list(function (err, ports) { ports.forEach(function(port) { console.log(port.comName); console.log(port.pnpId); console.log(port.manufacturer); }); }); //return; var port = new SerialPort('/dev/ttyUSB0',{ baudRate: 9600 }); var index=0; port.on('open',function(){ sendMsg(index,'Raspberry Pi Server'); }); //打开错误将会发出一个错误事件 port.on('error',function(err){ console.log('Error: ',err.message); port.close(); }); port.on('data',function(data){ console.log('Data: '+data); if(data.indexOf("ok")!=-1)next(); }); String.prototype.ResetBlank=function(){ var regEx = /\s+/g; return this.replace(regEx, ' '); }; function next(){ index++; if(index>7)index=1; switch(index){ case 1: sendMsg(index,'RUN_TIME:' + os.uptime()); break; case 2: sendMsg(index,'CPU:' + Math.floor(os.loadavg()[0]*100)/100+" "+ Math.floor(os.loadavg()[1]*100)/100+" "+ Math.floor(os.loadavg()[2]*100)/100); break; case 3: sendMsg(index,'RAM:' + Math.floor(((os.totalmem()-os.freemem())/os.totalmem())*10000)/100+"%"); break; case 4: var exec=require('child_process').exec; exec('df -h',function(err,out,err){ var list= out.split('\n'); for(var i=0;i<list.length;i++){ list[i]=list[i].ResetBlank(); var row=list[i].split(' '); if(row[0]==disk)sendMsg(index,'DISK:' +row[4]+" "+row[2]+"/"+row[1]); //console.log(i+" "+row); } }); break; case 5: var obj=os.networkInterfaces()["eth0"][0] sendMsg(index,'IP:' + obj.address); break; case 6: request("http://www.kejidana.com/ip.php", function (error, response, body) { if (!error && response.statusCode == 200) { sendMsg(index,'IP:' + body); }else{ next(); }}); break; case 7: var obj=os.networkInterfaces()["eth0"][0] sendMsg(index,'MAC:' + obj.mac); num++ if(num>50)port.close(); break; } } function sendMsg(l,msg,callback){ port.write(l+'@'+msg+'\n',function(err){ if(err){ port.close(); return console.log('Error on write: ' ,err.message); } if(callback)callback(); }); } |
连接好硬件,然后再树莓派上运行这个脚本。就可以持续刷新显示系统信息了。
安装pomelo
先介绍下pomelo,pomelo是网易开源的一个nodejs的游戏服务器框架,基于pomelo,可以很方便的开发游戏服务器。由于h5游戏开发使用的是JavaScript,nodejs也是JavaScript,使用pomelo能够非常方便的与客户端共用模块,对于一些小型项目非常合适。
pomelo的安装也非常简单:
使用npm(node包管理工具)全局安装pomelo:
1 |
$ npm install pomelo -g |
可以通过如下命令下载源代码的方式安装
1 2 3 4 5 |
$ git clone https://github.com/NetEase/pomelo.git $ cd pomelo $ npm install -g |
其中-g表示全局安装,关于npm的使用问题,可以参考npm的文档,里面有详细的npm使用的介绍。如果安装过程中没有报错误,说明安装成功。
创建项目
使用pomelo的命令行工具可以快速创建一个项目,命令如下:
1 2 3 4 5 6 7 8 9 10 11 |
$ pomelo init ./HelloWorld The default admin user is: username: admin password: admin You can configure admin users by editing adminUser.json later. Please select underly connector, 1 for websocket(native socket), 2 for socket.io, 3 for wss, 4 for socket.io(wss), 5 for udp, 6 for mqtt: [1] |
选择哪种通讯方式,h5游戏一般是用websocket 这里我们选1 ,然后等待命令行返回。
到这里 Raspberry Pi pomelo服务器就完全搭建好了。关于pomelo的一些使用和说明可以参考这里:https://github.com/NetEase/pomelo/wiki/Home-in-Chinese
api可以参考这里:http://pomelo.netease.com/api.html
这里可以查看pomelo开发的demo:
http://pomelo.netease.com/lordofpomelo/
欢迎有兴趣的同学跟我多多沟通,我的邮箱:flashria@ qq.com