文章内容如标题,本人是一名嵌入式程序员,熟悉硬件,并且长期使用C/C++编写程序,由于最近公司后台服务器工程师离职,后台数据服务器无法继续维护,于是就萌生了自己搭建一个数据服务器的想法。所谓的数据服务器也就是我们现在经常说的物联网设备接入服务器,还不敢谈平台,只是一个接入端而已,当然,接入平台也就是这么一个一个的小模块构成的。

    物联网概念就不说了,网上太多了,先谈一下在搭建物联网设备接入服务器需要考虑的要点:

    1. 我们所涉及到的物联网设备均为小型化、轻量级设备,采用socket TCP/UDP通讯方式,因此服务器需要同时支持TCP和UDP连接;

    2. 通讯模块有常见的WiFi、GPRS、Lo-Ra、NB-IoT、BLE等,4G/有线通常用于网关等设备;

    3. 采用运营商网络的NB-IoT/GPRS等网络的设备,流量有限,不能高频以及大量传输数据,因此传输的数据不能采用json等常见的文本格式,这不仅需要消耗双倍甚至以上的流量,也给设备端造成相当大的处理压力,写过单片机程序的童鞋都知道,十六进制原始数据与JSON文本的转换需要耗费大量的CPU运算时间,一方面代码不好写,更重要的就是造成了极大的电量消耗,要知道还有很多物联网设备采用的是电池供电,能源是有限的;

    4. 上面提到,有一大部分设备是采用电池供电,为了节约能源,设备在没有使用的情况下,处于关闭或待机状态,无法实时接收到来自服务器的数据,因此服务器还需要增加离线命令缓存功能,当设备上线时,将缓存数据发送至设备;

 

    好了,废话了一大段,画了一个设备与接入服务器的拓扑图, 一个简图,不是很丰富,能看懂,省略了上层的接口

   

    整个思路理清楚,知道这个服务器需要实现的目标之后,就开始考虑使用什么语言来书写了:

    1. 使用C/C++,本人曾经使用VC++写过一个用于测试的服务器,由于本人经验不多,并且VC++能提供的库也极其稀少,很多都还需要自己二次封装,工作量极其巨大,因此直接放弃该语言,建议经验不足的童鞋不要轻易尝试,当然采用C/C++无疑是所有语言中效率最高的语言;

    2. pass掉C/C++之后,任然考虑使用微软出品的.NET,也就是C#来编写,由于平时测试需要,也写过一个简易的服务器,极其简单方便,但由于咱们平时有些客户用的是Linux服务器,这就带来了一个可移植性的问题,因此也放弃了

    3. 探索新语言,前面提到的编写语言都是平时在工作中用到过的语言,都被我pass掉之后,我依次看了Java、Python、Node.js等语言,这几个语言网上评价颇高,在网上对比了这几个语言之后(具体对比过程就不多重复,百度一下你就知道),决定使用Node.js来做,因此找到了一个菜鸟教程,开始学习Node的基础语法,就在学习的过程当中,发现了Golang;

    4. 容易分心的我就又对比了Golang,就像发现新大陆一样,虽然这个语言已经流行了很久,但孤陋寡闻的我还是如同发现新大陆一样兴奋,有人说Golang是最有可能代替C语言的一个编写语言,也就是说他们的语法应该是相近的,因此我又在菜鸟教程里面花了两个小时,把Golang的基本语法以及关键字浏览完毕,发现数据类型,数据结构基本都与C语言类似,并且提供了很多丰富的库,GIT上面也可以很方便的找到第三方库,就他了,Golang是一个并发性的语言,理论上是非常适合写服务器的,更多的特性,请参考其他网友描述,我就不过多描述了。

       在浏览完Golang的基础语法过后,就开始搭建开发环境,windows下面的开发环境搭建也是极其的方便,一个安装程序,不停的下一步就完成了整个安装过程,完毕后又检查了一下环境变量的设置;

      有了编译环境,自然还需要编辑环境,以前常用sourceinsight已经太过陈旧,没有办法再继续使用了,于是选择了微软出品的一个开源环境:Visio studio code,如同VS一样,强大的编辑以及编译界面,非常好用,因此不费吹灰之力,就运行了Hello world。

      接下来就是重头戏,打开端口,并监听端口:

// socketloopback.go
// socket for loopback debug

package loopback

import (
	"bytes"
	"log"
	"net"
	"os"
	"strconv"

	"eexcloud/eexconfig"
)

type udpWriteParam struct {
	remoteAddr net.UDPAddr
	buffer     []byte
}

func loopbackLog(v ...interface{}) {
	log.Println(v...)
}

// Start 回环测试程序启动
func Start() {
	go TCPMain()
	go UDPMain()
}

// TCPMain 用于设备TCP连接
func TCPMain() {
	address := eexconfig.ValueT.ServerIP + ":" + strconv.Itoa(eexconfig.ValueT.LoopbackPort)

	loopbackLog("Loopback TCP start, Listen Addr:", address)

	listener, err := net.Listen("tcp", address)
	if err != nil {
		loopbackLog("Loopback TCP Listen Error:", err)
		os.Exit(1)
	}

	defer listener.Close()

	for {
		conn, err := listener.Accept()
		if err != nil {
			loopbackLog("Loopback TCP Accept Error:", err)
			continue
		}

		go handleTCPConn(conn)
	}
}

func handleTCPConn(conn net.Conn) {
	defer conn.Close()

	readChan := make(chan []byte)
	stopChan := make(chan bool)

	loopbackLog("handle loopback TCP Connection from :", conn.RemoteAddr().String())
	go readTCPConn(conn, readChan, stopChan)

handleTcpLoop:
	for {
		select {
		case writeData := <-readChan:
			//upper := strings.ToUpper(readStr)
			_, err := conn.Write(writeData)
			if err != nil {
				loopbackLog("Loopback TCP Conn Write error:", err)
				break
			}
			loopbackLog("Loopback TCP Send to [", conn.RemoteAddr().String(), "] ", bytes.Count(writeData, nil)-1, "bytes")
		case stop := <-stopChan:
			if stop {
				break handleTcpLoop
			}
		}
	}

	loopbackLog("Exit handleTCPConn Connection : ", conn.RemoteAddr().String())
}

func readTCPConn(conn net.Conn, readChan chan<- []byte, stopChan chan<- bool) {
	for {
		data := make([]byte, eexconfig.ValueT.LoopbackBufferLen)
		n, err := conn.Read(data)
		if err != nil {
			loopbackLog("readLoopbackTCPConn Read error:", err)
			break
		}
		loopbackLog("Loopback TCP Received from [", conn.RemoteAddr().String(), "] ", n, "bytes")

		readChan <- data[:n]
	}

	stopChan <- true
}

func writeUDPConn(conn *net.UDPConn, writeChan <-chan udpWriteParam, stopChan chan<- bool) {

	loopbackLog("writeLoopbackUDPConn start")
	for {
		udpParam := <-writeChan
		_, err := conn.WriteToUDP(udpParam.buffer, &udpParam.remoteAddr)
		if err != nil {
			loopbackLog("writeLoopbackUDPConn => WriteToUDP error:", err)
			break
		}

		loopbackLog("Loopback UDP Send to [", udpParam.remoteAddr.String(), "] ", bytes.Count(udpParam.buffer, nil)-1, "bytes")
	}

	stopChan <- true
}

// UDPMain thread for device UDP connection
func UDPMain() {
	address := eexconfig.ValueT.ServerIP + ":" + strconv.Itoa(eexconfig.ValueT.LoopbackPort)

	loopbackLog("Loopback UDP start, Listen Addr: ", address)

	udpWriteChan := make(chan udpWriteParam)
	stopChan := make(chan bool)

	addr, err := net.ResolveUDPAddr("udp", address)
	if err != nil {
		loopbackLog("LoopbackUDP => ResolveUDPAddr error:", err)
		os.Exit(1)
	}

	conn, err := net.ListenUDP("udp", addr)
	if err != nil {
		loopbackLog("LoopbackUDP => ListenUDP error:", err)
		os.Exit(1)
	}

	defer conn.Close()

	go writeUDPConn(conn, udpWriteChan, stopChan)

	for {
		// Here must use make and give the lenth of buffer
		data := make([]byte, eexconfig.ValueT.LoopbackBufferLen)
		n, rAddr, err := conn.ReadFromUDP(data)
		if err != nil {
			loopbackLog("LoopbackUDP => ReadFromUDP error:", err)
			continue
		}

		loopbackLog("Loopback UDP Received from [", rAddr.String(), "] ", n, "bytes")

		udpWriteBuffer := udpWriteParam{*rAddr, data[:n]}
		udpWriteChan <- udpWriteBuffer
	}
}

    以上代码就是参考网友代码后经过我自认为的改良后的一个回环测试程序。可以直接使用,在main函数里面Start一下就可以了。

    第一天的工作完毕,从一个新的语言到回环测试,不到8个小时,的确非常简单,有需要的童鞋可以进行尝试。

写这个文章的时候,整个服务器已经搭建完成,只有靠回忆来书写,想到什么写什么,文笔不好,希望与大家分享与交流,顺便提高一下自己,如有不足之处,请指正。