最近做的项目需要在两个程序间进行通信,server端是c++(qt)写的程序,client是c#写的程序。
之前使用QProcess通信已经测试通过了,不过是server和client都是用qt做的模拟,实际在c#中没办法接收到消息,现在重新用管道通信来实现。
server:
#include "mainwindow.h"
#include "ui_mainwindow.h"
HANDLE MainWindow::hPipe = NULL;
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
InputThread * inputThread = new InputThread;
connect(inputThread,SIGNAL(inputedSignal(QString)),this,SLOT(onReadOutput(QString)));
inputThread->start();
OnPipeCreate();
}
void MainWindow::OnPipeCreate()
{
MainWindow::hPipe = NULL;
// 注意管道创建的参数
// 第一个参数是管道名称,本机的话前面固定(\\\\.\\Pipe\\)必不可少
// PIPE_ACCESS_DUPLEX|FILE_FLAG_WRITE_THROUGH|FILE_FLAG_OVERLAPPED双通道,缓存,
MainWindow::hPipe = CreateNamedPipe(TEXT("\\\\.\\Pipe\\TestPipe"),PIPE_ACCESS_DUPLEX|FILE_FLAG_WRITE_THROUGH|FILE_FLAG_OVERLAPPED,PIPE_TYPE_MESSAGE|PIPE_READMODE_MESSAGE|PIPE_WAIT,PIPE_UNLIMITED_INSTANCES,0,0,NMPWAIT_WAIT_FOREVER,NULL);
if(INVALID_HANDLE_VALUE == MainWindow::hPipe)
{
QMessageBox::about(this,"提醒","创建管道失败");
CloseHandle(MainWindow::hPipe);
MainWindow::hPipe = NULL;
return;
}
HANDLE hEvent;
hEvent = CreateEvent(NULL,true,false,NULL);
if(!hEvent)
{
QMessageBox::about(this,"提醒","事件创建失败");
CloseHandle(MainWindow::hPipe);
MainWindow::hPipe = NULL;
return;
}
OVERLAPPED ovlap;
ovlap.hEvent = hEvent;
if(!ConnectNamedPipe(hPipe,&ovlap))
{
if(ERROR_IO_PENDING!=GetLastError())
{
QMessageBox::about(this,"提醒","等待客户端连接失败");
CloseHandle(hPipe);
CloseHandle(hEvent);
hPipe = NULL;
return;
}
}
if(WAIT_FAILED == WaitForSingleObject(hEvent,INFINITE))
{
QMessageBox::about(this,"提醒","等待对象失败");
CloseHandle(hPipe);
CloseHandle(hEvent);
hPipe = NULL;
return;
}
CloseHandle(hEvent);
}
void MainWindow::onReadOutput(QString str)
{
this->ui->receiveText->append(str.trimmed());
}
void MainWindow::on_sendBtn_clicked()
{
// server发送给client,\n必不可少
QString str = this->ui->sendText->text()+"\n";
char * buf;
QByteArray ba = str.toUtf8();
buf = ba.data();
DWORD dwWrite;
if(!WriteFile(MainWindow::hPipe,buf,strlen(buf),&dwWrite,NULL))
{
QMessageBox::about(this,"提醒","数据写入失败");
return;
}
}
MainWindow::~MainWindow()
{
delete ui;
}
client:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.IO.Pipes;
using System.Linq;
using System.Security.Principal;
using System.Text;
using System.Threading;
using System.Windows.Forms;
namespace TestCallTo
{
public partial class FormClient : Form
{
SynchronizationContext syncContext = null;
NamedPipeClientStream pipeClient = new NamedPipeClientStream(
".",
"TestPipe",
PipeDirection.InOut,
PipeOptions.Asynchronous|PipeOptions.WriteThrough,
TokenImpersonationLevel.None
);
// 增加一个StreamWriter的定义
StreamWriter sw = null;
// 增加一个StreamReader的定义
StreamReader sr = null;
BackgroundWorker worker = new BackgroundWorker();
public FormClient()
{
InitializeComponent();
this.FormClosed += FormClient_FormClosed;
worker.WorkerReportsProgress = true;
worker.ProgressChanged += worker_ProgressChanged;
worker.DoWork += worker_DoWork;
worker.RunWorkerAsync();
syncContext = SynchronizationContext.Current;
}
void FormClient_FormClosed(object sender, FormClosedEventArgs e)
{
if (pipeClient.IsConnected)
{
pipeClient.Close();
}
}
private void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
if (e.ProgressPercentage == 100)
{
rtbRevice.Text += e.UserState as string + "\r\n";
}
else if (e.ProgressPercentage == 10)
{
lbStatus.Text = (bool)e.UserState ? "已连接" : "未连接";
}
}
void worker_DoWork(object sender, DoWorkEventArgs e)
{
if (pipeClient.IsConnected == false)
{
worker.ReportProgress(10, false);
pipeClient.Connect();
// 增加StreamWriter的初始化
sw = new StreamWriter(pipeClient);
sw.AutoFlush = true;
// 增加StreamReader的初始化
sr = new StreamReader(pipeClient);
worker.ReportProgress(10, pipeClient.IsConnected);
}
while (true)
{
if (sr!=null)
{
var receiveStr = sr.ReadLine();
// 添加到文本框
syncContext.Post(SetTextSafePost, receiveStr);
}
}
}
private void btnSend_Click(object sender, EventArgs e)
{
if (pipeClient.IsConnected == false)
{
MessageBox.Show("没有连接服务端");
return;
}
if (tbSend.Text.Length == 0)
{
return;
}
// 修改发送方式
if(sw!=null)
sw.WriteLine(tbSend.Text);
//byte[] buffer = Encoding.Unicode.GetBytes(tbSend.Text);
//pipeClient.Write(buffer, 0, buffer.Length);
//pipeClient.Flush();
//pipeClient.WaitForPipeDrain();
}
private void SetTextSafePost(object text)
{
rtbRevice.AppendText(text.ToString());
}
}
}
重点说说server:
1、初始化:
MainWindow::hPipe = CreateNamedPipe(TEXT("\\\\.\\Pipe\\TestPipe"),PIPE_ACCESS_DUPLEX|FILE_FLAG_WRITE_THROUGH|FILE_FLAG_OVERLAPPED,PIPE_TYPE_MESSAGE|PIPE_READMODE_MESSAGE|PIPE_WAIT,PIPE_UNLIMITED_INSTANCES,0,0,NMPWAIT_WAIT_FOREVER,NULL);
第一个参数
管道名称,如果是本机的话,前缀"\\\\.\\Pipe\\"都是需要的,固定这样写就可以了,后面再加上管道的名称;
第二个参数
PIPE_ACCESS_DUPLEX表示此管道是双通道的;
FILE_FLAG_WRITE_THROUGH表示通过快速缓存写入磁盘;
FILE_FLAG_OVERLAPPEDB表示对文件进行重叠操作,即不要使用默认IO,而是IO在后台操作,你程序可以继续其他操作;
第三个参数
PIPE_TYPE_MESSAGE表示管道使用消息模式,区别于字节模式;
PIPE_READMODE_MESSAGE表示管道读取模式采用消息模式,区别于字节模式;
PIPE_WAIT表示管道会处于等待状态,等待客户端来连接,区别于不等待,不等待的话没有连接到客户端马上就关闭了;
第三个参数
PIPE_UNLIMITED_INSTANCES不限制实例,指的是不限制客户端的实例数量;
第四个参数
输出缓存(size),0表示默认;
第五个参数
输入缓存(size),0表示默认;
第六个参数
NMPWAIT_WAIT_FOREVER表示永远等待下去,不设超时;
第七个参数
安全校验模式,这里传NULL即不进行安全校验;
2、等待连接
创建管道时执行CreateNamedPipe就会进入等待连接状态,如果有客户端开启则会进行连接,也就是说server端没有必要再执行一个connect操作。理论上来说,server端只要执行一个CreateNamedPipe就好了。
但是我们要监控是否有客户端连接,就需要再做一些额外工作。
如果是C#就很好办,他这个管道是经过封装的,他有一个属性叫做IsConnected来判断是否连接。在c++的代码中就要费一些周折。
如文章开头所贴的大段代码,步骤简单叙述如下:
1、初始化即CreateNamedPipe;
2、执行一个CreateEvent,这个CreateEvent是为了后面的步骤做准备;
3、执行ConnectNamedPipe,此函数会马上返回是否有客户端已连接;
4、执行WaitForSingleObject,此函数会阻塞,直到有客户端来连接,否则会阻塞于等待连接的状态;
这里面第3步其实有2种用法,这里说明一下:
1、ConnectNamedPipe第一种用法,非阻塞:
hEvent = CreateEvent(NULL,true,false,NULL);
if(!hEvent)
{
QMessageBox::about(this,"提醒","事件创建失败");
CloseHandle(MainWindow::hPipe);
MainWindow::hPipe = NULL;
return;
}
OVERLAPPED ovlap;
ovlap.hEvent = hEvent;
if(!ConnectNamedPipe(hPipe,&ovlap))
{
if(ERROR_IO_PENDING!=GetLastError())
{
QMessageBox::about(this,"提醒","等待客户端连接失败");
CloseHandle(hPipe);
CloseHandle(hEvent);
hPipe = NULL;
return;
}
}
会传入HANDLE和一个OVERLAPPED,马上返回当前是否已连接。其实我怀疑这种用法是否凑效,实测在子线程里,多次执行此方法,即使在已经连接的情况下,他仍然返回未连接。
2、ConnectNamedPipe的第二种用法,阻塞:
if(!ConnectNamedPipe(hPipe,NULL))
{
if(ERROR_IO_PENDING!=GetLastError())
{
QMessageBox::about(this,"提醒","等待客户端连接失败");
CloseHandle(hPipe);
CloseHandle(hEvent);
hPipe = NULL;
return;
}
}else
{
this->ui->statusLabel->setText("已连接");
}
第二个参数传入NULL,他会一直等待连接,这样就解决了如何判断是否已连接的问题。
因此整个程序就可以改成,CreateNamedPipe+ConnectNamedPipe,然后处于等待状态,直到连接成功返回,因为一直阻塞所以应该将他放在子线程中处理。
3、注意事项:管道名称的传入
执行CreateNamedPipe的时候,要传入管道名称,这是一个LPCWSTR类型,直接传入QString肯定是不行的。
如果是常亮hardcode字符串,直接TEXT("ABC")这样传入就可以了;
如果是变量,则需要将QString转为指定的类型:
// 传入管道名称,如果是常量直接
// TEXT("ABC")
// 如果是变量,从QString转过来:
// QString myPipeName = QString("\\\\.\\Pipe\\%1").arg(pipeName);
// const QChar* pipeText = myPipeName.unicode();
// (LPCWSTR)pipeText
ControllerBg::hPipe = CreateNamedPipe((LPCWSTR)pipeText,PIPE_ACCESS_DUPLEX|FILE_FLAG_WRITE_THROUGH|FILE_FLAG_OVERLAPPED,PIPE_TYPE_MESSAGE|PIPE_READMODE_MESSAGE|PIPE_WAIT,PIPE_UNLIMITED_INSTANCES,0,0,NMPWAIT_WAIT_FOREVER,NULL);