由于项目要求,迫切需要掌握多线程通信的技术,参考了网上的一些好文章,实现了多线程命名管道。我尽量把注释写全一些,这样读者看起来要轻松一些。
一、服务器端的实现。
服务器端完成以下几项工作:
1、创建多线程,每一个线程处理一个客户机的请求,可以满足多个客户端同时连接到服务器。
2、每一个线程负责连接一个客户机,并创建一个命名管道。
3、每一个命名管道创建一个监听程序,以服务客户机。
4、当有客户机退出连接时,服务器应该重新建立一个服务线程,以维持总的活跃管道数量不变。
服务器端代码如下,有详尽的注释,比较容易理解。
#include "stdafx.h"
#include <Windows.h>
#include <stdio.h>
#include <conio.h>
#define BUFFERSIZE 256 //缓冲区大小
#define MAX_NUM_PIPES 5 //线程个数,即允许同时连接的客户机个数
DWORD WINAPI PipeInstanceProc(LPVOID lpParameter); //多线程回调函数
DWORD WINAPI ListenPipeProc(LPVOID lpParameter); //监听管道回调函数
int _tmain(int argc, _TCHAR* argv[])
{
HANDLE hThreadHandle;//线程句柄
DWORD threadId;//线程号
//创建五个线程,分别处理一个客户机连接
for (int i=0;i<MAX_NUM_PIPES;i++)
{
hThreadHandle=CreateThread(
NULL,//WinNT后永久设为NULL
0,//线程初始化堆栈大小,一般设为0
PipeInstanceProc,//线程回调函数的指针,即函数名
NULL,//传输给回调函数的参数,通过它实现对回调函数的控制
0,//线程创建完毕后的状态,0表示创建后执行线程,CREATE_SUSPENDED表示暂时挂起,等待叫醒
&threadId//线程ID值的地址
);
if(hThreadHandle==NULL)
{
printf("创建线程%d失败!%d\n",i,GetLastError());
return 0;
}
printf("创建命名管道线程%d成功!",i);
}
//等待线程结束
WaitForSingleObject(hThreadHandle,INFINITE);
CloseHandle(hThreadHandle);
hThreadHandle=NULL;
return 0;
}
/*
* 函数名称: PipeInstanceProc
* 函数参数: (in)LPVOID: 附加信息
* 函数作用: 处理一个命名管道实例的线程
* 函数返回值:(DWORD)若顺利完成,则返回1;若获取域名失败,则返回0
*/
DWORD WINAPI PipeInstanceProc(LPVOID lpParameter)
{
HANDLE hPipeHandle;//命名管道的句柄
HANDLE hThreadListen;//管道监听句柄
/*
* 函数名称: CreateNamedPipe
* 函数参数: (in)LPCTSTR 命名管道名字,UNC标准( \\IP地址\Pipe\唯一标识文件路径 )
* (in)DWORD 命名管道模式,单双向,读写控制,安全模式( 均被宏定义好,可位或操作 )
* (in)DWORD 命名管道读,写,等待模式( 均被宏定义好,可位或操作 )
* (in)DWORD 命名管道最多可创建的实例句柄个数
* (in)DWORD 命名管道输出缓冲区大小
* (in)DWORD 命名管道输入缓冲区大小
* (in)DWORD 命名管道默认超时时间
* (in)LPSECURITY_ATTRIBUTES 命名管道安全描述符,若为NULL,则句柄不可继承的默认安全。
* 函数作用: 创建一个命名管道实例
* 函数返回值:(HANDLE)成功则返回命名管道实例的句柄,失败则返回值INVALID_HANDLE_VALUE
*/
hPipeHandle = CreateNamedPipe(
_T("\\\\.\\pipe\\TrackerService"),
PIPE_ACCESS_DUPLEX,
PIPE_TYPE_BYTE | PIPE_READMODE_BYTE |PIPE_WAIT,
PIPE_UNLIMITED_INSTANCES,
BUFFERSIZE,
BUFFERSIZE,
0,
NULL
);
if (hPipeHandle == INVALID_HANDLE_VALUE)
{
printf("创建命名管道失败!%d\n",GetLastError());
return 0;
}
printf("创建命名管道成功!\n");
HANDLE h;//线程句柄,用于一个客户机退出连接之后,服务器重新建立一个服务线程
//处理客户端信息
while (1)
{
/*
* 函数名称: ConnectNamedPipe
* 函数参数: (in)HANDLE: 命名管道实例句柄
* (in)LPOVERLAPPED 是否锁定式命名管道
* 函数作用: 建立命名管道连接并监听
* 函数返回值:(bool)若顺利建立,则返回true;若顺利失败,则返回false
*/
if (ConnectNamedPipe(hPipeHandle,NULL))
{
printf("有客户端连接命名管道!\n");
//Create listen pipe thread
hThreadListen = CreateThread(
NULL,
0,
ListenPipeProc,
hPipeHandle,
0,
NULL
);
if (hThreadListen==NULL)
{
printf("创建监听线程失败!\n");
return 0;
}
printf("创建监听线程成功!\n");
WaitForSingleObject(hThreadListen,INFINITE);
//Close handle
CloseHandle(hThreadListen);
//set handle null
hThreadListen = NULL;
}
else
{
printf("有客户端断开连接!%d\n",GetLastError());
DWORD threadId;
//服务器重新建立一个服务线程
h=CreateThread(
NULL,//WinNT后永久设为NULL
0,//线程初始化堆栈大小,一般设为0
PipeInstanceProc,//线程回调函数的指针,即函数名
NULL,//传输给回调函数的参数,通过它实现对回调函数的控制
0,//线程创建完毕后的状态,0表示创建后执行线程,CREATE_SUSPENDED表示暂时挂起,等待叫醒
&threadId//线程ID值的地址
);
if(h==NULL)
{
printf("创建线程失败!%d\n",GetLastError());
return 0;
}
printf("创建命名管道线程成功!");
WaitForSingleObject(h,INFINITE);
CloseHandle(h);
h=NULL;
//销毁监听线程
CloseHandle(hThreadListen);
return 0;
}
}
CloseHandle(hPipeHandle);
return 1;
}
DWORD WINAPI ListenPipeProc(LPVOID lpParameter)
{
DWORD nBytesWritten;//发出的写入信息字节数
char cBuffer[BUFFERSIZE]="The MultiThread Test!...........";//字节存储数组
BOOL fSuccess = FALSE;
HANDLE hPipe=(HANDLE)lpParameter;//接收回调函数传来的参数
while (TRUE)
{
fSuccess= WriteFile(
hPipe,
cBuffer,
sizeof(cBuffer),
&nBytesWritten,
NULL);
if (!fSuccess)
{
break;
}
}
//刷新缓冲区
FlushFileBuffers(hPipe);
//断开命名管道的连接
if (DisconnectNamedPipe(hPipe) == 0)
{
printf("断开命名管道失败!%d\n",GetLastError());
CloseHandle(hPipe);
return 0;
}
CloseHandle(hPipe);
hPipe=NULL;
return 1;
}
二、客户机端的实现
客户机端完成以下工作:
1、检测服务器是否可以连接
2、打开命名管道
3、完成作业
客户机端代码如下:
#include "stdafx.h"
#include <iostream>
#include <Windows.h>
int _tmain(int argc, _TCHAR* argv[])
{
//检测服务器是否有命名管道可供连接
if (FALSE==WaitNamedPipe(_T("\\\\.\\pipe\\TrackerService"),NMPWAIT_WAIT_FOREVER))
{
std::cout<<"不能连接到服务器\n"<<std::endl;
return -1;
}
else
{
std::cout<<"连接服务器成功\n"<<std::endl;
}
//打开命名管道
HANDLE createHandle=CreateFile(_T("\\\\.\\pipe\\TrackerService"),GENERIC_READ,0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
if (createHandle==INVALID_HANDLE_VALUE)
{
std::cout<<"打开命名管道失败\n"<<std::endl;
return -1;
}
else
{
std::cout<<"打开命名管道成功\n"<<std::endl;
}
int lpNumberOfBytesRead=0;
char cBuffer[256];
//从服务器读取数据
if (ReadFile(createHandle,&cBuffer,sizeof(cBuffer),(LPDWORD)&lpNumberOfBytesRead,NULL))
{
std::cout<<cBuffer<<std::endl;
}
CloseHandle(createHandle);
return 0;
}