这一片文章技术上基本上都是后台相关的,不过比较浅~可以当做知识扩展来看~
做项目的时候遇到 RPC 通信,就研 zhe 究 teng 了一些相关的玩意,这是背景
注意标题:本文 C 的代码需要在 window 下执行
首先我们得知道什么是 IPC 通信
Inter-Process Communication 翻译过来是进程间通信,进程间通信。进程是什么就不用解释了吧~
有两种类型的进程间通信
1、LPC 本地过程调用
2、RPC 远程过程调用
这些调用有什么乱用?
它们可以 数据传输、共享数据、通知事件、资源共享等等
可以看到 IPC 调用还是很重要的,缺之不可
那么接下来的问题就是该如何实现 IPC 通信呢?
先说几个常见和通用的
1、管道
2、文件映射
3、共享内存
4、Socket
还有一些不同操作系统下面特有的,比如 window 下的邮件槽,剪切板,动态数据交换,动态链接库
linux 下特有的信号、消息队列、信号量
在这里我们也主要讲前四种~当然我也不会原因是后面的我也没研究~
一、管道
netstat -a | grep node
上面这条命令对用过 linux 的童鞋来说应该很熟悉吧,这是 linux 里面的管道符“ | ” 的基本用法,管道的原理是什么呢?看图~
管道是一个内核的缓冲区,它的一端连接一个进程的输出,管道的另一端连接另外一个进程,负责输出,有没有一种抽象的概念了?
管道分为两种,一种是匿名管道,主要用于具有亲缘关系的进程通信,eg:node 中用 fork 或者 exec 创建的新进程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
var fork = require('child_process').fork; var child = fork('./client'); console.log(child.pid); console.log(process.pid); child.on('message', function(m) { console.log('Server Listen:', m); }); process.stdin.resume(); process.stdin.setEncoding('utf8'); process.stdin.on('data', function(data) { child.send(data); }); |
代码就不解释了,及其的简单
不过我们从匿名管道中就能感觉到其局限性,必须是亲缘关系的进程才可以通信,命名管道改变了这一点
命名管道的原理是酱紫的:服务器创建一个命名管道对象,然后等待连接,客户端连接,二者可以读写数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
#include<stdio.h> #include<windows.h> #define PIPE_NAME L"\\\\.\\Pipe\\test" HANDLE g_hPipe = INVALID_HANDLE_VALUE; int out(HANDLE g) { printf("Close pipe.\n"); CloseHandle(g); system("pause"); return 0; } int main() { char buffer[1024]; DWORD WriteNum; printf("test server.\n"); g_hPipe = CreateNamedPipe(PIPE_NAME, PIPE_ACCESS_DUPLEX, PIPE_TYPE_BYTE | PIPE_READMODE_BYTE, 1, 0, 0, 1000, NULL); if (g_hPipe == INVALID_HANDLE_VALUE) { printf("Create name pipe failed!\n"); out(g_hPipe); } printf("Wait for connect...\n"); if (ConnectNamedPipe(g_hPipe, NULL) == FALSE) { printf("Connect failed!\n"); out(g_hPipe); } printf("Connected.\n"); while (1) { scanf("%s", &buffer); if (WriteFile(g_hPipe, buffer, (DWORD)strlen(buffer), &WriteNum, NULL) == FALSE) { printf("Write failed!\n"); break; } else { printf("Write success!\n"); } } } |
PIPE_NAME 是需要统一的管道名,CreateNamedPipe 创建一个命名管道(具体参数我这里就不说了,可以上手册,毕竟这里不是后台的博客……),while(1) 这样的代码出现是不是很神奇~此时服务器端就在监听客户端的管道连接啦
下面是客户端的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
#include<stdio.h> #include<windows.h> #define PIPE_NAME L"\\\\.\\Pipe\\test" HANDLE g_hPipe = INVALID_HANDLE_VALUE; int out(HANDLE g) { printf("Close pipe.\n"); CloseHandle(g); system("pause"); return 0; } int main() { char buffer[1024]; DWORD ReadNum; printf("test client.\n"); g_hPipe = CreateFile(PIPE_NAME, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (g_hPipe == INVALID_HANDLE_VALUE) { printf("Connect pipe failed!\n"); out(g_hPipe); } printf("Connected.\n"); while (1) { if (ReadFile(g_hPipe, buffer, sizeof(buffer), &ReadNum, NULL) == FALSE) { break; } buffer[ReadNum] = 0; printf("%s\n", buffer); } } |
用 CreateFile 指向 PIPE_NAME 对应的管道,while(1) 循环数据服务器端处理过来的值,图示这样的
接下来是内存映射文件和共享内存,这两者之间几乎没什么区别,看下去就知道了~首先图示说一下原理
稍微解释一下:文件映射呢,就是一种将文件内容映射到内存地址的技术,通过对映射内存,读写文件如同读写内存一般简单;它的实现原理如果简单一点说就是硬盘文件的位置与进程的逻辑地址空间中一块大小相同的区域之间一一对应
贴代码~服务器端
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
#include <stdio.h> #include <windows.h> #include <stddef.h> int main() { HANDLE hFile; HANDLE hMapFile; HANDLE hFileRead; HANDLE hFileWrite; void *pMemory; // wchar_t buf[4096]; char buf[4096]; char index; hFile = CreateFile(L"D:\\test.txt", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); hMapFile = CreateFileMapping(hFile, NULL, PAGE_READWRITE, 0, 4 * 1024, TEXT("shareFile")); if(hMapFile == NULL) { printf("分配内存出错"); return 0; } pMemory = MapViewOfFile(hMapFile, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 0); if(pMemory == NULL) { printf("申请内存失败"); return 0; } while(1) { scanf("%s", &index); if (index == 's') { printf("\n请输入:"); scanf("%s", &buf); // lstrcpy((wchar_t *)pMemory, buf); strcpy((char *)pMemory, buf); printf("\nsuccess!\n"); } else { UnmapViewOfFile(hFile); return 0; } } } |
注意此时我只想的是 D:\\test.txt 这一文件,当程序跑起来的时候,你是没法打开这个文件的~
客户端代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
#include <stdio.h> #include <windows.h> #include <stddef.h> int main() { HANDLE hFile = OpenFileMapping(FILE_MAP_ALL_ACCESS, true, TEXT("shareFile")); HANDLE hMapFile = MapViewOfFile(hFile, FILE_MAP_ALL_ACCESS, 0, 0, 0); wchar_t buf[4096]; char index; if (hFile == NULL) { printf("获取内存映射失败"); return 0; } while (1) { scanf("%s", &index); if (index == 'v') { UnmapViewOfFile(hMapFile); return 0; } else { printf("%s\n", (char *)hMapFile); } } } |
效果图我就不演示了,和管道差不多,区别是文件映射我这边需要主动的去拉取映射的文件的内容,并没有自动的判断内容变化将其输出…… 好吧偷懒了
共享内存和文件映射代码上的区别一句话概括
最后是 socket,好吧终于到了和我们前端关联可能较紧密的了,我说的项目里面 RPC 通信用的也是 socket,上面说的那三种都是 IPC 通信中 LPC 的范畴,本地的进程间调用
Socket 工作在 OSI 模型的会话层,方便使用更底层的协议(TCP,UDP)而存在的一个抽象层
如何使用是关键,我们生活在一个幸福的时代,已经有很多封装好的库可以使用啦,比如:
socket.io、net、ws 等等
socket.io 名气太大,举例子就不用它啦,这次就用 net 好了~
服务器端代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
var net = require('net'); var server = net.createServer(function(conn){ console.log('connected'); conn.on('data', function(data){ console.log(data + 'from' + conn.remoteAddress + ' ' + conn.remotePort); conn.write('Repeating:' + data); }); conn.on('close', function(){ console.log('client closed connection'); }); }).listen(8124); |
客户端代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
var net = require('net'); var client = new net.Socket(); client.setEncoding('utf8'); client.connect('8124', 'localhost', function(){ console.log('connected to server'); }); process.stdin.resume(); process.stdin.on('data', function(data){ client.write(data); }); client.on('data', function(data){ console.log(data); }); client.on('close', function(){ console.log('connection is closed'); }); |
结果如下:
好啦,今天的 window 下 IPC 探索就介绍到这里啦
霸道女总裁 2015 年 10 月 26 日
[叹号]