优秀的毕业设计论文网
计算机 JAVA 电子信息 单片机 机械机电 模具 土木工程 建筑结构 论文
热门搜索词:网络 ASP.NET 汽车 电气 数控 PLC

053_串口通信服务

以下是资料介绍,如需要完整的请充值下载.
1.无需注册登录,支付后按照提示操作即可获取该资料.
2.资料以网页介绍的为准,下载后不会有水印.资料仅供学习参考之用.
  
资料介绍:

3 需求分析及设计方案
3.1 功能需求
3.1.1 服务器端
串口服务器上的串口通信服务打开一个固定的端口,监听客户端的Socket连接。若检测到客户端的连接则为客户端分配一个串口,供客户端发送数据。客户端同服务器协商好串口的配置信息,则客户端发送的数据服务器直接发送到串口,通过串口发送除去。若服务器检测到串口接受到数据,者直接把接收到的数据发送到客户端。即服务器中转串口和客户端的数据。这样一来客户端就好像直接链接到串口服务器上的串口,与服务器串口相连接的外设进行通信。串口服务器具有很多个串口形成一个串口池。串口池中的串口连接着不通或相同的外设,客户通过Internet联网的方式链接到服务器,同服务器的外设通信。 【买计算机毕业论文就到www.think58.com】
3.1.2 串口通信
串口通信服务管理整个串口池。服务程序对串口池进行合理管理和分配,服务器端接收到客户端的连接请求,服务器会从串口池中选取一个串口供客户端使用。当客户端通信完毕,断开同服务器的链接时,服务器端回收这个串口,以供其他客户端使用。在分配串口的时候不能让不同用户使用同一串口,让每一个客户端独立的使用某一个串口。当客户端链接上服务器后,服务器为客户端分配一个串口供客户端使用。这时候串口通信需要被实现。包括串口的打开,串口参数设置,读写数据到串口以及关闭串口。

think58.com

[版权所有:http://think58.com]


3.1.3 客户端
客户端的需求相对简单。连接服务器的客户端通过指定服务器地址和端口号连接到服务器,设置串口的通信参数,同服务器通信。可以发送数据到服务器和接受服务器发送回来的数据,并显示接收到的数据。通信完毕时断开通服务器的连接。
3.2 设计方案
3.2.1 服务器端
服务器端使用Windows NT服务来提供服务,此服务监听某一端口,等待客户端的链接。为客户端提供服务。使用Windows API自己定义服务的行为。服务管理串口池,服务启动初始化时,检测服务器的串口,串口统一进入一个全局安全队列。当客户端发起连接请求时,服务器开启一个线程来处理该客户的请求,直到客户端断开连接,线程退出执行。在线程中,从安全队列的队头出队列一个串口,该串口分配给该客户使用,客户端首先设置该串口的通信参数,否则按照默认串口通信参数进行通信。如果有多个客户同时连接服务器则服务器同时开启多个线程来处理多个用户的连接请求。分配给客户端使用的串口接收外设发来数据时,服务器通过连接的Socket发送到客户端,客户端处理接收到的数据。服务器端接收到客户端发来的数据时,把接收到的数据发送到分配给这个客户端的串口,通过串口发送到外设,外设处理接收到的数据从而做出反应,完成通信。 本文来自think58

[来源:http://www.think58.com]


3.2.2 串口通信
使用Windows API的串口操作函数,封装成串口类,来处理串口通信。此串口类统一操作串口池里的串口,根据不同的串口号来操作这些串口。从而达到每一个客户的使用一个串口,每个串口都是相同的操作。可以根据不同的通信设备为不同的串口设置不同的通信参数。串口通信负责处理串口的参数设置,串口的打开和关闭,数据的读和写,即写数据到串口和从串口读数据。打开串口时,服务处理此客户的线程开启另一线程监听串口是否有数据接收,当发现有数据来的时候,串口读出该数据通过Socket发送到客户端。
3.2.3 客户端
客户端使用MFC编程创建基于对话框的应用程序。该程序使用Windows Socket建立同指定服务器的数据连接。当建立好HTTP链接后,可设置串口的通信参数,如:波特率,数据位,奇偶校验,停止位等。设置好串口的通信参数后就可以同服务器分配给的串口链接的外设通信了。发送数据到服务器的串口,和接收服务器发送回来的数据。
3.2.4 开发工具
Microsoft Visual C++ 6.0是微软公司推出的开发Win32应用程序(Windows 95/98/2000/XP/NT)的、面向对象的可视化集成工具。它的最大优点就是提供了功能强大的MFC类库,MFC是一个很大的C++类层次结构,其中封装了大量的类及其函数,很多Windows程序所共有的标准内容可以由MFC的类来提供,MFC类为这些内容提供了用户接口的标准实现方法,程序员所要做的就是通过预定义的接口把具体应用程序特有的东西填入这个轮廓,这将简化编程工作,大大的减少程序员编写的代码数量,使编程工作变得更加轻松容易。 内容来自think58 [版权所有:http://think58.com]
4 具体设计流程与实现
4.1 安全队列
数据保护是在使用多线程技术时必须要解决的关键问题,防止多个线程同时操作某一变量,导致逻辑错误,是多线程协调运行的重要内容。线程安全队列这种数据保护机可以保护共享数据,实现线程间安全的数据传输。
一般地,每个进程中的线程都不知道进程中的其他线程的任何情况,除非我们在程序中明确的使他们互相可见。为了在不同时刻访问共享的资源,任何共享这些资源的线程都必须通过进程通信的手段进行调度。线程调度是一个复杂的问题,不正确的调度将导致进程的死锁。为了这种情况的发生,访问共享资源的线程必须进行同步。
临界区对象只有由同一个进程的线程共享,临界区对象在某一个时刻只能由一个线程所拥有。临界区对象不保证线程对临界区的访问顺序。进程负责分配临界区所使用的内存,一般声明一个类型为CRITICAL_SECTION的变量来完成此任务。在进程的线程使用临界区之前,必须使用函数InitializeCriticalSection或InitializeCriticalSectionAndSpinCount初始化临界区。线程使用函数EnterCriticalSection或函数TryEnterCriticalSection请求对临界区的拥有权。如果临界区对象正由其他线程所拥有,则函数EnterCriticalSection无限等待该拥有权。线程使用LeaveCriticalSection释放对临界区的拥有权。进程的所有线程都可以使用函数DeleteCriticalSection释放临界区对象初始化时分配的系统资源。 [资料来源:THINK58.com]
线程安全队列工作机制如图,操作队列的时候需要先进入临界区,操作完成后离开临界区,由此保证在任何时候都只有一个线程操作该队列。线程间的数据传输是安全可靠的。

图 2 安全队列工作机制
入队列相关代码:
BOOL Push(const Type & item)
{
EnterCriticalSection(&lock); //进入临界区
_safe_queue_item<Type> *next=new _safe_queue_item<Type> (item);
if (first==NULL)
last=first=next;
else {
last->next=next;
last=next;
}
LeaveCriticalSection(&lock); //离开临界区
return TRUE;
};
出队列相关代码:
Pop() {
EnterCriticalSection(&lock); //进入临界区
Type val;
_safe_queue_item<Type> *oldFirst=first;
if(first==NULL)
{
LeaveCriticalSection(&lock);
throw "SB Queue underflow !";
} else {
val=first->value;
first=first->next;
delete oldFirst;
LeaveCriticalSection(&lock); //离开临界区
}
4.2 服务应用程序 [资料来源:www.THINK58.com]
在基于NT的系统中,全部服务任务都是由服务控制管理器(SCM)系统管理。SCM维持注册表中已知服务列表,打开各项服务(即可以是开机时自动打开,也可以使用户请求打开)。SCM把服务列表和它们的打开状态保存在注册表中,新服务项在安装时加入此列表中。另外也可以删除服务项目。作为服务任务的程序是一个普通的exe文件,但是必须满足特定的要求才能确切地与SCM连接。微软已经详细编制了这些函数的程序流,必须紧密依据此流程表,否则服务项目就不能运行。要求如下:
1.服务项目的EXE文件必须有一个普通的Main或WinMain函数,此函数必须立即(特定情况下是在30秒之内)调用函数StartServiceCrtlDispatcher。调用之后,EXE文件就在SCM中注册,并且给出一个调用函数ServiceMain(在启动服务时调用)的指针。可以更改函数ServiceMain的函数名,然后就能在ServiceMain下面的记录文本中找到所用函数名的描述。main函数应该在注册ServiceMain之后返回。不能用命令行方式运行服务的exe文件。而应该在SCM知道的服务列表中安装,SCM会调用main函数,打开EXE文件。偶然用命令行方式执行服务时一定会出错,因为它不能连接SCM。
2.SCM在打开服务时将调用函数ServiceMain。例如,当管理员选择“控制面板”上“服务”的start按钮时,SCM将在一个工作线程中执行函数ServiceMain。函数ServiceMain完成多项工作。其中最为主要的是立即调用函数RegisterServiceCtrlHandler,该函数能用SCM注册一个函数Handler以调用控制要求。此函数的函数名可以随便更改,但大会在Handler下面的记录文本中列出。函数RegisterServiceCtrlHandler返回一个句柄,服务在给SCM发送状态信息时将用到该句柄。函数ServiceMain还开始将完成实际服务任务的工作现程。一旦线程开始,函数ServiceMain就等待一个事件的发生。直道服务停止,函数ServiceMain才返回。之后,重新调用函数ServiceMain,SCM将重新开始该项服务。

本文来自think58 [来源:http://www.think58.com]


3.函数Handler包含一个转换语句,用于分析来自SCM的控制请求。默认时,SCM发送以下控制常数:
SERVICE_CONTROL_STOP 通知服务停止
SERVICE_CONTROL_PAUSE 通知服务暂停
SERVICE_CONTROL_CONTINUE 通知服务重新开始
SERVICE_CONTROL_INTERROGATE 通知服务立即报告当前状态
SERVICE_CONTROL_SHUTDOWN 通知服务紧急关闭
用户也可以创建自定义常数(128~255之间),通过SCM把它们发送到服务项。当创建一个包含上述main、ServiceMain和Handler等函数的EXE文件以及一个包含服务线程的函数时,就得到了一个完整的服务任务。

图 3 服务中各部分之间的关系
Main函数,这是代码的入口。是在这里解析任何命令行参数并进行服务的安装,移除,启动等。提供真正服务代码的入口函数叫 ServiceMain。在服务第一次启动的时候,将该函数的地址传递给服务管理器。处理来自服务管理器命令消息的函数。这个函数叫 Handler,
服务回调函数,因为 ServiceMain 和 Handler 函数都是由系统来调用,所以它们必须遵循操作系统的参数传递规范和调用规范。也就是说,它们不能简单地作为某个 C++ 类的成员函数。这样就给封装带来一些不便,因为想把 Win32 服务的功能封装在一个 C++ 类中。为了解决这个问题,将 ServiceMain 和 Handler 函数创建成 CNTService 类的静态成员。这样就使得以创建可以由操作系统调用的函数。 但是,这样做还没有完全解决问题,因为系统不允许给被调用的函数传递任何形式的用户数据,所以我们无法确定对 C++ 对象特定实例的 ServiceMain 或 Handler 的调用。在这里用了一个非常简单但有局限的方法来解决这个问题。创建一个包含 C++ 对象指针的静态变量。这个变量是在该对象首次创建是进行初始化的。这样便限制每个服务应用只有一个C++对象。下面是 NTService.h 文件中的声明: [资料来源:THINK58.com]
class CNTService
{
  [...]
  // 静态数据
  static CNTService* m_pThis; // nasty hack to get object ptr
  [...]
};
下面是初始化 m_pThis 指针:
CNTService::CNTService(const char* szServiceName)
{
  m_pThis = this;
  [...]
}
ServiceMain函数:
ServiceMain函数提供真正服务代码的入口。它完成多项工作。其中最为主要的是立即调用函数RegisterServiceCtrlHandler。之后ServiceMain做一些服务初始化的工作,为工作线程的运行提供必要的准备。最后函数ServiceMain还开始将完成实际服务任务的工作线程。一旦线程开始,函数ServiceMain就等待一个事件的发生。直到服务停止,函数ServiceMain才返回。之后重新调用函数ServiceMain,SCM将重新开始该项服务。流程如图:

图 4 ServerMain函数流程
初始化工作OnInit函数:
OnInit函数的主要任务是初始化全局变量串口队列。扫描服务器的串口,如果串口可用,则把此串口进入安全队列。以备客户端请求使用。

图 5初始化工作流程
相关代码:
for(i=1;i<=ComCount;i++)
{
sprintf(buf,"\\\\.\\COM%d",i);

copyright think58

[资料来源:http://www.THINK58.com]


if (com.EPortAvailable==com.CheckPort(buf))
ComQueue.Push(i); //串口入队列
}
服务工作线程ServiceThread由Run函数实现:
ServiceThread由函数ServerMain打开,开始完成实际服务任务的工作线程,该线程执行服务的具体任务。该函数绑定服务器地址后监听本地端口等待客户端的连接,当客户端连接上后,创建子线程,由子线程来处理这个连接,函数继续监听本地端口。子线程处理完链接后关闭链接套接字退出。

图 6 服务工作线程流程
相关代码:
sockaddr_in service;
service.sin_family=AF_INET;
service.sin_addr.s_addr=INADDR_ANY; //ip地址
service.sin_port=htons(2501); //端口号
bind(m_socket,(SOCKADDR*)&service,sizeof(service));
listen(m_socket,20); //监听连接

while (m_bIsRunning) { //直到服务停止运行
AcceptSocket=SOCKET_ERROR;
while(AcceptSocket==SOCKET_ERROR)
{
AcceptSocket=accept(m_socket,NULL,NULL);//接收连接
}
//创建子线程处理该连接
CreateThread(NULL,NULL,AnswerThread,(LPVOID)AcceptSocket,0,&TId); [来源:http://www.think58.com]
} 本文来自think58
服务子线程:
由类的静态成员函数作为线程函数。在线程中,首先判断服务器是否还有剩余串口可分配,也就是判断串口队列中是否为空。若为空,则提示用户服务器串口已使用完毕,请稍后在试,断开同客户端的连接。否则串口队列出队列一个串口,分配给该客户使用,接下来就是打开串口,配置串口参数,开启串口监听线程,等待客户端发送的数据。直到客户端断开连接,串口重新进入队列,等待下次的分配。服务子线程退出。

图 7 服务子线程工作流程
相关代码:
if(!ComQueue.IsEmpty()) //判断队列是否为空
{
com=ComQueue.Pop(); //出队列
sprintf(buf,"\\\\.\\COM%d",com);
//打开串口配置默认通信参数
Serial.Open(buf,ClientSocket,1024,1024);
Serial.StartListener(); //开启监听线程
while(bytesRecv) //连接是否断开
{
while(bytesRecv==SOCKET_ERROR)
{
//接受客户端的数据
bytesRecv=recv(ClientSocket,recvbuf,1024,0);
}
Serial.Write(recvbuf,&Len); //写串口
}
Serial.StopListener(); //停止监听子线程 [来源:http://www.think58.com]
Serial.Close(); //关闭串口
ComQueue.Push(com); //串口入队列
}
Else
{
//发送提示信息到客户端
send(ClientSocket,szBuffer,strlen(szBuffer),0);
closesocket(ClientSocket);//断开连接
}
服务的安装
要使用服务,首先应进行安装。安装过程使SCM知道有这项服务存在,让SCM把它添加到服务列表中。通过调用函数OpenSCManager,打开一个到服务控制管理器的连接。然后调用函数CreateService在SCM数据库中安装服务。函数CreateService的调用中使用了打开SCM的指针,服务名,识别符以及一族填充其他值的基本参数。服务名是指在scm内部使用的服务的名字,删除服务时指定改名字可删除该服务,识别符是指在“控制面板”的“服务”中表示服务项的识别符。最后关闭服务控制管理器的连接。

图 8 服务安装流程
服务的卸载
卸载服务时,同样需要先打开一个到服务控制管理器的连接。然后查询该服务当前是否是被停止,若还未停止,则调用函数ControlService尝试停止该服务。这样做有利于卸载服务项:如果服务在删除过程中继续运行,则“控制面板”中”服务“将保留与服务的连接,实际上服务可以继续运行;当有人试图停止该“孤儿”服务时,“服务”可能就会抗议。先停止服务再删除它,就可以避免该问题。服务停止后,调用函数DeleteService删除该服务。

think58好,好think58

[资料来源:www.THINK58.com]



图 9 服务卸载流程

内容来自think58 [资料来源:www.THINK58.com]

[资料来源:THINK58.com]