(1)命名管道是围绕Windows文件系统设计的一种机制,采用“‘命名管道文件系统’(Named Pipe File System,NPFS)“接口
(2)命名管道通过网络来完成进程间的通信,它屏蔽了底层的网络协议细节;命名管道不仅可以在本机上实现两个进程间的通信,还可以跨网络实现两个进程间的通信
(3)命名管道分为客户端和服务器,两者的区别在于:服务器是唯一有权创建命名管道的进程,只有他才能接受管道客户端的请求;同时,对同一个命名管道的实例来说,在某一时刻,他只能和一个客户端进行通信
(4)命名管道提供了两种基本通信模式:字节模式和消息模式;
(5)字节模式:数据以一个连续的字节流的形式在客户机和服务器之间流动
(6)消息模式:客户机和服务器通过一系列不连续的数据单位,进行数据的收发,每次管道上发出了一个消息后,他必须作为完整的信息读入
(7)在采用命名管道进行通信的两个进程之间,不需要有任何的关系,可以独立的启动这两个进程
二、从代码进行分析 1.服务器进程//创建命名管道
void CNamedPipSrvView::OnCreatePipe()
{
// TODO: Add your command handler code here
hPipe = CreateNamedPipe(_T("\\\\.\\pipe\\MyPipe"),
PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED,
0,
1,
1024,
1024,
0,
NULL);
if (INVALID_HANDLE_VALUE == hPipe)
{
MessageBox(_T("管道创建失败!"));
hPipe = NULL;
return;
}
HANDLE hEvent;
hEvent = CreateEvent(NULL,TRUE,FALSE,NULL);
OVERLAPPED ovlap;
ZeroMemory(&ovlap,sizeof(ovlap));
ovlap.hEvent = hEvent;
if (!ConnectNamedPipe(hPipe,&ovlap))
{
if (ERROR_IO_PENDING != GetLastError())
{
MessageBox(_T("等待客户连接失败!"));
CloseHandle(hEvent);
CloseHandle(hPipe);
hPipe = NULL;
return;
}
}
if (WAIT_FAILED == WaitForSingleObject(hEvent,INFINITE))
{
MessageBox(_T("等待事件对象失败!"));
CloseHandle(hEvent);
CloseHandle(hPipe);
hPipe = NULL;
return;
}
CloseHandle(hEvent);
}
//向管道写入数据
void CNamedPipSrvView::OnWrite()
{
// TODO: Add your command handler code here
char buf[] = "服务器进程写入的数据";
DWORD dwWrite;
if (!WriteFile(hPipe,buf,strlen(buf)+1,&dwWrite,NULL))
{
::MessageBox(m_hWnd,_T("写入数据失败!"),NULL,MB_OK);
return;
}
}
//从管道读取数据
void CNamedPipSrvView::OnRead()
{
// TODO: Add your command handler code here
char buf[100];
DWORD dwRead;
if (!ReadFile(hPipe,buf,100,&dwRead,NULL))
{
::MessageBox(m_hWnd,_T("读取数据失败!"),NULL,MB_OK);
return;
}
::MessageBox(m_hWnd,(LPCTSTR)buf,NULL,MB_OK);
}
//连接管道
void CNamedPipCltView::OnOpen()
{
// TODO: Add your command handler code here
if (!WaitNamedPipe(_T("\\\\.\\pipe\\MyPipe"),NMPWAIT_WAIT_FOREVER))
{
MessageBox(_T("当前没有可利用的命名管道!"));
return;
}
hPipe = CreateFile(_T("\\\\.\\pipe\\MyPipe"),
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);
if (INVALID_HANDLE_VALUE == hPipe)
{
MessageBox(_T("打开命名管道失败!"));
hPipe = NULL;
return;
}
}
//向管道写入数据
void CNamedPipCltView::OnWrite()
{
// TODO: Add your command handler code here
char buf[] = "客户端进程写入的数据";
DWORD dwWrite;
if (!WriteFile(hPipe,buf,strlen(buf)+1,&dwWrite,NULL))
{
::MessageBox(m_hWnd,_T("写入数据失败!"),NULL,MB_OK);
return;
}
}
//从管道读取数据
void CNamedPipCltView::OnRead()
{
// TODO: Add your command handler code here
char buf[100];
DWORD dwRead;
if (!ReadFile(hPipe,buf,100,&dwRead,NULL))
{
::MessageBox(m_hWnd,_T("读取数据失败!"),NULL,MB_OK);
return;
}
::MessageBox(m_hWnd,(LPCTSTR)buf,NULL,MB_OK);
}
(1)CreateNamedPipe
(2)CreateEvent
(3)GetLastError
(4)ConnectNamedPipe
(5)WaitForSingleObject
(6)CreateFile
本实例的代码请看上篇博客《AJAX实例之股票实时信息显示》。虽然Java和C#两门语言语法非常相似,但是细节上还是有很多不同之处,所以在转换的过程中,还是有几个问题:
HttpServlet专门用于响应请求的类,最开始我用的是Asp.net下的Web窗体,但Web窗体很明显更倾向于可视,而这个例子所要求的服务端不必有页面,所以使用的是一般处理程序(*.ashx),这个和HttpServlet非常类似,可以专门用于处理简单的请求,相对于Web窗体更省资源且响应速度更快。
DictionaryC#没有HashMap,不过可以使用其Dictionary达到同样的效果,Dictionary存放的同样是键值对组合,当然值可以为对象类型。
NextBoolean()Java下可以根据nextBoolean()决定是涨是降。
// 新建一个生成随机数的对象 final Random random = new Random(); //上涨或下降 if (random.nextBoolean()) { sz = 0 - sz; }C#呢?这可以灵活使用nextDouble(),nextDouble()取值为0~1,与0.5比较同样可以决定是涨还是降。
Random rdm = new Random(); //上涨浮动 if (rdm.NextDouble() > 0.5) { sz = 0 - sz; }匿名函数
好吧,前三个问题很简单,第四个就比较困难了,先看一下未解决以前的计时代码(服务端部分代码):
/// <summary> /// 初始化配置数据 /// </summary> public void InitNew() { //设定股票字典 stock = new Dictionary<string, Stock>(); stock.Add("300001", szzs); stock.Add("600000", pfyh); stock.Add("601398", gsyh); stock.Add("601857", zgsy); //设置计时器参数 timer = new System.Timers. Timer(); timer.Enabled = true; timer.Interval = 50; //执行计时器函数theout timer.Elapsed +=new System.Timers.ElapsedEventHandler(theout); } /// <summary> /// 计时器执行函数 /// </summary> /// <param name="source">事件源</param> /// <param name="e">事件参数</param> public void theout( object source,System.Timers.ElapsedEventArgs e) { //股票变动范围 Random rdm = new Random(); double rdm2 = rdm.NextDouble(); //上涨浮动 double sz = rdm2 * 30; double pf = rdm2 * 0.5; double gs = rdm2 * 0.1; double zg = rdm2 * 0.3; //下跌浮动 if (rdm2 > 0.5) { sz = 0 - sz; } if (rdm2 > 0.5) { pf = 0 - pf; } if (rdm2 > 0.5) { gs = 0 - gs; } if (rdm2 > 0.5) { zg = 0 - zg; } //当前股票价格 szzs.SetCurrent(Math.Round((szzs.GetCurrent() + sz) * 100) / 100.0); pfyh.SetCurrent(Math.Round((pfyh.GetCurrent() + pf) * 100) / 100.0); gsyh.SetCurrent(Math.Round((gsyh.GetCurrent() + gs) * 100) / 100.0); zgsy.SetCurrent(Math.Round((zgsy.GetCurrent() + zg) * 100) / 100.0); }没有加客户端代码,直接运行服务端代码,运行结果是:
这是我们比较常用的委托方式,但是很明显,股票浮动值属性"ran"读取不出来,如果你把代码放到VS中逐过程调试会发现:程序会在theout事件中随机的重复执行。
更改后的代码为:
/// <summary> /// 初始化配置数据 /// </summary> private void Init() { //新建四支股票 Stock szzs = new Stock("300001", "上证指数", 300); Stock pfyh = new Stock("600000", "浦发银行", 25); Stock gsyh = new Stock("601398", "工商银行", 6.5); Stock zgsy = new Stock("601857", "中国石油", 19.1); //设定股票字典 stock = new Dictionary<string, Stock>(); //添加股票 stock.Add("300001", szzs); stock.Add("600000", pfyh); stock.Add("601398", gsyh); stock.Add("601857", zgsy); //设置计时器参数,不要太大 timer = new System.Timers.Timer(50); timer.Enabled = true; //执行计时器函数theout Random rdm = new Random(); //每次只去一个,防止循环执行获取随机数 double mdr = rdm.NextDouble(); //timer的振荡事件,采用匿名函数方式执行 timer.Elapsed += delegate(object source, System.Timers.ElapsedEventArgs e) { //股票变动范围 //上涨浮动 double sz = mdr * 30; double pf = mdr * 0.5; double gs = mdr * 0.1; double zg = mdr * 0.3; //下跌浮动 if (mdr > 0.5) { sz = 0 - sz; } if (mdr > 0.5) { pf = 0 - pf; } if (mdr > 0.5) { gs = 0 - gs; } if (mdr > 0.5) { zg = 0 - zg; } //当前股票价格 szzs.SetCurrent(Math.Round((szzs.GetCurrent() + sz) * 100) / 100.0); pfyh.SetCurrent(Math.Round((pfyh.GetCurrent() + pf) * 100) / 100.0); gsyh.SetCurrent(Math.Round((gsyh.GetCurrent() + gs) * 100) / 100.0); zgsy.SetCurrent(Math.Round((zgsy.GetCurrent() + zg) * 100) / 100.0); }; }
运行结果为:
可以看到这次就能读取到股票浮动字段"ran"了,在VS中逐过程调试也会发现,每次计时器的振荡事件代码只执行一次。
比较一下这两段代码的不同,差别仅是在计时器振荡事件代码的位置:第一段的时钟事件代码放到了theout事件中,而第二段代码则放到了匿名函数中,看似差别不大,但是这里涉及到了变量的作用域问题,第一段代码中,如果想使用
Stock szzs = new Stock("300001", "上证指数", 300); Stock pfyh = new Stock("600000", "浦发银行", 25); Stock gsyh = new Stock("601398", "工商银行", 6.5); Stock zgsy = new Stock("601857", "中国石油", 19.1);这个变量,只能将其放到函数或事件外,用作全局变量;且
Random rdm = new Random(); double rdm2 = rdm.NextDouble();这两行代码放到theout中和匿名函数中也会有不同的运行结果。 重复执行
上面说到重复执行的问题,甚至会在
参考内容:http://smackerelofopinion.blogspot.com/2009/11/qemu-efi-bios.html
我在archlinux上使用aur上的ovmf来实现了相应的功能,ovmf使用的是TianoCore项目QEMU的EFI BIOS的实现。
直接安装:
sudo yaourt -S ovmf-bin可以使用这个EFI BIOS来启动:
qemu-kvm -L /usr/share/ovmf/ -m 2048 -hda efi.img
或者
qemu-kvm -bios /usr/share/ovmf/bios.bin -m 2048 -hda efi.img启动的画面如下:
进入一个EFI的命令行(和virtualbox的EFI命令行一样):