邮槽

邮槽 （MailSlot）是Windows操作系统提供的一种单向进程间通信机制，可用于单机或者网络上的多机分布式环境。

对于相对简短的低频率信息发送，使用邮槽通常比命名管道或Unix域套接字更简单。如低频率的状态改变消息、作为对等点发现协议（peer-discovery protocol）的一部分。邮槽机制允许短报文广播给域上的计算机中所有监听的进程。

## 特性

邮槽是一种服务器-客户接口。服务器创建邮槽，客户可以向命名的邮槽写入内容。只有服务器可以读取邮槽，因此邮槽是一种单向进程间通信机制。邮槽不提供报文已收到的确认，因此是不可靠通信。

邮槽基于RPC协议，可以在同一个网络域上跨计算机使用。

## 应用

Windows信使服务是邮槽的最知名的应用。信使服务是一个邮槽服务器，等待报文到达后，就弹窗显示在屏幕上。

邮槽的应用举例:

- MAILSLOT\Messngr - 微软Windows信使服务协议
- MAILSLOT\Browse - 微软网络邻居共享资源浏览服务
- MAILSLOT\Alerter
- MAILSLOT\53cb31a0\UnimodemNotifyTSP
- MAILSLOT\HydraLsServer - Microsoft Terminal Services Licensing
- MAILSLOT\CheyenneDS - CA BrightStor Discovery Service

## 实现

### 邮槽命名

邮槽的名字，从形式上看类似于文件名。格式为`\\ComputerName\mailslot\[path]name` 本地邮槽名字中的ComputerName使用`.`来代替，即`\\.\MailSlot\路径\文件名`。如果向当前发送计算机所在工作组或域群发，可以使用`*`作为ComputerName。如果向指定工作组或域群发，应使用工作组或域名作为ComputerName

本机的Windows的信使服务使用的邮槽名字是`\\.\MailSlot\ messngr`

### 邮槽报文内容

邮槽报文内容包含：接收日期、发送人、接收人、具体内容。发送人、接收人、具体内容三项内容之间使用字节值`0`间隔。

### 创建邮槽

使用`CreateMailslot`

### 写入报文到邮槽

类似于写入普通文件，使用`CreateFile`打开邮槽，使用`WriteFile`将内容写入。

也可以使用`NetMessageBufferSend`直接发送。

### 读取邮槽报文

使用`GetMailSlotInfo`来判断邮槽内是否有内容。发现有内容的时候，可以使用`ReadFile`读取。如果`ReadFile`

在使用MAILSLOT_WAIT_FOREVER标志的邮槽上等待消息到来，这时邮槽突然中止运行，那么这个应用会被永远“挂起”直至重启Windows系统。为此，读邮槽的进程可以使用一个单独线程执行读挂起操作；主线程要结束进程时给一个全局标志位置位，并给邮槽写入一条消息以唤醒读邮槽线程。

`SetMailslotInfo`设置读取邮槽的超时值

## 例子

```cpp
//邮槽服务器，负责创建邮槽，读取邮槽
#include <windows.h>
#include <stdio.h>
int main()
{
 HANDLE Mailslot;
 char buffer[256];
 DWORD NumberOfBytesRead;
 //Create the mailslot
 Mailslot = CreateMailslot("////.//Mailslot//Myslot",  //指定邮槽的名字
  0,                  //可写入邮槽的消息的最大字节长度；为0表示接收任意长度消息
  MAILSLOT_WAIT_FOREVER, //等待或不等待，单位毫秒，MAILSLOT_WAIT_FOREVER无限期等待，0立即返回 
  NULL);               //访问控制权限，一般为NULL
 if (INVALID_HANDLE_VALUE == Mailslot)
 {
  printf("Failed to create a mailslot %d/n", GetLastError());
  return -1;
 }
 //Read data from the mailslot forever!
 while (0 != ReadFile(Mailslot, buffer, 256, &NumberOfBytesRead, NULL))
 {
  printf("%.*s/n",NumberOfBytesRead,buffer);
 }
 CloseHandle(Mailslot);
 return 0;
}
```

```cpp
//邮槽客户端，用于发送数据到邮槽服务器
#include <windows.h>
#include <stdio.h>
int main(int argc, char *argv[])
{
    HANDLE Mailslot;
    DWORD BytesWritten;
    CHAR ServerName[256];
    //从命令行接受要发送数据到的服务器名
    if (argc < 2)
    {
        printf("Usage: client <server name>/n");
        return -1;
    }
    sprintf(ServerName, "////%s//Mailslot//Myslot",argv[1]);
    Mailslot = CreateFile(ServerName, //邮槽名字
        GENERIC_WRITE,      //必须为GENERIC_WRITE,因为客户端只能向邮槽写入数据
        FILE_SHARE_READ,   /必须为FILE_SHARE_READ,因为邮槽服务器需要做打开和读操作
        NULL, 
        OPEN_EXISTING,     //邮槽在本地且还没有创建邮槽则当前调用失败；邮槽在远程，该参数无意义
        FILE_ATTRIBUTE_NORMAL,
        NULL);
    if (INVALID_HANDLE_VALUE == Mailslot)
    {
        printf("WriteFile failed with error %d/n",GetLastError());
        return -1;
    }
    if(0 == WriteFile(Mailslot, "This is a test", 14, &BytesWritten,NULL))
    {
        printf("WriteFile failed with error %d/n", GetLastError());
        return -1;
    }
    printf("Wrote %d byteds/n", BytesWritten);
    CloseHandle(Mailslot);
    return 0;
}
```

## 外部链接

- Mailslots (MSDN Documentation)
- Using Mailslots for Interprocess Communication
- Using a Mailslot to read/write data over a network