记一次数据采集软件(服务器)开发经历(c#,socket,TCP)

这次经历大致分为以下几个阶段:
一、C#基本操作学习和简单TCP通信实现


    先前有使用MFC的经历,因此对于C#的控件使用比较容易上手,每次使用之前可通过网络查询到控件使用方法,直接拖拽就OK,唯一不适应的就是C#全部都是类,不过习惯后会觉得比C++更方便调用。TCP的实现主要还是以网络博客为主,这类的博客很多,通过简单的搭建,就可以实现一个服务器。有同事的前车之鉴,建议我们使用异步。我使用的方法来自于这篇博客。里面使用List来维护所连接的客户端,TcpClientState这个类中封装每个客户端的连接状态,buff缓存等。
       https://blog.csdn.net/zhujunxxxxx/article/details/44258719

二、数据缓存和解析的实现


     关于数据解析这一部分,首先做了一个大的队列,Queue,之前写过一个队列的类,直接将c++改成c#类,来调用,内部也做了线程安全,我的想法是每一帧数据都是一个byte数组,新开线程,在队列非空的情况下,每次取一个byte[]数组,进行解析。使用Queue的时候,有时取不到数据,没找到原因,那时候我也是too young too simple,后来觉得list更顺手,就改为list实现了。解析没什么好说的,按照项目协议解析出每个字段,解析不出来的记录下来哪里有问题就可以了。

三、异常捕捉
    之前没用过try{}catch{},在同事的帮助下开始对可能存在问题的地方进行异常记录。使用不难。

四、数据入库
   我们采用的是mysql,具体入库之前,先学习了下C#和数据库是怎么连接起来的,C#要调用MySql.Date.dll,这些需要在网上下载,另外还需要下载一个MySQL Connector Net,需要安装。然后就可以参考网上的教程,连接数据库,写sql语句,然后数据入库了。具体实现时,根据数据库表的设计生成了n个datatable,存入栏名,然后解析结果存入datatable中,然后从datatable中取数据,生成sql语句,进行入库。实现过程也较为顺利。之所以使用datatable作为缓存是因为我们数据结构的特殊性,里面有些字段有,有些字段没有,因此不能写统一的sql语句,只能先放在统一的datatable中,使用同一个函数生成sql,而不是每个表单独生成语句,这样会减少代码量。


五、日志的实现
    整个系统需要记录的日志较多,而且文件种类的不同,位置的不同,管理起来较为复杂,直接记录文件比较麻烦,说白了,就是懒。。。于是乎在网上搜索如何记录日志,c#下有个框架log4net,这是我第一次接触所谓的框架,入门较容易,理解起来有点难,因为里面使用了app.config这些配置文件,配置文件放置的位置,还有怎么加载,都不知道。关于配置文件,里面有很多配置参数不在赘述,网上一找一大堆,里面主要就是讲这个文件名、文件位置、文件格式、里面的内容排版什么的,还有一些比较高端的用法,比如文件最大可为多少M,如果超了,就自动新建一个文件,原来的日志文件名后面加个.1,一直可以.2等等等等。
      详细记录日志时又出现另一个坑,那就是每次记录日志,我要根据日志的类别,记录在不同的路径下,比如一个上报数据帧是来自于某个设备的,我要记录在设备文件夹下,对应的日期文件,另一个数据帧是告警数据帧,要直接记录在日期文件内,不对设备编号做区分。找了许久不知道该怎么实现,后来找到了配置文件中的某个参数是记录文件名的,只要每次记录前把文件名进行配置,就可以记录到对应的路径中,采用的是读xml文件,然后更改相应字段,重新加载一下配置文件就可以。

六、池的概念
      开发到这一步,似乎基本的东西都弄好了,只待继续优化。这里我使用了一个测试软件TCP/UDP Performance,可以在这个网址www.ikende.com中下载到。这个测试软件可以同时模拟n个客户端发送数据。但只能发送同一条内容,而且间隔时间固定。开客户端数量较多的时候,服务端处理数据很慢,毕竟我这个也属于高并发了。如果采用先前入库那一套,每次插入时,先打开连接,插入,然后关闭连接,效率可想而知会比较慢,这时我考虑用链接池,并且为了保障整个程序只有一个连接池,这里第一次接触了所谓的设计模式----单例模式,当然,目前为止,我也就接触过这么一种。每次要入库时,先从池中取一个对象,然后使用完归还就ok,大大减少了开关连接时消耗的时间。当中有个小插曲,就是使用池的效果没那么好,修改了一个参数,具体哪个参数忘记了,之后效率提升很快。


六、TCP协议的再理解
      整个系统进行到这里,似乎只待完善了,然后使用实际设备上手一测试,我傻掉了。数据来的超级快,也超级多。当然这点后期我们会进行相同数据过滤,暂且不表。之前处理数据,我采用的是所有客户端发过来的数据包放在一个大的list中,理所当然的想,来一帧,我解一帧数据。但我忽略了TCP协议,TCP是以流的形式传递数据,并不是我们以为的一帧来传递,它会自己根据网络情况来进行数据传输,因此,我以为的一帧,有可能是好几帧,这还只是一个客户端的情况,如果是n个客户端同时发,那就会出现客户端1的半帧+客户端2的半帧,这种粘包现象,因此我的数据缓存完全失效。有两个想法,从客户端接收到数据后,对数据添加客户端标记,比如IP之类的,解析的时候,如果帧完整,就解析,不完整就一直取,等另一半,想法挺好,但我不知道怎么做。另一个想法,就是在客户端类中加线程,各自处理各自的数据,这样就不会出现串台问题了。每个客户端处理每个客户端的事儿,听起来不错。于是乎,上手做了,开了几个客户端来测,内存卒。
      原因显而易见,来一个客户端就开一个线程,消耗太大,而且我们的服务器是要以1000台为基准,几台就不行了。遂寻求大佬帮助,还有个坑事儿,就是一般来讲,数据帧都是通过帧头帧尾校验来判断,我们数据帧的格式是定长消息头+不定长消息体,so,如果串台,根本不知道自己该从哪里继续读。
      大佬直接甩了个supersocket框架给我。。。

七、投入supersocket的怀抱
     直接看官网文档,理解每个类的意义,重点是理解它是怎么使用session来管理客户端的,另外就是对于TCP下的数据是如何解析的,supersocket的确很super啊,里面封装了很多东西,接收到的数据直接使用过滤器,就可以将每个帧取出,而且不会串台!它提供了好几种过滤器的方式,对于我这种头部格式固定,不定长消息体的数据帧,它也有一个相应的类可以使用,就是FixedHeaderReceiveFilter,通过过滤器,你可以将你接收的数据组合成你想要的Info格式,这个Info要继承IRequestInfo,像我的的话,为了方便使用,里面写了消息头结构体、消息体数组、还有整个数据帧的数组。然后再在MyAppServer中添加三个事件就ok,一个连接、一个接收到数据、一个断开,接收到数据的事件函数里,直接写,你是怎么操作你的Info就行了。其他的,supersocket都替你做好了。本以为学一个框架会比较麻烦,等真正使用了,就会觉得便利了。后面关于客户端的管理,直接使用SessionList就可以,实质上就是一个键值对,我使用了两个list来维护,一个是IP,一个是设备编号,IP用来维护设备的连接,设备编号是为了查询在线设备时,直接在界面显示设备编号的,实质都一样。

八、其他
      除了上面讲的几个大的部分,还剩一些比如入库时,将代号与实际意义相关联,http的实现,supersocket的一些配置,还有服务端发送给指定客户端消息,客户端和服务端心跳的维护,入库时使用事务,还有子表和主表关系复杂,主外键的坑事儿,GUID,SVN的学习和使用,这里不做赘述。

九、后记
      实际开发大致从三月多开始,到现在六月底结束,一直在不断摸索。我也是第一次做这种比较有实际意义的开发,每次使用新的东西,我都是先写一个小的test,测试通过后,再加到项目中,收获颇多。当然,这个采集软件还是存在很多问题,只是我现在可能没发现,在以后的日子,逐渐改进吧。

你可能感兴趣的:(网络编,服务)