接上篇:
我采用的是C#开发的一个windows应用程序(pipe_server_csharp)作为服务器端,而MFC开发的应用程序(NamedPipeClient_vc)作为客户端。客户端和服务器端要进行频繁的大量的通信,常见的是文本信息和曲线数据,例如,一共有10条曲线,每条曲线有1000000条double数据。
服务器端:
服务器端是用在VS2005中用C#开发的一个名为pipe_server_csharp的应用程序,只有一个名为frmServer的主界面。
由于管道的相关API函数都是属于kernel32.dll函数,C#中不能直接调用,所以必须将所要用到的API函数全部封装在一个类NamedPipeNative中。 至于如何调用这些API函数,有兴趣的朋友可以上网搜索,或者看我的另一篇文章《C#中怎么调用命名管道的WinAPI》。
NamedPipeNative类中几个主要函数如下(大家可以对比这些重写的API函数和原来的函数有什么变化):
[DllImport("kernel32.dll", SetLastError=true)]
public static extern IntPtr CreateNamedPipe
(
String lpName, // pipe name
uint dwOpenMode, // pipe open mode
uint dwPipeMode, // pipe-specific modes
uint nMaxInstances, // maximum number of instances
uint nOutBufferSize, // output buffer size
uint nInBufferSize, // input buffer size
uint nDefaultTimeOut, // time-out interval
IntPtr pipeSecurityDescriptor // SD
);
[DllImport("kernel32.dll", SetLastError=true)]
public static extern bool ConnectNamedPipe
(
IntPtr hHandle, // handle to named pipe
Overlapped lpOverlapped // overlapped structure
);
[DllImport("kernel32.dll", SetLastError=true)]
public static extern IntPtr CreateFile
(
String lpFileName, // file name
uint dwDesiredAccess, // access mode
uint dwShareMode, // share mode
SecurityAttributes attr, // SD
uint dwCreationDisposition, // how to create
uint dwFlagsAndAttributes, // file attributes
uint hTemplateFile); // handle to template file
[DllImport("kernel32.dll", SetLastError=true)]
public static extern bool ReadFile
(
IntPtr hHandle, // handle to file
byte[] lpBuffer,// data buffer字节流
uint nNumberOfBytesToRead,// number of bytes to read
byte[] lpNumberOfBytesRead,// number of bytes read
uint lpOverlapped// overlapped buffer
);
[DllImport("kernel32.dll", SetLastError=true)]
public static extern bool WriteFile
(
IntPtr hHandle, // handle to file
byte[] lpBuffer,// data buffer字节流
uint nNumberOfBytesToWrite, // number of bytes to write
byte[] lpNumberOfBytesWritten, // number of bytes written
uint lpOverlapped // overlapped buffer
);
还有其他一些常量:
public const uint PIPE_ACCESS_DUPLEX = 0x00000003;
public const uint PIPE_ACCESS_OUTBOUND = 0x00000002;
public const uint PIPE_TYPE_BYTE = 0x00000000;
public const uint PIPE_TYPE_MESSAGE = 0x00000004;
在此不一一列举了。
封装之后,在C#中就可以直接调用这些函数和常量了。
另外,创建了一个类CMyPipe,封装了服务器管道的属性和方法,如创建管道,读写数据等:
namespace pipe_server_csharp
{
public class CMyPipe
{
private string m_PipeName;//管道名全称
public string PipeName
{
get { return m_PipeName; }
set { m_PipeName = value; }
}
private IntPtr m_HPipe;//管道句柄
public IntPtr HPipe
{
get { return m_HPipe; }
set { m_HPipe = value; }
}
public CMyPipe()//无参构造函数
{
m_HPipe = (IntPtr)NamedPipeNative.INVALID_HANDLE_VALUE;
m_PipeName = "\\\\.\\pipe\\robinTest";
}
public CMyPipe(string pipeName)//有参构造函数
{
m_HPipe = (IntPtr)NamedPipeNative.INVALID_HANDLE_VALUE;
m_PipeName = pipeName;
}
~CMyPipe()//析构函数
{
Dispose();
}
public void Dispose()
{
lock (this)
{
if (m_HPipe != (IntPtr)NamedPipeNative.INVALID_HANDLE_VALUE)
{
NamedPipeNative.CloseHandle(m_HPipe);
m_HPipe = (IntPtr)NamedPipeNative.INVALID_HANDLE_VALUE;
}
}
}
public void CreatePipe()//创建管道
{
m_HPipe = NamedPipeNative.CreateNamedPipe(m_PipeName,
NamedPipeNative.PIPE_ACCESS_DUPLEX, // 数据双工通信
NamedPipeNative. PIPE_TYPE_MESSAGE | NamedPipeNative.PIPE_WAIT, 100, // 最大实例个数
128, // 流出数据缓冲大小
128, // 流入数据缓冲大小
150, // 超时,毫秒
IntPtr.Zero // 安全信息
);
if (m_HPipe.ToInt32() == NamedPipeNative.INVALID_HANDLE_VALUE)
{
frmServer.ActivityRef.AppendText("创建命名管道失败" );
frmServer.ActivityRef.AppendText(Environment.NewLine);
return;
}
frmServer.ActivityRef.AppendText("创建命名管道完毕" );
frmServer.ActivityRef.AppendText(Environment.NewLine);
}
public void ReadCurveData()//读取曲线数据
{
int nCurvesToRead = 0;
nCurvesToRead = ReadInt(HPipe);//先读取需要读取的曲线条数,如
frmServer.CurveDataList.Clear();
for (int j = 1; j <= nCurvesToRead; j++)
{
string curveName = ReadString(HPipe);//读取该曲线名称
int nCount = 0;
nCount = ReadInt(HPipe);//读取该曲线的数据总数(数组大小)
double[] readData = new double[nCount];
for (int i = 0; i < nCount; i++)
{
readData[i] = ReadDouble(HPipe);//顺序读取曲线的数据
}
CCurve newCurve = new CCurve();
newCurve.CurveName = curveName;
newCurve.CurveData = readData;
frmServer.CurveDataList.Add(newCurve);
}
}
public void ReadTextInfo()//读取文本信息
{
string textInfo = ReadString(HPipe);//读取该曲线名称
frmServer.ActivityRef.AppendText(textInfo + Environment.NewLine);
frmServer.ActivityRef.AppendText(Environment.NewLine);
}
public void ReadData()
{
//read data
int flag = -1;
flag = ReadInt(HPipe);//获取当前要读取的数据的信息
if (flag == 0)//flag==0表示读取曲线数据
{
ReadCurveData();
}
else if (flag == 1)//flag==1表示读取文本信息
{
ReadTextInfo();
}
}
//写字符串,由于字符串的大小不同,所以先将字符串的大小写入,然后写字符串内容,对应的,
//在c++端,读字符串时先读取字符数组大小,从而给字符数组开辟相应的空间,然后读取字符串内容。
public bool WriteString(IntPtr HPipe, string str)
{
byte[] Val = System.Text.Encoding.UTF8.GetBytes(str);
byte[] dwBytesWrite = new byte[4];
int len = Val.Length;
byte[] lenBytes = System.BitConverter.GetBytes(len);
if (NamedPipeNative.WriteFile(HPipe, lenBytes, 4, dwBytesWrite, 0))
return (NamedPipeNative.WriteFile(HPipe, Val, (uint)len, dwBytesWrite, 0));
else
return false;
}
public string ReadString(IntPtr HPipe)
{
string Val = "";
byte[] bytes = ReadBytes(HPipe);
if (bytes != null)
{
Val = System.Text.Encoding.UTF8.GetString(bytes);
}
return Val;
}
public byte[] ReadBytes(IntPtr HPipe)
{
//传字节流
byte[] szMsg = null;
byte[] dwBytesRead = new byte[4];
byte[] lenBytes = new byte[4];
int len;
if (NamedPipeNative.ReadFile(HPipe, lenBytes, 4, dwBytesRead, 0))//先读大小
{
len = System.BitConverter.ToInt32(lenBytes, 0);
szMsg = new byte[len];
if (!NamedPipeNative.ReadFile(HPipe, szMsg, (uint)len, dwBytesRead, 0))
{
//frmServer.ActivityRef.AppendText("读取数据失败");
return null;
}
}
return szMsg;
}
public float ReadFloat(IntPtr HPipe)
{
float Val = 0;
byte[] bytes = new byte[4]; //单精度需4个字节存储
byte[] dwBytesRead = new byte[4];
if (!NamedPipeNative.ReadFile(HPipe, bytes, 4, dwBytesRead, 0))
{
//frmServer.ActivityRef.AppendText("读取数据失败");
return 0;
}
Val = System.BitConverter.ToSingle(bytes, 0);
return Val;
}
public double ReadDouble(IntPtr HPipe)
{
double Val = 0;
byte[] bytes = new byte[8]; //双精度需8个字节存储
byte[] dwBytesRead = new byte[4];
if (!NamedPipeNative.ReadFile(HPipe, bytes, 8, dwBytesRead, 0))
{
//frmServer.ActivityRef.AppendText("读取数据失败");
return 0;
}
Val = System.BitConverter.ToDouble(bytes, 0);
return Val;
}
public int ReadInt(IntPtr HPipe)
{
int Val = 0;
byte[] bytes = new byte[4]; //整型需4个字节存储
byte[] dwBytesRead = new byte[4];
if (!NamedPipeNative.ReadFile(HPipe, bytes, 4, dwBytesRead, 0))
{
//frmServer.ActivityRef.AppendText("读取数据失败");
return 0;
}
Val = System.BitConverter.ToInt32(bytes, 0);
return Val;
}
}
}
主程序,读取和显示通信信息:
public partial class frmServer : Form
{
public static System.Windows.Forms.TextBox ActivityRef;//显示信息
public static ArrayList CurveDataList = new ArrayList();//存储数据
public static string PipeName = "robinTest";
public static string ServeName = ".";
public static string FullPipeName = "\\\\"+ServeName+"\\pipe\\"+PipeName;//管道全名
public static CMyPipe ThePipe;//管道实例
private Thread hThread;//线程
public frmServer()
{
InitializeComponent();
frmServer.ActivityRef = this.txtMessage;
Control.CheckForIllegalCrossThreadCalls = false;//不捕获线程错误调用,这样线程就可以别的线程中创建的东西,这样做也会破坏了线程的安全
ThePipe = new CMyPipe(FullPipeName);
ThePipe.CreatePipe();//创建管道
hThread = new Thread(new ThreadStart(PipeProcess));
hThread.IsBackground = true;
hThread.Name = "Main Pipe Thread";
hThread.Start();// 启动线程.监听管道,进行通信
Thread.Sleep(1000);
}
public void PipeProcess()
{
bool ret = false;
try
{
while (true)
{
ret = NamedPipeNative.ConnectNamedPipe(ThePipe.HPipe, null);//连接到管道
if (!ret)
{
if (NamedPipeNative.GetLastError() == NamedPipeNative.ERROR_PIPE_CONNECTED) // 客户还在连接中
{
frmServer.ActivityRef.AppendText("连接正常,客户还在连接中……" + Environment.NewLine);//此处会出错,因为线程调用的问题
ret = true;
}
if (NamedPipeNative.GetLastError() == NamedPipeNative.ERROR_NO_DATA) // 客户已经关闭
{
frmServer.ActivityRef.AppendText("客户已经关闭,等待客户…………" + Environment.NewLine);
NamedPipeNative.DisconnectNamedPipe(ThePipe.HPipe);
continue;
}
}
else
{
frmServer.ActivityRef.AppendText("有客户接入" + Environment.NewLine);
}
ThePipe.ReadData();//读取数据
}
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
finally
{
frmServer.ActivityRef.AppendText("线程退出!");
}
}
}
客户机端:
客户端是在VS6.0中用MFC开发的一个单界面应用程序,名为NamedPipeClient_vc。在vc6.0中可以直接调用API函数,不需要封装。
当然,按照惯例,还是将客户端的命名管道相关的属性和方法封装成类CMyPipe,如下:
CString PipeName = "robinTest";
CString ServerName = ".";
CString FullPipeName="\\\\"+ServerName+"\\pipe\\"+PipeName;
CMyPipe::CMyPipe()
{
m_pThread=NULL;
}
CMyPipe::~CMyPipe()
{
if(m_pThread)
{
if(TerminateThread(m_pThread->m_hThread,0))
{
if(m_pThread)
delete m_pThread;
m_pThread = NULL;
}
}
}
//客户端和服务器端建立连接
void CMyPipe::ClientCreateFile()
{
BOOL ret = ::WaitNamedPipe(FullPipeName, 5000);
if (!ret)
{
//ClientMsg="管道忙或者没有启动……\n";
return;
}
m_hPipe = ::CreateFile(FullPipeName,
GENERIC_READ|GENERIC_WRITE,
FILE_SHARE_READ|FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);
if (m_hPipe == INVALID_HANDLE_VALUE)
{
char szErr[256] = "";
DWORD dwErr = GetLastError();
sprintf(szErr, "%l", dwErr);
//ClientMsg=szErr;
return;
}
//ClientMsg="打开管道了\n";
}
//写字符串
void CMyPipe::WriteString(char *szMsg)
{
DWORD dwSize = strlen(szMsg)+1, dwBytesWritten = 0;
BOOL ret =WriteFile(m_hPipe, &dwSize, 4, &dwBytesWritten, NULL);
if (ret)
{
BOOL ret2=WriteFile(m_hPipe, szMsg, dwSize, &dwBytesWritten, NULL);
}
}
//写单精度
void CMyPipe::WriteFloat(float szMsg)
{
DWORD dwSize = 4, dwBytesWritten = 0;
BOOL ret=WriteFile(m_hPipe, &szMsg, dwSize, &dwBytesWritten, NULL);
}
//写双精度
void CMyPipe::WriteDouble(double szMsg)
{
DWORD dwSize = 8, dwBytesWritten = 0;
BOOL ret=WriteFile(m_hPipe, &szMsg, dwSize, &dwBytesWritten, NULL);
}
//写整型
void CMyPipe::WriteInt(int szMsg)
{
DWORD dwSize = 4, dwBytesWritten = 0;
BOOL ret=WriteFile(m_hPipe, &szMsg, dwSize, &dwBytesWritten, NULL);
}
void CMyPipe::ClosePipe()
{
CloseHandle(m_hPipe);
}
//读字符串
char * CMyPipe::ReadString()
{
char *readStr;
DWORD drSize=0, dwBytesRead = 0;
BOOL ret =ReadFile(m_hPipe, &drSize, 4, &dwBytesRead, NULL);
if (ret)
{
readStr=new char[drSize];
BOOL ret2=ReadFile(m_hPipe, readStr, drSize, &dwBytesRead, NULL);
}
return readStr;
}
int CMyPipe::ReadInt()
{
int intRead=0;
DWORD drSize=4, dwBytesRead = 0;
BOOL ret =ReadFile(m_hPipe, &intRead, drSize, &dwBytesRead, NULL);
return intRead;
}
float CMyPipe::ReadFloat()
{
float floatRead=0;
DWORD drSize=4, dwBytesRead = 0;
BOOL ret =ReadFile(m_hPipe, &floatRead, drSize, &dwBytesRead, NULL);
return floatRead;
}
double CMyPipe::ReadDouble()
{
double floatRead=0;
DWORD drSize=8, dwBytesRead = 0;
BOOL ret =ReadFile(m_hPipe, &floatRead, drSize, &dwBytesRead, NULL);
return floatRead;
}
而主程序NamedPipeClient_vcDlg.cpp如下:
CMyPipe thePipe;
//连接服务器管道
void CNamedPipeClient_vcDlg::OnOpen()
{
// TODO: Add your control notification handler code here
thePipe.ClientCreateFile();
}
//发送数据
void CNamedPipeClient_vcDlg::OnWrite()
{
// TODO: Add your control notification handler code here
WriteData();
}
//关闭管道
void CNamedPipeClient_vcDlg::OnClose()
{
// TODO: Add your control notification handler code here
thePipe.ClosePipe();
}
//写曲线数据(注意写入的顺序,读的时候也要按照这个顺序)
void CNamedPipeClient_vcDlg::WriteCurveData()
{
int nCurvesToWrite=10;
thePipe.WriteInt(nCurvesToWrite);//先写入要传递的曲线总条数
for (int j = 1; j <= nCurvesToWrite; j++)
{
char *curveName= "curve";//
thePipe.WriteString(curveName);//写该曲线名称
int nCount = 10000;//该曲线的数据总数(数组大小)
thePipe.WriteInt(nCount);
double *writeData=new double[nCount];
for (int i = 0; i < nCount; i++)
{
writeData[i]=i;//给每个曲线数据赋初值
thePipe.WriteDouble(writeData[i]);//顺序写曲线的数据
}
}
}
//写文本信息
void CNamedPipeClient_vcDlg::WriteTextInfo(char *textInfo)
{
thePipe.WriteString(textInfo);
}
void CNamedPipeClient_vcDlg::WriteData()
{
int flag=1;//传递的信息的标识,0表示曲线数据,1表示文本信息
if (flag==0)
{
thePipe.WriteInt(flag);
WriteCurveData();
}
else if (flag==1)
{
thePipe.WriteInt(flag);
char * charWrite="hedafasfddsafdsafdsafdsafdsafsafdsafdsafdsafsaaaa";
WriteTextInfo(charWrite);
}
}