虽然目前.NET的主流的开发基本上是基于Web方式(传统的Web方式和Silvelight方式)、基于Winform方式(传统的Winform模式和WPF方式等)、基于服务应用方式(传统的WebService和WCF服务方式)等主要几种开发,另外,还有一种就是基于Socket协议的开发方式,不同于高级服务层的WebService和WCF服务,基于Socket协议开发是较为底层的开发方式,它往往具有更加灵活,可控性更高的优点,不过相对来说,开发难度也会大一些。
我由于工作需要,需要开发一个数据采集客户端,监控指定目录的文件变化,登陆服务器端并传送数据内容,本人将其重构优化,逐步提炼把它升级为一种Socket框架的方式来对待,其中涉及一些有意思的技术应用,在此分享讨论下。首先我们看来看数据采集客户端的主界面。
一般的Socket客户端需要涉及到服务启动、服务停止、状态自动检查、用户登录、用户注销、日志记录、协议数据组装拆包、协议数据收发、多线程数据处理、数据展示通知、参数配置等等功能。本文命名为Socket开发框架之数据采集客户端,就是想站在一个较为通用、完好封装的角度上介绍这些功能点的实现。
1、 服务启动及服务停止
根据不同的业务需要,我创建几个业务管理类,分别是CommonManager、DispatchManager、FileWatcherManger、LoginManager、ReqAnsManager。
其中CommonManager是其他业务管理类的总管,并且包含和Socket服务器连接的控制管理,DispatchManager则是负责解析收到的数据并对不同数据进行分发处理,FileWatcherManger是实现对指定目录的文件进行监控并对其数据进行管理,LoginManager主要是实现登录控制管理、ReqAnsManager是对发送数据包需要后续验证处理的管理业务类。他们的关系如图所示:
其中重要的总管CommonManager类负责管理各类的协调,其中启动、停止及退出实现如下所示:
#region启动和退出
bool_isStart=false;
publicDateTimeStartTime;
publicDateTimeStopTime;
publicboolIsStart
{
get{return_isStart;}
set{_isStart=value;}
}
publicvoidStart()
{
if(!IsStart)
{
StartTime=DateTime.Now;
DispatchManager.Instance.Start();
LoginManager.Instance.Login();//使用默认账号密码登录
this.CheckTimer();
TimeSpants=(TimeSpan)(DateTime.Now-this.StartTime);
Log.WriteInfo(string.Format("[{0}]结束启动服务.|cost:{1}ms",this._Name,ts.TotalMilliseconds));
IsStart=true;
}
}
publicvoidStop()
{
if(pcClient.Connected)
{
pcClient.DisConnect();
}
this.StopBaseDataRefresh();
FileWatcherManger.Instance.StopFileWatcher();
this.StopCheckTimer();
StopTime=DateTime.Now;
Portal.gc.MainDialog.SetNotifyStatus(NotifyIconHelper.Status.Offline);
Log.WriteInfo(string.Format("操作者主动停止服务"));
IsStart=false;
}
publicvoidExit()
{
if(IsProcess)
{
Application.ExitThread();
System.Diagnostics.Process.GetCurrentProcess().Kill();
}
else
{
Stop();
}
}
#endregion
#region终端通信
PCDataClientpcClient=newPCDataClient();
publicboolConnected
{
get{returnpcClient.Connected;}
}
///<summary>
///连接服务器
///</summary>
publicvoidConnect(stringip,intport)
{
pcClient.Connect(ip,port);
}
///<summary>
///不指定服务器ip和port时,使用默认值
///</summary>
publicvoidConnect()
{
this.Connect(PCDataCollector_Config.Default.ServerIP,PCDataCollector_Config.Default.ServerPort);
}
publicboolSend(stringsend)
{
if(!this.Connected)
{
Connect();//确保连接上
}
returnpcClient.SendTo(send);
}
#endregion
2、 状态自动检查
另外,CommonManager类需要启用一个定时器,来定时检测Socket客户端的连接情况,数据接收线程的正常情况,数据处理分派业务类的正常情况,把它放到一个Timer处理定时检测的实现。
///<summary>
///服务检查
///</summary>
publicvoidCheckTimer()
{
if(_CheckTimer==null)
{
_CheckTimer=newSystem.Threading.Timer(newTimerCallback(Check));
_CheckTimer.Change(PCDataCollector_Config.Default.CheckTimerSpan,PCDataCollector_Config.Default.CheckTimerSpan);
}
Log.WriteInfo(string.Format("[{0}]服务检查线程启动.....",this._Name));
}
///<summary>
///停止服务器检查
///</summary>
publicvoidStopCheckTimer()
{
if(_CheckTimer!=null)
{
_CheckTimer.Change(Timeout.Infinite,Timeout.Infinite);
}
}
///<summary>
///检查
///</summary>
///<paramname="stateInfo"></param>
privatevoidCheck(ObjectstateInfo)
{
if(_CheckTimer!=null)
{
_CheckTimer.Change(Timeout.Infinite,Timeout.Infinite);
}
Check();
if(_CheckTimer!=null)
{
_CheckTimer.Change(PCDataCollector_Config.Default.CheckTimerSpan,PCDataCollector_Config.Default.CheckTimerSpan);
}
}
publicvoidCheck()
{
pcClient.CheckConnect();
ReceiverForServer.Instance.Check();
DispatchManager.Instance.Check();
PostServerInfo();
}
这样终端就能定时的检测和通讯服务器之间的连接以及数据处理线程的正常运行。
3、 用户登录及用户注销
用户登录,首先通过发送登录协议指令并在内存中记录发送的协议包,等待服务器的响应,当收到服务器的登录响应后 ,执行相关的操作,如启动文件目录监控,为发送数据做好准备。
publicvoidLogin()
{
Login(PCDataCollector_Config.Default.UserName,PCDataCollector_Config.Default.Password);
}
///<summary>
///使用账号密码登录
///</summary>
publicvoidLogin(stringuserNo,stringpassword)
{
if(string.IsNullOrEmpty(userNo)&&string.IsNullOrEmpty(password))
{
thrownewException("用户名或密码不能为空!");
}
this.userNo=userNo;
this.password=password;
SendLogin();
}
///<summary>
///重新登录
///</summary>
publicvoidReLogin()
{
SendLogin();
}
privatevoidSendLogin()
{
stringseqNo=DateTime.Now.ToString("yyyyMMdd")+newRandom().Next(99999).ToString().PadLeft(5,'0');//发送请求
AuthenticationRequestrequestData=newAuthenticationRequest(seqNo,userNo,password);
CommonManager.Instance.Send(requestData.ToString());
//记录请求
ReqAnsManager.Instance.Add(newRequestRecord(DataTypeKey.AuthenticationRequest,seqNo,DateTime.Now.AddSeconds(10)));
Log.WriteInfo(string.Format("正在登录。。。。{0}",tryCount));
Interlocked.Increment(reftryCount);//计数
}
///<summary>
///服务器响应处理
///</summary>
publicvoidHandleLoginResult(AuthenticationAnswerDatadata)
{
try
{
RequestRecordrecord=ReqAnsManager.Instance.Find(data.SeqNo,DataTypeKey.AuthenticationRequest);
if(record!=null)
{
ReqAnsManager.Instance.Remove(record);
if(data.ValidateResult==0)
{
tryCount=0;//重置失败次数为0
lastLoginTime=DateTime.Now;
isLogined=true;
Portal.gc.MainDialog.SetNotifyStatus(NotifyIconHelper.Status.Online);
Log.WriteInfo("登录成功!");
//ThreadPool.QueueUserWorkItem(DataAccess.Instance.LoadBaseData);
//CommonManager.Instance.StarBaseDataRefresh();
//登录成功后,对指定文件夹进行监控,自动发送数据
FileWatcherManger.Instance.StartFileWatcher();
}
else
{
lastLoginTime=DateTime.Now;
isLogined=false;
Portal.gc.MainDialog.SetNotifyStatus(NotifyIconHelper.Status.Offline);
Log.WriteError("登录失败:"+data.Message);
if(tryCount<PCDataCollector_Config.Default.TryLoginCount)
{
Thread.Sleep(100);
LoginManager.Instance.ReLogin();
}
else
{
Log.WriteInfo(string.Format("尝试登录失败超过【{0}】次,等待【{1}】秒后再进行连接!",
PCDataCollector_Config.Default.TryLoginCount,PCDataCollector_Config.Default.ReConnectSecconds));
tryCount=0;//重置失败次数为0
Thread.Sleep(PCDataCollector_Config.Default.ReConnectSecconds*1000);
LoginManager.Instance.ReLogin();
}
}
}
}
catch(Exceptionex)
{
Log.WriteError("初始化异常:"+ex.Message);
CommonManager.Instance.Exit();
}
}
用户的注销及断开,是通过客户端连接类来处理,该类命名为PCDataClient,其继承自Socket客户端处理类BaseSocketClient类,基类封装了对一般Socket类的连接、接收、发送等操作。该类只需要把解析好的数据传送给ReceiverForServer类进行处理,而该类会把数据分派给处理类DispatchManager类进行进一步处理。
publicclassPCDataClient:BaseSocketClient
{
protectedoverridevoidOnRead(PreDatadata)
{
ReceiverForServer.Instance.AppendPreData(data);
ReceiverForServer.Instance.Check();
}
///<summary>
///断开连接
///</summary>
publicoverridevoidDisConnect()
{
base.DisConnect();
LoginManager.Instance.isLogined=false;
LoginManager.Instance.tryCount=0;//重置失败次数为0
//客户端和服务器连接中断
Log.WriteError(string.Format("客户端和服务器连接中断"));
}
///<summary>
///由于基类只是在调用CheckConnect()函数时确保连接,并没有重新登录,
///因此需要重载基类,如果断网了,连接后重新执行登录,否则收发数据不成功
///</summary>
publicoverridevoidCheckConnect()
{
if(!this.Connected)
{
Connect(IP,Port);
LoginManager.Instance.Login();
}
Log.WriteInfo(string.Format("[{0}]服务连接|IP:{1}|Port:{2}|receive:{3}|send:{4}",
_Name,IP,Port,ReceivePackCount,SendPackCount));
}
}
对接收到的数据进行统一处理,只需要继承基类BaseReceiver即可,相关基类的实现可以参考随笔《Socket开发探秘--基类及公共类的定义》。
publicclassReceiverForServer:BaseReceiver
{
#region单件
///<summary>
///Receiver实例
///</summary>
privatestaticReceiverForServer_manager;
///<summary>
///锁定实例
///</summary>
privatestaticobjectoClassLock=typeof(ReceiverForServer);
///<summary>
///得到该实例
///</summary>
///<returns></returns>
publicstaticReceiverForServerInstance
{
get
{
lock(oClassLock)//加锁只生成一个实例
{
if(_manager==null)
{
_manager=newReceiverForServer();
}
}
return_manager;
}
}
///<summary>
///私有的构造函数,防止从外部实例化
///</summary>
privateReceiverForServer()
{
}
#endregion
publicoverridevoidPreDataHandle(PreDatadata)
{
DispatchManager.Instance.Dispatch(data);
}
}
4、 协议数据组装拆包
数据的组包和拆包,在较早的随笔《Socket开发探秘--数据封包和拆包》有详细的介绍,数据的组装和拆包主要通过反射原理,把字符串数据转换为对应的实体类,或者把实体类组装成字符串。
5、 数据分派处理
publicsealedclassDispatchManager
{
#region单例
privatestaticDispatchManager_manager=newDispatchManager();
publicstaticDispatchManagerInstance
{
get
{
return_manager;
}
}
privateDispatchManager(){}
#endregion
#region委托、事件
//认证应答事件
publicdelegatevoidAuthenticationAnswerDelegate(AuthenticationAnswerDatadata);
publiceventAuthenticationAnswerDelegateSignalAuthenticationAnswer;
//空车位上传数据应答事件
publicdelegatevoidPCParkingDataAnswerDelegate(PCParkingInfoAnswerdata);
publiceventPCParkingDataAnswerDelegateparkingDataAnswer;
//明细信息上传的应答事件
publicdelegatevoidPCTicketDataAnswerDelegate(PCTicketDataAnswerdata);
publiceventPCTicketDataAnswerDelegateticketDataAnswer;
#endregion
///<summary>
///启动,关联所有socket接收过来分发的事件
///</summary>
publicvoidStart()
{
//注册登录结果处理方法
SignalAuthenticationAnswer+=newDispatchManager.AuthenticationAnswerDelegate(LoginManager.Instance.HandleLoginResult);
parkingDataAnswer+=newPCParkingDataAnswerDelegate(FileWatcherManger.Instance.HandleAnswerResult);
ticketDataAnswer+=newPCTicketDataAnswerDelegate(FileWatcherManger.Instance.HandleAnswerResult);
}
#region方法
///<summary>
///分发应答数据
///对于部分类型的应答,需要进行内部广播
///</summary>
///<paramname="predata"></param>
publicvoidDispatch(PreDatapredata)
{
if(predata==null)
return;
switch(predata.Key)
{
//身份认证应答
caseDataTypeKey.AuthenticationAnswer:
AuthenticationAnswerDataauthData=newAuthenticationAnswerData(predata.Content);
SignalAuthenticationAnswer(authData);
break;
caseDataTypeKey.PCParkingInfoAnswer:
PCParkingInfoAnswerspaceData=newPCParkingInfoAnswer(predata.Content);
parkingDataAnswer(spaceData);
break;
caseDataTypeKey.PCTicketDataAnswer:
PCTicketDataAnswerpickBaseData=newPCTicketDataAnswer(predata.Content);
ticketDataAnswer(pickBaseData);
break;
default:
break;
}
}
///<summary>
///系统检查
///</summary>
publicvoidCheck()
{
}
#endregion
}
分享到:
相关推荐
矿用产量数据采集应用实例,使用HPsocket框架结构,通过UDP协议接收从服务器上传的数据并进行解码
安装好需要的库以后,可直接make编译...实现功能:此源码是linux平台C语言实现的采集摄像头数据并压缩成H264或者MJPEG的数据流上传到服务器,由服务器分发到各个客户端; 如有不详尽之处可以联系qq:294050476欢迎交流
你无须了解如何使用 Socket, 如何维护 Socket 连接和 Socket 如何工作,但是你却可以使用 SuperSocket 很容易的开发出一款 Socket 服务器端软件,例如游戏服务器,GPS 服务器, 工业控制服务和数据采集服务器等等。...
通用数据传输socket构架源码 源码描述: 一、源码特点 1、此软件的基本思想是为了建立一套简单稳点可多负载的架构,通用数据通讯构建,设计基于TCP通信的交互框架。目前以达到3.0版本,可规范先后台交互处理,可...
通用数据传输socket构架源码 源码描述: 一、源码特点 1、此软件的基本思想是为了建立一套简单稳点可多负载的架构,通用数据通讯构建,设计基于TCP通信的交互框架。目前以达到3.0版本,可规范先后台交互处理,可...
3、摄像头客户端通过socket发送一帧压缩的数据 4、服务器端管理客户端(包括摄像头)的网络通断 5、服务器接收到摄像头的一帧H264数据分发给在线的客户端 6、客户端接收到服务器转发的H264数据解码通过SDL2显示 存在...
该项目由客户端、服务器构成,采用大并发通信框架思想和自定义协议,基于 TCP/IP 通讯协议封装了包含通信组件、动态数组以及线程池等技术的通信框架。以及基于 OpenCV 的图像处理功能。 功能模块:红绿灯检测、车辆...
3、数据采集和邮件群发。这是一个基于多线程的邮件营销平台,核心技术包括网络爬虫、多线程、HTML解析、邮件发送、生产者消费者模式等。 注:以上三个案例,上课时会根据每个班的课堂反馈选择其中一个案例予以讲解...
04 Form组件之动态绑定数据 第60章 Django序列化共6课 第61章 01 上节内容回顾 02 上传文件 03 制作上传按钮 04 Form组件上传文件 05 上传相关内容梳理 06 Model操作知识提问 07 Model操作概述 08 Model字段 09 ...
此次开发,按个人的开发与运维经验,结合以往的采集,做了一些功能添加和效率优化,代码完全个人重构。 netty网关,支持百万客户端连接,压力测试ing...,并优化了与服务端集群通信,以往轮询往多个服务器发消息,...
概述:分布式温度监控系统基于 STM32 系类芯片开发,支持采集多达六个分节点的温度数据,网关节点收集分节点的数据并通过 WIFI 上传云端远程实时监视,也可本地连接串口与 PC 端通讯,上位机实时显示分节点数据。...