邮槽通信

邮槽定义

邮槽(Mailslot)也称为邮件槽,其是 Windows 提供的一种用来实现进程间通信的手段,

其提供的是基于不可靠的,并且是单向数据传输的服务。

邮件槽只支持单向数据传输,也就是服务器只能接收数据,而客户端只能发送数据,

何为服务端?何为客户端?

服务端就是创建邮槽的那一端,而客户端就是已存在的邮件槽的那一端。

还有需要提及的一点是,客户端在使用邮槽发送数据的时候只有当数据的长度 < 425 字节时,

才可以被广播给多个服务器,如果消息的长度 > 425 字节的话,那么在这种情形下,

邮槽是不支持广播通信的。

 

 

邮槽的实现

 

首先是服务端调用 CreateMailslot 函数,这个函数会将创建邮件槽的请求传递给内核的系统服务,

也就是 NtCreateMailslot 函数,而 NtCreateMailslotFile 这个函数会到达底层的邮槽驱动程序,

也就是 msfs.sys ,然后一些创建邮槽的工作就交给邮槽驱动程序来完成了,对于底层驱动,这里不作介绍,

而在高层,我们也就只需要调用 CreateMailslot 函数就可以实现创建邮槽了。

 

 

邮槽的创建

下面我们就来看看这个 CreateMailslot 函数了:

该函数利用指定的名称来创建一个邮槽,然后返回所创建的邮槽的句柄。

HANDLE    WINAPI   CreateMailslot(
        __in          LPCTSTR lpName,
        __in          DWORD nMaxMessageSize,
        __in          DWORD lReadTimeout,
        __in_opt      LPSECURITY_ATTRIBUTES lpSecurityAttributes
);

参数 lpName 指定了将要创建的邮槽的名称,该名称的格式必须为 \\.\mailslot\MailslotName

在这里需要注意的是两个斜杠后的那个 “.”,在这里使用圆点代表的是本地机器,

参数 nMaxMessageSize 用来指定可以被写入到邮槽的单一消息的最大尺寸,

为了可以发送任意大小的消息,需要将该参数设置为 

参数 lReadTimeOut 指定读取操作的超时时间间隔,以毫秒作为单位。

读取操作在超时之前可以等待一个消息被写入到邮槽中,如果将这个值设置为 ,那么若没有消息可用的话,该函数将立即返回。

如果将该值设置为 MAILSLOT_WAIT_FOREVER,则该函数会一直等待,直到有消息可用。

参数 lpSecurityAttributes 一般设置为 NULL 即可,即采用 Windows 默认的针对于邮槽的安全性。

 

 

示例:邮槽实现进程间通信

服务端实现:(简单 MFC 程序)

项目结构:

image

消息以及成员函数和成员变量的声明:

// 实现
protected:
    HICON m_hIcon;
 
    // 生成的消息映射函数
    virtual BOOL OnInitDialog();
    afx_msg void OnPaint();
    afx_msg HCURSOR OnQueryDragIcon();
    DECLARE_MESSAGE_MAP()
public:
    afx_msg void OnBnClickedBtnExit();
    afx_msg void OnBnClickedBtnRecv();
    afx_msg void OnBnClickedBtnCreate();
 
    //定义一个用来创建线程的成员函数
    HANDLE    CreateRecvThread(LPVOID lpParameter, DWORD threadFlag, LPDWORD lpThreadID);
 
    //控件变量:用来接收用户输入的数据
    CEdit m_RecvEdit;
 
    //成员变量:用来保存创建的邮件槽句柄
    HANDLE m_hMailslot;

消息映射表定义:

//用来定义邮槽发送和接收的最大数据字节数
const int        maxDataLen = 424;
 
//用来接收由客户端发送过来的数据
char *            pStrRecvData;
 
CMailSlotServerDlg::CMailSlotServerDlg(CWnd* pParent )
    : CDialogEx(CMailSlotServerDlg::IDD, pParent)
{
    m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
    
 
    m_hMailslot = NULL;
 
    //给用来接收数据的指针变量分配内存并清为 0
    pStrRecvData = new char[maxDataLen];
    memset(pStrRecvData, 0, maxDataLen);
}
 
void CMailSlotServerDlg::DoDataExchange(CDataExchange* pDX)
{
    CDialogEx::DoDataExchange(pDX);
    DDX_Control(pDX, IDC_EDIT_MAILSLOT, m_RecvEdit);
}
 
BEGIN_MESSAGE_MAP(CMailSlotServerDlg, CDialogEx)
    ON_WM_PAINT()
    ON_WM_QUERYDRAGICON()
    ON_BN_CLICKED(ID_BTN_EXIT, &CMailSlotServerDlg::OnBnClickedBtnExit)
    ON_BN_CLICKED(ID_BTN_RECV, &CMailSlotServerDlg::OnBnClickedBtnRecv)
    ON_BN_CLICKED(ID_BTN_CREATE, &CMailSlotServerDlg::OnBnClickedBtnCreate)
END_MESSAGE_MAP()

消息处理函数:

//退出按钮的消息处理例程
void CMailSlotServerDlg::OnBnClickedBtnExit()
{
    CDialogEx::OnOK();
}
 
//创建按钮的消息处理
void CMailSlotServerDlg::OnBnClickedBtnCreate()
{
    //创建名为 ZacharyMailSlot 的邮槽
    this->m_hMailslot = CreateMailslot(TEXT("\\\\.\\mailslot\\ZacharyMailSlot"), 0, 
        MAILSLOT_WAIT_FOREVER, NULL);
 
    if(INVALID_HANDLE_VALUE == this->m_hMailslot)
    {
        MessageBox(TEXT("创建邮槽失败 ..."), TEXT("提示"), MB_ICONERROR);
        return;
    }
}
 
//接收按钮的消息处理
void CMailSlotServerDlg::OnBnClickedBtnRecv()
{
    CString                cStrRecvData;
    DWORD                dwRead;
 
    //创建接收数据的线程,将邮槽句柄传递给线程
    CreateRecvThread((LPVOID)this->m_hMailslot, 0, NULL);
 
    cStrRecvData = pStrRecvData;
 
    this->m_RecvEdit.SetWindowText(cStrRecvData);
 
    UpdateData(FALSE);
}
 
//线程处理函数
DWORD WINAPI RecvThreadProc(LPVOID lpPrameter)
{
    HANDLE                hRecvMailSlot;
    DWORD                dwRead;
 
    hRecvMailSlot = (HANDLE)lpPrameter;
 
    //利用传进来的邮槽句柄接收收据,并将数据存放到 pStrRecvData 中
    if(!ReadFile(hRecvMailSlot, pStrRecvData, maxDataLen, &dwRead, NULL))
    {
        return NULL;
    }
 
    //关闭邮槽
    CloseHandle(hRecvMailSlot);
 
    return NULL;
}
 
HANDLE CMailSlotServerDlg::CreateRecvThread(LPVOID lpParameter, DWORD threadFlag, LPDWORD lpThreadID)
{
    //创建一个线程
    return CreateThread(NULL, 0, RecvThreadProc, lpParameter, threadFlag, lpThreadID);
}

客户端实现:(简单 MFC 程序)

项目结构:

image

消息以及成员函数和成员变量的声明:

// 实现
protected:
    HICON m_hIcon;
 
    // 生成的消息映射函数
    virtual BOOL OnInitDialog();
    afx_msg void OnPaint();
    afx_msg HCURSOR OnQueryDragIcon();
    DECLARE_MESSAGE_MAP()
public:
    afx_msg void OnBnClickedBtnExit();
    afx_msg void OnBnClickedBtnSend();
    CEdit m_SendEdit;

消息映射表定义:

const int maxDataLen = 424;

CMailSlotClientDlg::CMailSlotClientDlg(CWnd* pParent )
    : CDialogEx(CMailSlotClientDlg::IDD, pParent)
{
    m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}

void CMailSlotClientDlg::DoDataExchange(CDataExchange* pDX)
{
    CDialogEx::DoDataExchange(pDX);
    DDX_Control(pDX, IDC_EDIT_SEND, m_SendEdit);
}

BEGIN_MESSAGE_MAP(CMailSlotClientDlg, CDialogEx)
    ON_WM_PAINT()
    ON_WM_QUERYDRAGICON()
    ON_BN_CLICKED(ID_BTN_EXIT, &CMailSlotClientDlg::OnBnClickedBtnExit)
    ON_BN_CLICKED(ID_BTN_SEND, &CMailSlotClientDlg::OnBnClickedBtnSend)
END_MESSAGE_MAP()

消息处理函数:

//退出按钮的消息处理例程
void CMailSlotClientDlg::OnBnClickedBtnExit()
{
    CDialogEx::OnOK();
}
 
 
//发送数据的消息处理例程
void CMailSlotClientDlg::OnBnClickedBtnSend()
{
    UpdateData();
 
    if(this->m_SendEdit.GetWindowTextLength() > 0 && 
       this->m_SendEdit.GetWindowTextLength() < maxDataLen)
    {
        HANDLE                hSendMailSlot;
        CString                cStrSendData;
        DWORD                dwWrite;
        char *                pSendBuf;
 
        //打开由服务端创建的邮件槽
        hSendMailSlot = CreateFile(TEXT("\\\\.\\mailslot\\ZacharyMailSlot"), 
            GENERIC_WRITE, FILE_SHARE_READ, NULL, 
            OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
 
        if(INVALID_HANDLE_VALUE == hSendMailSlot)
        {
            MessageBox(TEXT("打开邮槽失败 ..."), TEXT("提示"), MB_ICONERROR);
            return;
        }
 
        this->m_SendEdit.GetWindowText(cStrSendData);
 
        //需要将 Unicode 字符转换为 ASCII 字符发送
        pSendBuf = new char[cStrSendData.GetLength() + 1];
        memset(pSendBuf, 0, sizeof(cStrSendData.GetLength() + 1));
        for(int i=0;i
        {
            pSendBuf[i] = cStrSendData.GetAt(i);
        }
 
        //通过邮件槽向服务端发送数据
        if(!WriteFile(hSendMailSlot, pSendBuf, cStrSendData.GetLength(), &dwWrite, NULL))
        {
            MessageBox(TEXT("写入数据失败 ..."), TEXT("提示"), MB_ICONERROR);
 
            CloseHandle(hSendMailSlot);
            return;
        }
        MessageBox(TEXT("写入数据成功 ..."), TEXT("提示"), MB_ICONINFORMATION);
    }
}

效果展示:

首先启动服务端进程并单击创建按钮:

image

然后启动客户端进程,并在客户端程序文本框中输入数据,然后单击发送按钮:

image

然后回到服务端程序中,并且单击接收按钮:

image

从上面的截图中可以看出,通过邮槽确实实现了从客户端进程向服务端进程发送数据。

当然上面的 Demo 中的服务端和客户端都是在本地机器上实现的,

如果想要实现本地进程和远程进程通信的话,

只需在客户端调用 CreateFile 打开邮槽时,将下面截图中标记的圆点置换为远程服务器的名称即可以实现了。

image

 

 

结束语

对于邮槽呢,其实还是蛮简单的,

在服务端的话,也就只需要在服务端调用 CreateMailslot 创建一个邮槽,

然后再在服务端调用 ReadFile 来等待读取数据即可以了,

而在客户端的话,也就只需要调用 CreateFile 来打开一个已经在服务端创建好的邮槽,

然后再调用 WriteFile 往这个邮槽中写入数据就可以了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值