新书推介:《语义网技术体系》
作者:瞿裕忠,胡伟,程龚
   XML论坛     W3CHINA.ORG讨论区     >>计算机科学论坛<<     SOAChina论坛     Blog     开放翻译计划     新浪微博  
 
  • 首页
  • 登录
  • 注册
  • 软件下载
  • 资料下载
  • 核心成员
  • 帮助
  •   Add to Google

    >> 本版讨论高级C/C++编程、代码重构(Refactoring)、极限编程(XP)、泛型编程等话题
    [返回] 计算机科学论坛计算机技术与应用『 C/C++编程思想 』 → 一个通信演示程序(精品) 查看新帖用户列表

      发表一个新主题  发表一个新投票  回复主题  (订阅本版) 您是本帖的第 6335 个阅读者浏览上一篇主题  刷新本主题   树形显示贴子 浏览下一篇主题
     * 贴子主题: 一个通信演示程序(精品) 举报  打印  推荐  IE收藏夹 
       本主题类别:     
     卷积内核 帅哥哟,离线,有人找我吗?
      
      
      威望:8
      头衔:总统
      等级:博士二年级(版主)
      文章:3942
      积分:27590
      门派:XML.ORG.CN
      注册:2004/7/21

    姓名:(无权查看)
    城市:(无权查看)
    院校:(无权查看)
    给卷积内核发送一个短消息 把卷积内核加入好友 查看卷积内核的个人资料 搜索卷积内核在『 C/C++编程思想 』的所有贴子 访问卷积内核的主页 引用回复这个贴子 回复这个贴子 查看卷积内核的博客楼主
    发贴心情 一个通信演示程序(精品)

    一个通信演示程序(精品)
    网上资源,  软件技术

    卷积内核 发表于 2005-5-19 10:05:14

     

    为了使读者更好地掌握本章的概念,这里举一个具体实例来说明问题。如图12.1所示,例子程序名为Terminal,是一个简单的TTY终端仿真程序。读者可以用该程序打开一个串行口,该程序会把用户的键盘输入发送给串行口,并把从串口接收到的字符显示在视图中。用户通过选择File->Connect命令来打开串行口,选择File->Disconnect命令则关闭串行口。

    按此在新窗口浏览图片500)this.width=500" border=0>

    图12.1 Terminal终端仿真程序

     

    当用户选择File->Settings...命令时,会弹出一个Communication settings对话框,如图12.2所示。该对话框主要用来设置串行口,包括端口、波特率、每字节位数、校验、停止位数和流控制。

    按此在新窗口浏览图片500)this.width=500" border=0>

    图12.2 Communication settings对话框

     

    通过该对话框也可以设置TTY终端仿真的属性,如果选择New Line(自动换行),那么每当从串口读到回车符(‘\r’)时,视图中的正文就会换行,否则,只有在读到换行符(‘\n’)时才会换行。如果选择Local echo(本地回显),那么发送的字符会在视图中显示出来。

    终端仿真程序的特点是数据的传输没有规律。因为键盘输入速度有限,所以发送的数据量较小,但接收的数据源是不确定的,所以有可能会有大量数据高速涌入的情况发生。根据Terminal的这些特性,我们在程序中创建了一个辅助工作者线程专门来监视串行口的输入。由于写入串行口的数据量不大,不会太费时,所以在主线程中完成写端口的任务是可以的,不必另外创建线程。

    现在就让我们开始工作。请读者按下面几步进行:

    用AppWizard建立一个名为Terminal的MFC应用程序。在MFC AppWizard对话框的第1步选择Single document,在第4步去掉Docking toolbar的选择,在第6步把CTerminalView的基类改为CEditView。

    在Terminal工程的资源视图中打开IDR_MAINFRAME菜单资源。去掉Edit菜单和View菜单,并去掉File菜单中除Exit以外的所有菜单项。然后在File菜单中加入三个菜单项,如表12.5所示。

     

    表12.5 新菜单项


    标题


    ID


    Settings...
    ID_FILE_SETTINGS

    Connect
    ID_FILE_CONNECT

    Disconnect
    ID_FILE_DISCONNECT

     

     

    用ClassWizard为CTerminalDoc类创建三个与上表菜单消息对应的命令处理函数,使用缺省的函数名。为ID_FILE_CONNECT和ID_FILE_DISCONNECT命令创建命令更新处理函数。另外,用ClassWizard为该类加入CanCloseFrame成员函数。

    用ClassWizard为CTerminalView类创建OnChar函数,该函数用来把用户键入的字符向串行口输出。

    新建一个对话框模板资源,令其ID为IDD_COMSETTINGS。请按图12.2和表12.6设计对话框模板。

     

    表12.6 通信设置对话框中的主要控件


    控件


    ID


    属性设置


    Base options组框
    缺省
    标题为Base options

    Port组合框
    IDC_PORT
    Drop List,不选Sort,初始列表为COM1、COM2、COM3、COM4

    Baud rate组合框
    IDC_BAUD
    Drop List,不选Sort,初始列表为300、600、1200、2400、9600、14400、19200、38400、57600

    Data bits组合框
    IDC_DATABITS
    Drop List,不选Sort,初列表为5、6、7、8

    Parity组合框
    IDC_PARITY
    Drop List,不选Sort,初列表为None、Even、Odd

    Stop bits组合框
    IDC_STOPBITS
    Drop List,不选Sort,初列表为1、1.5、2

    Flow control组框
    缺省
    标题为Flow control

    None单选按钮
    IDC_FLOWCTRL
    标题为None,选择Group属性

    RTS/CTS单选按钮
    缺省
    标题为RTS/CTS

    XON/XOFF单选按钮
    缺省
    标题为XON/XOFF

    TTY options组框
    缺省
    标题为TTY options

    New line检查框
    IDC_NEWLINE
    标题为New line

    Local echo检查框
    IDC_ECHO
    标题为Local echo

     

     

    打开ClassWizard,为IDD_COMSETTINGS模板创建一个名为CSetupDlg的对话框类。为该类加入OnInitDialog成员函数,并按表12.7加入数据成员。

     

    表12.7 CSetupDlg类的数据成员


    控件ID


    变量名


    数据类型


    IDC_BAND
    m_sBaud
    CString

    IDC_DATABITS
    m_sDataBits
    CString

    IDC_ECHO
    m_bEcho
    BOOL

    IDC_FLOWCTRL
    m_nFlowCtrl
    int

    IDC_NEWLINE
    m_bNewLine
    BOOL

    IDC_PARITY
    m_nParity
    int

    IDC_PORT
    m_sPort
    CString

    IDC_STOPBITS
    m_nStopBits
    int

     

     

    按清单12.6、12.7和12.8修改程序。清单12.6列出了CTerminalDoc类的部分代码,清单12.7是CTerminalView的部分代码,清单12.8是CSetupDlg类的部分代码。在本例中使用了WM_COMMNOTIFY消息。虽然在Win32中,WM_COMMNOTIFY消息已经取消,系统自己不会产生该消息,但Visual C++对该消息的定义依然保留。考虑到使用习惯,Terminal程序辅助线程通过发送该消息来通知视图有通信事件发生。

     

    清单12.6 CTerminalDoc类的部分代码

    // TerminalDoc.h : interface of the CTerminalDoc class

    //

    /////////////////////////////////////////////////////////////////////////////

     

     

    #define MAXBLOCK 2048

    #define XON 0x11

    #define XOFF 0x13

     

    UINT CommProc(LPVOID pParam);

     

    class CTerminalDoc : public CDocument

    {

    protected: // create from serialization only

    CTerminalDoc();

    DECLARE_DYNCREATE(CTerminalDoc)

     

    // Attributes

    public:

     

    CWinThread* m_pThread; // 代表辅助线程

    volatile BOOL m_bConnected;

    volatile HWND m_hTermWnd;

    volatile HANDLE m_hPostMsgEvent; // 用于WM_COMMNOTIFY消息的事件对象

    OVERLAPPED m_osRead, m_osWrite; // 用于重叠读/写

     

    volatile HANDLE m_hCom; // 串行口句柄

    int m_nBaud;

    int m_nDataBits;

    BOOL m_bEcho;

    int m_nFlowCtrl;

    BOOL m_bNewLine;

    int m_nParity;

    CString m_sPort;

    int m_nStopBits;

     

     

    // Operations

    public:

     

    BOOL ConfigConnection();

    BOOL OpenConnection();

    void CloseConnection();

    DWORD ReadComm(char *buf,DWORD dwLength);

    DWORD WriteComm(char *buf,DWORD dwLength);

    // Overrides

    . . .

    };

     

    /////////////////////////////////////////////////////////////////////////////

    // TerminalDoc.cpp : implementation of the CTerminalDoc class

    //

     

    #include "SetupDlg.h"

     

    CTerminalDoc::CTerminalDoc()

    {

    // TODO: add one-time construction code here

     

    m_bConnected=FALSE;

    m_pThread=NULL;

     

    m_nBaud = 9600;

    m_nDataBits = 8;

    m_bEcho = FALSE;

    m_nFlowCtrl = 0;

    m_bNewLine = FALSE;

    m_nParity = 0;

    m_sPort = "COM2";

    m_nStopBits = 0;

    }

     

    CTerminalDoc::~CTerminalDoc()

    {

     

    if(m_bConnected)

    CloseConnection();

    // 删除事件句柄

    if(m_hPostMsgEvent)

    CloseHandle(m_hPostMsgEvent);

    if(m_osRead.hEvent)

    CloseHandle(m_osRead.hEvent);

    if(m_osWrite.hEvent)

    CloseHandle(m_osWrite.hEvent);

    }

     

    BOOL CTerminalDoc::OnNewDocument()

    {

    if (!CDocument::OnNewDocument())

    return FALSE;

    ((CEditView*)m_viewList.GetHead())->SetWindowText(NULL);

     

    // TODO: add reinitialization code here

    // (SDI documents will reuse this document)

     

     

    // 为WM_COMMNOTIFY消息创建事件对象,手工重置,初始化为有信号的

    if((m_hPostMsgEvent=CreateEvent(NULL, TRUE, TRUE, NULL))==NULL)

    return FALSE;

    memset(&m_osRead, 0, sizeof(OVERLAPPED));

    memset(&m_osWrite, 0, sizeof(OVERLAPPED));

    // 为重叠读创建事件对象,手工重置,初始化为无信号的

    if((m_osRead.hEvent=CreateEvent(NULL, TRUE, FALSE, NULL))==NULL)

    return FALSE;

    // 为重叠写创建事件对象,手工重置,初始化为无信号的

    if((m_osWrite.hEvent=CreateEvent(NULL, TRUE, FALSE, NULL))==NULL)

    return FALSE;

    return TRUE;

    }

     

    void CTerminalDoc::OnFileConnect()

    {

    // TODO: Add your command handler code here

     

    if(!OpenConnection())

    AfxMessageBox("Can't open connection");

    }

     

    void CTerminalDoc::OnFileDisconnect()

    {

    // TODO: Add your command handler code here

     

    CloseConnection();

    }

     

    void CTerminalDoc::OnUpdateFileConnect(CCmdUI* pCmdUI)

    {

    // TODO: Add your command update UI handler code here

     

    pCmdUI->Enable(!m_bConnected);

    }

     

    void CTerminalDoc::OnUpdateFileDisconnect(CCmdUI* pCmdUI)

    {

    // TODO: Add your command update UI handler code here

     

    pCmdUI->Enable(m_bConnected);

    }

     

     

    // 打开并配置串行口,建立工作者线程

    BOOL CTerminalDoc::OpenConnection()

    {

    COMMTIMEOUTS TimeOuts;

    POSITION firstViewPos;

    CView *pView;

     

    firstViewPos=GetFirstViewPosition();

    pView=GetNextView(firstViewPos);

    m_hTermWnd=pView->GetSafeHwnd();

     

    if(m_bConnected)

    return FALSE;

    m_hCom=CreateFile(m_sPort, GENERIC_READ | GENERIC_WRITE, 0, NULL,

    OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,

    NULL); // 重叠方式

    if(m_hCom==INVALID_HANDLE_VALUE)

    return FALSE;

    SetupComm(m_hCom,MAXBLOCK,MAXBLOCK);

    SetCommMask(m_hCom, EV_RXCHAR);

     

    // 把间隔超时设为最大,把总超时设为0将导致ReadFile立即返回并完成操作

    TimeOuts.ReadIntervalTimeout=MAXDWORD;

    TimeOuts.ReadTotalTimeoutMultiplier=0;

    TimeOuts.ReadTotalTimeoutConstant=0;

    /* 设置写超时以指定WriteComm成员函数中的

    GetOverlappedResult函数的等待时间*/

    TimeOuts.WriteTotalTimeoutMultiplier=50;

    TimeOuts.WriteTotalTimeoutConstant=2000;

    SetCommTimeouts(m_hCom, &TimeOuts);

    if(ConfigConnection())

    {

    m_pThread=AfxBeginThread(CommProc, this, THREAD_PRIORITY_NORMAL,

    0, CREATE_SUSPENDED, NULL); // 创建并挂起线程

    if(m_pThread==NULL)

    {

    CloseHandle(m_hCom);

    return FALSE;

    }

    else

    {

    m_bConnected=TRUE;

    m_pThread->ResumeThread(); // 恢复线程运行

    }

    }

    else

    {

    CloseHandle(m_hCom);

    return FALSE;

    }

    return TRUE;

    }

     

     

    // 结束工作者线程,关闭串行口

    void CTerminalDoc::CloseConnection()

    {

    if(!m_bConnected) return;

    m_bConnected=FALSE;

     

    //结束CommProc线程中WaitSingleObject函数的等待

    SetEvent(m_hPostMsgEvent);

     

    //结束CommProc线程中WaitCommEvent的等待

    SetCommMask(m_hCom, 0);

     

    //等待辅助线程终止

    WaitForSingleObject(m_pThread->m_hThread, INFINITE);

    m_pThread=NULL;

    CloseHandle(m_hCom);

    }

     

    // 让用户设置串行口

    void CTerminalDoc::OnFileSettings()

    {

    // TODO: Add your command handler code here

     

    CSetupDlg dlg;

    CString str;

     

    dlg.m_bConnected=m_bConnected;

    dlg.m_sPort=m_sPort;

    str.Format("%d",m_nBaud);

    dlg.m_sBaud=str;

    str.Format("%d",m_nDataBits);

    dlg.m_sDataBits=str;

    dlg.m_nParity=m_nParity;

    dlg.m_nStopBits=m_nStopBits;

    dlg.m_nFlowCtrl=m_nFlowCtrl;

    dlg.m_bEcho=m_bEcho;

    dlg.m_bNewLine=m_bNewLine;

    if(dlg.DoModal()==IDOK)

    {

    m_sPort=dlg.m_sPort;

    m_nBaud=atoi(dlg.m_sBaud);

    m_nDataBits=atoi(dlg.m_sDataBits);

    m_nParity=dlg.m_nParity;

    m_nStopBits=dlg.m_nStopBits;

    m_nFlowCtrl=dlg.m_nFlowCtrl;

    m_bEcho=dlg.m_bEcho;

    m_bNewLine=dlg.m_bNewLine;

    if(m_bConnected)

    if(!ConfigConnection())

    AfxMessageBox("Can't realize the settings!");

    }

    }

     

     

    // 配置串行口

    BOOL CTerminalDoc::ConfigConnection()

    {

    DCB dcb;

     

    if(!GetCommState(m_hCom, &dcb))

    return FALSE;

    dcb.fBinary=TRUE;

    dcb.BaudRate=m_nBaud; // 波特率

    dcb.ByteSize=m_nDataBits; // 每字节位数

    dcb.fParity=TRUE;

    switch(m_nParity) // 校验设置

    {

    case 0: dcb.Parity=NOPARITY;

    break;

    case 1: dcb.Parity=EVENPARITY;

    break;

    case 2: dcb.Parity=ODDPARITY;

    break;

    default:;

    }

    switch(m_nStopBits) // 停止位

    {

    case 0: dcb.StopBits=ONESTOPBIT;

    break;

    case 1: dcb.StopBits=ONE5STOPBITS;

    break;

    case 2: dcb.StopBits=TWOSTOPBITS;

    break;

    default:;

    }

    // 硬件流控制设置

    dcb.fOutxCtsFlow=m_nFlowCtrl==1;

    dcb.fRtsControl=m_nFlowCtrl==1?

    RTS_CONTROL_HANDSHAKE:RTS_CONTROL_ENABLE;

    // XON/XOFF流控制设置

    dcb.fInX=dcb.fOutX=m_nFlowCtrl==2;

    dcb.XonChar=XON;

    dcb.XoffChar=XOFF;

    dcb.XonLim=50;

    dcb.XoffLim=50;

    return SetCommState(m_hCom, &dcb);

    }

     

     

    // 从串行口输入缓冲区中读入指定数量的字符

    DWORD CTerminalDoc::ReadComm(char *buf,DWORD dwLength)

    {

    DWORD length=0;

    COMSTAT ComStat;

    DWORD dwErrorFlags;

    ClearCommError(m_hCom,&dwErrorFlags,&ComStat);

    length=min(dwLength, ComStat.cbInQue);

    ReadFile(m_hCom,buf,length,&length,&m_osRead);

    return length;

     

    }

     

    // 将指定数量的字符从串行口输出

    DWORD CTerminalDoc::WriteComm(char *buf,DWORD dwLength)

    {

    BOOL fState;

    DWORD length=dwLength;

    COMSTAT ComStat;

    DWORD dwErrorFlags;

    ClearCommError(m_hCom,&dwErrorFlags,&ComStat);

    fState=WriteFile(m_hCom,buf,length,&length,&m_osWrite);

    if(!fState){

    if(GetLastError()==ERROR_IO_PENDING)

    {

    GetOverlappedResult(m_hCom,&m_osWrite,&length,TRUE);// 等待

    }

    else

    length=0;

    }

    return length;

    }

     

    // 工作者线程,负责监视串行口

    UINT CommProc(LPVOID pParam)

    {

    OVERLAPPED os;

    DWORD dwMask, dwTrans;

    COMSTAT ComStat;

    DWORD dwErrorFlags;

    CTerminalDoc *pDoc=(CTerminalDoc*)pParam;

     

    memset(&os, 0, sizeof(OVERLAPPED));

    os.hEvent=CreateEvent(NULL, TRUE, FALSE, NULL);

    if(os.hEvent==NULL)

    {

    AfxMessageBox("Can't create event object!");

    return (UINT)-1;

    }

    while(pDoc->m_bConnected)

    {

    ClearCommError(pDoc->m_hCom,&dwErrorFlags,&ComStat);

    if(ComStat.cbInQue)

    {

    // 无限等待WM_COMMNOTIFY消息被处理完

    WaitForSingleObject(pDoc->m_hPostMsgEvent, INFINITE);

    ResetEvent(pDoc->m_hPostMsgEvent);

    // 通知视图

    PostMessage(pDoc->m_hTermWnd, WM_COMMNOTIFY, EV_RXCHAR, 0);

    continue;

    }

    dwMask=0;

    if(!WaitCommEvent(pDoc->m_hCom, &dwMask, &os)) // 重叠操作

    {

    if(GetLastError()==ERROR_IO_PENDING)

    // 无限等待重叠操作结果

    GetOverlappedResult(pDoc->m_hCom, &os, &dwTrans, TRUE);

    else

    {

    CloseHandle(os.hEvent);

    return (UINT)-1;

    }

    }

    }

    CloseHandle(os.hEvent);

    return 0;

    }

     

    BOOL CTerminalDoc::CanCloseFrame(CFrameWnd* pFrame)

    {

    // TODO: Add your specialized code here and/or call the base class

     

    SetModifiedFlag(FALSE); // 将文档的修改标志设置成未修改

    return CDocument::CanCloseFrame(pFrame);

    }

     

     

     

    毫无疑问,CTerminalDoc类是研究重点。该类负责Terminal的通信任务,主要包括设置通信参数、打开和关闭串行口、建立和终止辅助工作线程、用辅助线程监视串行口等等。

    在CTerminalDoc类的头文件中,有些变量是用volatile关键字声明的。当两个线程都要用到某一个变量且该变量的值会被改变时,应该用volatile声明,该关键字的作用是防止优化编译器把变量从内存装入CPU寄存器中。如果变量被装入寄存器,那么两个线程有可能一个使用内存中的变量,一个使用寄存器中的变量,这会造成程序的错误执行。

    成员m_bConnected用来表明当前是否存在一个通信连接。m_hTermWnd用来保存是视图的窗口句柄。m_hPostMsgEvent事件对象用于WM_COMMNOTIFY消息的允许和禁止。m_pThread用来指向AfxBeginThread创建的CWinThread对象,以便对线程进行控制。OVERLAPPED结构m_osRead和m_osWrite用于串行口的重叠读/写,程序应该为它们的hEvent成员创建事件句柄。

    CTerminalDoc类的构造函数主要完成一些通信参数的初始化工作。OnNewDocument成员函数创建了三个事件对象,CTerminalDoc的析构函数关闭串行口并删除事件对象句柄。

    OnFileSettings是File->Settings...的命令处理函数,该函数弹出一个CSetupDlg对话框来设置通信参数。实际的设置工作由ConfigConnection函数完成,在OpenConnection和OnFileSettings中都会调用该函数。

    OpenConnection负责打开串行口并建立辅助工作线程,当用户选择了File->Connect命令时,消息处理函数OnFileConnect将调用该函数。该函数调用CreateFile以重叠方式打开指定的串行口并把返回的句柄保存在m_hCom成员中。接着,函数对m_hCom通信设备进行各种设置。需要注意的是对超时的设定,将读间隔超时设置为MAXDWORD并使其它读超时参数为0会导致ReadFile函数立即完成操作并返回,而不管读入了多少字符。设置超时就规定了GetOverlappedResult函数的等待时间,因此有必要将写超时设置成适当的值,这样如果不能完成写串口的任务,GetOverlappedResult函数会在超过规定超时后结束等待并报告实际传输的字符数。

    如果对m_hCom设置成功,则函数会建立一个辅助线程并暂时将其挂起。在最后,调用CWinThread:: ResumeThread使线程开始运行。

    OpenConnection调用成功后,线程函数CommProc就开始工作。该函数的主体是一个while循环,在该循环内,混合了两种方法监视串行口输入的方法。先是调用ClearCommError函数查询输入缓冲区中是否有字符,如果有,就向视图发送WM_COMMNOTIFY消息通知其接收字符。如果没有,则调用WaitCommEvent函数监视EV_RXCHAR通信事件,该函数执行重叠操作,紧接着调用的GetOverlappedResult函数无限等待通信事件,如果EV_RXCHAR事件发生(串口收到字符并放入输入缓冲区中),那么函数就结束等待。

    上述两种方法的混合使用兼顾了线程的效率和可靠性。如果只用ClearCommError函数,则辅助线程将不断耗费CPU时间来查询,效率较低。如果只用WaitCommEvent来监视,那么由于该函数对输入缓冲区中已有的字符不会产生EV_RXCHAR事件,因此在通信速率较高时,会造成数据的延误和丢失。

    注意到辅助线程用m_PostMsgEvent


       收藏   分享  
    顶(0)
      




    ----------------------------------------------
    事业是国家的,荣誉是单位的,成绩是领导的,工资是老婆的,财产是孩子的,错误是自己的。

    点击查看用户来源及管理<br>发贴IP:*.*.*.* 2005/5/20 17:23:00
     
     GoogleAdSense
      
      
      等级:大一新生
      文章:1
      积分:50
      门派:无门无派
      院校:未填写
      注册:2007-01-01
    给Google AdSense发送一个短消息 把Google AdSense加入好友 查看Google AdSense的个人资料 搜索Google AdSense在『 C/C++编程思想 』的所有贴子 访问Google AdSense的主页 引用回复这个贴子 回复这个贴子 查看Google AdSense的博客广告
    2024/5/6 23:38:29

    本主题贴数1,分页: [1]

    管理选项修改tag | 锁定 | 解锁 | 提升 | 删除 | 移动 | 固顶 | 总固顶 | 奖励 | 惩罚 | 发布公告
    W3C Contributing Supporter! W 3 C h i n a ( since 2003 ) 旗 下 站 点
    苏ICP备05006046号《全国人大常委会关于维护互联网安全的决定》《计算机信息网络国际联网安全保护管理办法》
    6,625.000ms