进程间通信:同步双工管道
因为工作需要,需要设计出一个双工的IPC。(转载请指明出处)在一番比较后,我发现管道是比较符合我们的需求的。但是我们需求要求管道的对方是可信任的,而在vista以下系统是没有GetNamedPipeClientProcessId、GetNamedPipeClientSessionId、GetNamedPipeServerProcessId、
GetNamedPipeServerSessionId这样的函数,于是是否可信这个操作就要求由客户端和服务端两方互检来完成,至于互检的思路,我会在之后管道的加强版中给出思路和例子。而本文只是简单介绍一个同步双工管道。
在工作中写的管道模型中,服务端每次被连接上,都会启动一个连接实例(线程)。于是如果存在多个客户端接入的情况下,将启动多个线程。这样的模型比较简单,但是效率存在问题。这些天我参考了微软的例子,重写了管道模型。服务端只启动一个线程,利用该线程的APC完成所有连接的读写操作。因为是同步双工,所以我设计的模型是不停的一问一答。当有消息要发向对方时,只需要向“问”列表中插入消息,底层会将这条消息发往对方;如果“问”表中不存数据,则发一条垃圾消息,对方在接受到这条消息后不做任何处理。这样的设计也就是为了维持管道畅通,不因一个环节卡住导致其他操作不可完成。
对于管道模型,我设计成:传输层,数据层,逻辑层,应用层四层结构。其中传输层只负责管道连接和数据传输,不关心数据内容;数据层会将传输层所有取到的数据以管道句柄为依据进行分组,同时负责将各个连接要传给对方的数据汇总供传输层使用;逻辑层考虑加入验证逻辑,即验证对方是否为可信任,同时为应用层提供方便的调用支持,比如在逻辑层启动一个线程调用一个应用层设置的回调函数来处理接受到的消息,同时暴露一个发送数据的函数供应用层使用。这样应用层只要实现处理消息的回调、调用发送数据的接口即可。(工作中设计的管道模型就是这样子的。因为我准备重写一个更稳定和高效的管道,目前只大致写好了传输层代码。)
服务端
#include "stdafx.h"
#include "PipeServerInstance.h"
#include <string>
#include <strsafe.h>CPipeServerInstance::CPipeServerInstance()
{m_hPipe = NULL;
}CPipeServerInstance::~CPipeServerInstance()
{}VOID CPipeServerInstance::StartService()
{HANDLE hConnectEvent = NULL; // 创建一个连接等待事件hConnectEvent = CreateEvent( NULL, TRUE, TRUE, NULL );if ( NULL == hConnectEvent ) {// 创建连接事件失败MYTRACE(L"CreateEvent failed with %d.\n", GetLastError()); return;}OVERLAPPED oConnect;::ZeroMemory( &oConnect, sizeof(OVERLAPPED) );oConnect.hEvent = hConnectEvent; BOOL bPendingIO = FALSE;// 创建一个管道实例并等待客户端接入bPendingIO = CreateAndConnectInstance( &oConnect ); // 等待事件的返回值DWORD dwWait = WAIT_TIMEOUT;while ( TRUE ) { // 等待一个客户端的接入,或者为了读写例程执行dwWait = WaitForSingleObjectEx( hConnectEvent, INFINITE, TRUE );switch ( dwWait ) { case WAIT_OBJECT_0: // 一个客户端接入if ( FALSE == RunServerInstance( &oConnect, bPendingIO) ){return;}if ( FALSE == bPendingIO ){return;}break; case WAIT_IO_COMPLETION: // 等待事件是由读写完成例程触发的break; default: {// 错误MYTRACE(L"WaitForSingleObjectEx (%d)\n", GetLastError()); return ;}} } return;
}BOOL CPipeServerInstance::RunServerInstance( LPOVERLAPPED lpoConnect, BOOL& bPendingIO )
{DWORD cbRet = 0;// 如果一个操作被挂起,则获取这个连接的结果if ( FALSE != bPendingIO ) { if ( FALSE == GetOverlappedResult( m_hPipe, lpoConnect, &cbRet, FALSE ) ) {MYTRACE(L"ConnectNamedPipe (%d)\n", GetLastError()); return FALSE;}} LPPIPEINST lpPipeInst; // 分配全局固定内存空间用于保存读写数据lpPipeInst = (LPPIPEINST) GlobalAlloc( GPTR, sizeof(PIPEINST) ); if ( NULL == lpPipeInst) {MYTRACE(L"GlobalAlloc failed (%d)\n", GetLastError()); return FALSE;}lpPipeInst->hPipeInst = m_hPipe; lpPipeInst->lpPointer = this;DealMsg( lpPipeInst );WriteFileEx( lpPipeInst->hPipeInst, lpPipeInst->cbWrite, lpPipeInst->dwWrite, (LPOVERLAPPED) lpPipeInst, (LPOVERLAPPED_COMPLETION_ROUTINE) CompletedWriteRoutine); // 创建一个新的管道实例去等待下一个客户端接入bPendingIO = CreateAndConnectInstance( lpoConnect ); return TRUE;
}BOOL CPipeServerInstance::StopService()
{m_CriticalPipeHandle.Lock();for ( VECHANDLEITER it = m_VecPipeHandle.begin(); it != m_VecPipeHandle.end(); it++ ){DisconnectNamedPipe( *it );}m_CriticalPipeHandle.Unlock();return TRUE;
}VOID CPipeServerInstance::DisconnectAndClose( LPPIPEINST lpPipeInst )
{if( NULL == lpPipeInst || NULL == lpPipeInst->hPipeInst ){return;}// 断开管道连接if ( FALSE == DisconnectNamedPipe( lpPipeInst->hPipeInst ) ) {MYTRACE(L"DisconnectNamedPipe failed with %d.\n", GetLastError());}// 关闭管道CloseHandle( lpPipeInst->hPipeInst ); // 释放掉全局分配的内存if ( NULL != lpPipeInst ) {GlobalFree(lpPipeInst); }
}BOOL CPipeServerInstance::CreateAndConnectInstance( LPOVERLAPPED lpoOverlap )
{m_hPipe = CreateNamedPipe( PIPENAME, PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED,PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT,PIPE_UNLIMITED_INSTANCES,sizeof(PipeMsgStruct),sizeof(PipeMsgStruct),PIPE_TIMEOUT,NULL );if ( INVALID_HANDLE_VALUE == m_hPipe ) {MYTRACE(L"CreateNamedPipe failed with %d.\n", GetLastError()); return FALSE;}m_CriticalPipeHandle.Lock();m_VecPipeHandle.push_back( m_hPipe );m_CriticalPipeHandle.Unlock();// 启动一个新的连接等待客户端接入return ConnectToNewClient( m_hPipe, lpoOverlap );
}BOOL CPipeServerInstance::ConnectToNewClient( HANDLE hPipe, LPOVERLAPPED lpo )
{BOOL fPendingIO = FALSE; DWORD dwLastError = ERROR_SUCCESS;// 异步命名管道连接应该失败if ( FALSE != ConnectNamedPipe( hPipe, lpo ) ) { MYTRACE( L"ConnectNamedPipe failed with %d.\n", GetLastError() ); return FALSE;}switch ( GetLastError() ) { // If the function fails, the return value is zero // and GetLastError returns a value other than ERROR_IO_PENDING or ERROR_PIPE_CONNECTED.case ERROR_IO_PENDING: {// 正在连接fPendingIO = TRUE; }break; case ERROR_PIPE_CONNECTED: {// If a client connects before the function is called, // the function returns zero and GetLastError returns ERROR_PIPE_CONNECTED. // This can happen if a client connects in the interval // between the call to CreateNamedPipe and the call to ConnectNamedPipe.// In this situation, there is a good connection between client and server, // even though the function returns zero.// 如果客户端已经连接上,则设置事件if ( SetEvent(lpo->hEvent) ) {break; }}// 这个地方故意不break的,因为SetEvent失败了default: {MYTRACE(L"ConnectNamedPipe failed with %d.\n", GetLastError());}} return fPendingIO;
}VOID WINAPI CPipeServerInstance::CompletedWriteRoutine( DWORD dwErr, DWORD cbWritten,LPOVERLAPPED lpOverLap )
{LPPIPEINST lpPipeInst = NULL; BOOL fRead = FALSE; // 因为lpOverLap的内存是固定的,而其又是LPPIPEINST类型的第一个元素// 于是,这样就可以获得之前分配的LPPIPEINST类型的对象lpPipeInst = (LPPIPEINST) lpOverLap; // 已经异步写完,于是再异步读if ( ( 0 == dwErr ) && ( cbWritten == lpPipeInst->dwWrite ) ) fRead = ReadFileEx( lpPipeInst->hPipeInst, lpPipeInst->cbRead, PIPEMSGLENGTH, (LPOVERLAPPED) lpPipeInst, (LPOVERLAPPED_COMPLETION_ROUTINE) CompletedReadRoutine); // 如果写失败了,就断开连接CPipeServerInstance* pThis = static_cast<CPipeServerInstance*> (lpPipeInst->lpPointer);if ( FALSE == fRead && NULL != pThis ) {pThis->DisconnectAndClose( lpPipeInst ); }
}VOID WINAPI CPipeServerInstance::CompletedReadRoutine( DWORD dwErr, DWORD cbBytesRead, LPOVERLAPPED lpOverLap )
{LPPIPEINST lpPipeInst = NULL; BOOL fWrite = FALSE; // 因为lpOverLap的内存是固定的,而其又是LPPIPEINST类型的第一个元素// 于是,这样就可以获得之前分配的LPPIPEINST类型的对象lpPipeInst = (LPPIPEINST) lpOverLap; CPipeServerInstance* pThis = static_cast<CPipeServerInstance*> (lpPipeInst->lpPointer);// 已经异步读完,于是再异步写if ( 0 == dwErr && 0 != cbBytesRead && NULL != pThis) { pThis->DealMsg( lpPipeInst );fWrite = WriteFileEx( lpPipeInst->hPipeInst, lpPipeInst->cbWrite, lpPipeInst->dwWrite, (LPOVERLAPPED) lpPipeInst, (LPOVERLAPPED_COMPLETION_ROUTINE) CompletedWriteRoutine); }// 如果读失败了,就断开连接if ( FALSE == fWrite && NULL != pThis ) {pThis->DisconnectAndClose(lpPipeInst); }
}VOID CPipeServerInstance::DealMsg( LPPIPEINST pipe )
{MYTRACE( L"[%p] %s\n", pipe->hPipeInst, pipe->cbRead);std::wstring strCmd = L"Default answer from server";StringCchCopy( (WCHAR*)pipe->cbWrite, PIPEMSGLENGTH, strCmd.c_str() );pipe->dwWrite = ( lstrlen( strCmd.c_str() ) + 1 ) * sizeof(TCHAR);
}
客户端
#include "stdafx.h"
#include "PipeClientInstance.h"
#include <string>
#include <strsafe.h>CPipeClientInstance::CPipeClientInstance()
{m_hPipe = NULL;m_hStopEvent = NULL;
}CPipeClientInstance::~CPipeClientInstance()
{}BOOL CPipeClientInstance::StartClient()
{LPPIPEINST lpPipeInst = NULL; // 创建一个退出事件m_hStopEvent = CreateEvent( NULL, FALSE, FALSE, NULL );if ( NULL == m_hStopEvent ) {// 创建退出事件失败MYTRACE(L"CreateEvent failed with %d.\n", GetLastError()); return FALSE;}// 连接服务器if ( FALSE == ConnectToServer() ){return FALSE;}// 消息循环if ( FALSE == MsgLoop( lpPipeInst ) ){return FALSE;}// 关闭管道清除内存DisconnectAndClose( lpPipeInst );return TRUE;
}BOOL CPipeClientInstance::ConnectToServer()
{while ( WAIT_TIMEOUT == WaitForSingleObjectEx( m_hStopEvent, 10, TRUE ) ) { // 异步的方式打开一个已经存在的管道m_hPipe = CreateFile( PIPENAME, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);if ( INVALID_HANDLE_VALUE != m_hPipe ){// 打开管道成功break; }if ( ERROR_PIPE_BUSY != GetLastError() ) {// 除了发生ERROR_PIPE_BUSY错误,否则其他错误都认为打开失败MYTRACE( L"Could not open pipe. GLE=%d\n", GetLastError() ); return FALSE;}// 所有的管道处于“忙”状态,所以等待20秒if ( FALSE == WaitNamedPipe( PIPENAME, 20000 ) ) { MYTRACE(L"Could not open pipe: 20 second wait timed out."); return FALSE;}} return TRUE;
}BOOL CPipeClientInstance::MsgLoop( LPPIPEINST& lpPipeInst )
{DWORD dwMode = PIPE_READMODE_BYTE; BOOL bSuccess = SetNamedPipeHandleState( m_hPipe, &dwMode, NULL, NULL );if ( FALSE == bSuccess ){return FALSE;}// 分配全局固定内存空间用于保存读写数据lpPipeInst = (LPPIPEINST) GlobalAlloc( GPTR, sizeof(PIPEINST) ); if ( NULL == lpPipeInst) {MYTRACE(L"GlobalAlloc failed (%d)\n", GetLastError()); return FALSE;}lpPipeInst->hPipeInst = m_hPipe; lpPipeInst->lpPointer = this;lpPipeInst->dwWrite = 0;m_CriticalSectionQuit.Lock();lpPipeInst->bSuccess = TRUE;m_CriticalSectionQuit.Unlock();CompletedWriteRoutine( 0, 0, (LPOVERLAPPED) lpPipeInst ); DWORD dwWait = WAIT_TIMEOUT;bSuccess = FALSE;BOOL bExit = FALSE;do {bSuccess = FALSE;// 等待退出事件,同时也让读写完成例程执行dwWait = WaitForSingleObjectEx( m_hStopEvent, INFINITE, TRUE );switch( dwWait ){case WAIT_OBJECT_0:{// 等到了终止事件,退出循环bExit = TRUE;}break;case WAIT_IO_COMPLETION:{m_CriticalSectionQuit.Lock();if ( FALSE != lpPipeInst->bSuccess ){// 读写完成,继续循环bSuccess = TRUE;}else{bExit = TRUE;}m_CriticalSectionQuit.Unlock();}break;default:{// 其他类型导致退出,认为是出错,退出循环bExit = TRUE;}}if ( FALSE != bExit ){break;}} while( FALSE != bSuccess );return TRUE;
}BOOL CPipeClientInstance::StopClient()
{if ( NULL != m_hStopEvent ){::SetEvent( m_hStopEvent );}return TRUE;
}VOID CPipeClientInstance::DisconnectAndClose( LPPIPEINST lpPipeInst )
{if ( NULL != lpPipeInst->hPipeInst ){// 关闭管道CloseHandle( lpPipeInst->hPipeInst ); }// 释放掉全局分配的内存if ( NULL != lpPipeInst ) {GlobalFree(lpPipeInst); }
}VOID WINAPI CPipeClientInstance::CompletedWriteRoutine( DWORD dwErr, DWORD cbWritten,LPOVERLAPPED lpOverLap )
{LPPIPEINST lpPipeInst = NULL; BOOL fRead = FALSE; // 因为lpOverLap的内存是固定的,而其又是LPPIPEINST类型的第一个元素// 于是,这样就可以获得之前分配的LPPIPEINST类型的对象lpPipeInst = (LPPIPEINST) lpOverLap; // 已经异步写完,于是再异步读if ( ( 0 == dwErr ) && ( cbWritten == lpPipeInst->dwWrite ) ) fRead = ReadFileEx( lpPipeInst->hPipeInst, lpPipeInst->cbRead, PIPEMSGLENGTH, (LPOVERLAPPED) lpPipeInst, (LPOVERLAPPED_COMPLETION_ROUTINE) CompletedReadRoutine); // 如果写失败了,就断开连接CPipeClientInstance* pThis = static_cast<CPipeClientInstance*> ( lpPipeInst->lpPointer );if ( FALSE == fRead && NULL != pThis ) {pThis->NotifyExit( lpPipeInst ); }
}VOID WINAPI CPipeClientInstance::CompletedReadRoutine( DWORD dwErr, DWORD cbBytesRead, LPOVERLAPPED lpOverLap )
{LPPIPEINST lpPipeInst = NULL; BOOL fWrite = FALSE; // 因为lpOverLap的内存是固定的,而其又是LPPIPEINST类型的第一个元素// 于是,这样就可以获得之前分配的LPPIPEINST类型的对象lpPipeInst = (LPPIPEINST) lpOverLap; CPipeClientInstance* pThis = static_cast<CPipeClientInstance*> (lpPipeInst->lpPointer);// 已经异步读完,于是再异步写if ( 0 == dwErr && 0 != cbBytesRead && NULL != pThis) { pThis->DealMsg( lpPipeInst );fWrite = WriteFileEx( lpPipeInst->hPipeInst, lpPipeInst->cbWrite, lpPipeInst->dwWrite, (LPOVERLAPPED) lpPipeInst, (LPOVERLAPPED_COMPLETION_ROUTINE) CompletedWriteRoutine); }// 如果读失败了,就断开连接if ( FALSE == fWrite && NULL != pThis ) {pThis->NotifyExit(lpPipeInst); }
}VOID CPipeClientInstance::DealMsg( LPPIPEINST pipe )
{MYTRACE( L"[%p] %s\n", pipe->hPipeInst, pipe->cbRead );std::wstring strCmd = L"Default answer from client";StringCchCopy( (WCHAR*)pipe->cbWrite, PIPEMSGLENGTH, strCmd.c_str() );pipe->dwWrite = ( lstrlen( strCmd.c_str() ) + 1 ) * sizeof(TCHAR);
}VOID CPipeClientInstance::NotifyExit( LPPIPEINST lpPipeInst )
{m_CriticalSectionQuit.Lock();lpPipeInst->bSuccess = FALSE;m_CriticalSectionQuit.Unlock();
}
这个代码中的一些值得注意的设计:
- 在写完成例程中调用异步读,在读完成例程中调用异步写,从而实现同步双工。(特别注意不要在完成例程中的异步操作后WaitforXXEX,否则会出现严重的递归问题,最后内存耗尽,程序挂掉)
- 对每一个接入,都分配一个不可移动的内存,其第一个元素设置成OVERLAPPED结构对象,同时让这个结构对象就是异步操作和完成例程中都会使用的那个参数。如异步操作
BOOL WINAPI ReadFileEx(__in HANDLE hFile,__out_opt LPVOID lpBuffer,__in DWORD nNumberOfBytesToRead,__inout LPOVERLAPPED lpOverlapped,__in_opt LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
);
完成例程
VOID CALLBACK FileIOCompletionRoutine(__in DWORD dwErrorCode,__in DWORD dwNumberOfBytesTransfered,__inout LPOVERLAPPED lpOverlapped
);
这样设计,就可以达到一个很重要的目的:在完成例程中获取“读/写”的数据。
对应的工程地址是:CommunicatePipe工程
(转载请指明出处)
相关文章:
就因为一个笔记本,运营和产品吵得不可开交......
上班最讨厌的一件事情,莫过于开会,因为每次开会感觉就要吵架,这个今天开会又吵架了,吵架竟然是因为产品小姐姐的笔记本。产品小姐姐用了一本可擦笔记本记录会议内容,运营小姐姐竟然说这个本子有什么用,不就…

Ka的递归编程练习 Part4|Hanoi汉诺塔,双色汉诺塔的也有
1 #include <stdio.h>2 void hanoi(int s,char a,char b,char c) //a是出发盘,b是中途盘,c是结束盘 3 {4 if(s0) return;5 hanoi(s-1,a,c,b); //把最底下的从a借助c移动到b6 printf("%d from %c move to %c\n",s,a,c);7 …

一种精确从文本中提取URL的思路及实现
在今年三四月份,我接受了一个需求:从文本中提取URL。这样的需求,可能算是非常小众的需求了。大概只有QQ、飞信、阿里旺旺等之类的即时通讯软件存在这样的需求。在研究这个之前,我测试了这些软件这块功能,发现它们这块的…
解读 | 2019年10篇计算机视觉精选论文(上)
作者 | 神经小兮来源 | HyperAI超神经(ID:HyperAI)2019 年转眼已经接近尾声,我们看到,这一年计算机视觉(CV)领域又诞生了大量出色的论文,提出了许多新颖的架构和方法,进一步提高了视…

不错的工具:Reflector for .NET
下载地址: http://www.aisto.com/roeder/dotnet/ 注意:下载时要输一些注册信息,输入用户名时,中间要加一个空格。

Possible MySQL server UUID duplication for server
在mysql enterprise monitor监控过程中出现这样的event事件,Topic: Possible MySQL server UUID duplication for server 事件,从该提示的描述来看貌似是存在重复的uuid,而实际上主从关系并不存在重复的uuid。主从关…

使用VC实现一个“智能”自增减线程池
工作中接手了一款产品的改造。因为该产品可能使用很多线程,所以产品中使用了线程池。(转载请指明来自BreakSoftware的CSDN博客) 线程池的一个优点是降低线程创建和销毁的频率;缺点是可能在比较闲的时候还存在一定数量的空闲线程。…

国内外财务软件科目结构的比较
科目结构是整个会计核算的基础。国内外财务软件都是任意定义科目的分段及科目编码长度,一般都能支持六段到九段。但科目结构在不同的国家有不同的规范,因而在不同的财务软件中也就有不同的控制。在科目分类上,国内外有明显的区别。国外财务软…
朋友圈装死,微博蹦迪,Python教你如何掌握女神情绪变化 | CSDN博文精选
作者 | A字头来源 | 数据札记倌很多人都是在朋友圈装死,微博上蹦迪。微信朋友圈已经不是一个可以随意发表心情的地方了,微博才是!所以你不要傻傻盯着女神的朋友圈发呆啦!本文教你如何用Python自动通知女神微博情绪变化,…

java异常笔记
Throwable是所有Java程序中错误处理的父类,有两种资类:Error和Exception。Error:表示由JVM所侦测到的无法预期的错误,由于这是属于JVM层次的严重错误,导致JVM无法继续执行,因此,这是不可捕捉到的…
2019最新进展 | Transformer在深度推荐系统中的应用
作者 | Alex-zhai来源 | 深度传送门(ID:deep_deliver)【导读】最近基于Transformer的一些NLP模型很火(比如BERT,GPT-2等),因此将Transformer模型引入到推荐算法中是近期的一个潮流。Transformer比起传统的L…

自己架设windows升级服务器
大部分对计算机比较熟悉的朋友都知道,通常安装好Windows 操作系统后要做的第一件事就是上Windows Update网站去给Windows 安装补丁程序,否则各种漏洞对系统就是一个很大的威胁。不过遗憾的是很多人还没有这样的意识,疏忽了给系统打补丁。这也…

内嵌IE网页窗口中消除IE默认脚本设置影响的方法
随着人们对客户端软件界面要求的不断提高,软件开发商面临着一个问题:如何快速廉价开发出各种丰富效果的UI界面。设计出一套丰富控件的界面库是不容易的,且产品经理丰富的想法和UED对效果的追求,往往会使程序员疲于编写这些“效果控件”。目前市面上使用的很多界面库是基于X…

win7 64位操作系统中 Oracle 11g 安装教程(图解)
1.下载Oracle 11g R2 for Windows版本,下载地址如下 官方网站: http://download.oracle.com/otn/nt/oracle11g/112010/win32_11gR2_database_1of2.zip http://download.oracle.com/otn/nt/oracle11g/112010/win32_11gR2_database_2of2.zip 2.解压两…

使用APIHOOK实现进程隐藏
今天翻出一些今年前写的代码。其中一个是09年,我帮一个读研的同学写的一个“无公害恶意”程序。大致要求就是要实现自启动和自我隐藏。我使用的都是些简单的技术,只是实现自我隐藏稍微让我花费了点时间写算法。其实这个算法也很简单,就是大学…
程序员创业前要做哪些准备?
作者 | hsm_computer出品 | CSDN博客在互联网时代,不少干IT的人白手起家,在短短的几年里通过努力干出了一番事业,有房有车有公司,在人前也很光鲜。这就吸引了更多的程序员想要通过自主创业来实现财务自由。殊不知,创业…

Flex编码过程
Flex编码过程当我们开发一个Flex程序,我们重复其他类型网络程序的过程,例如HTML,JSP,ASP和CFML。创建一个有用的Flex程序是很容易的:打开我们最喜欢的文本编辑器,例如Flex Builder,输入XML标签,编译成为SWF…

BufferedWriter
package JBJADV003;import java.io.*;public class BufferedWriterTest { public static void main(String[] args) { try { //创建一个FileWriter 对象 FileWriter fwnew FileWriter("c:\\myDoc\\hello.txt"); //创建一个BufferedWriter 对象 BufferedWriter bwnew…
使用VC内嵌Python实现的一个代码检测工具
最近组内准备整顿代码,领导让我写个简单的python脚本分析代码中注释的行数和无效注释。因为这个需求不是很急,所以我想把简单的事情做复杂点。于是就写了一个用VC内嵌Python,并通过模拟按键和发消息去控制其他软件的工具。(转载请…
Python如何实现24个微信大群万人同步转发直播?
作者 | 猪哥66来源 | CSDN博客今天我们来学习微信机器人多群转发做同步图文直播!一、背景介绍猪哥一年前在建Python学习群的时候就说过,要邀请企业大佬来学习群做直播。其实文章早就写好了,但是一直没有找到好的转发软件,所以耽搁…

ITSM实施三招[案例]
当前国外成熟的ITSM解决方案的实施成本相对比较高,使一些对成本较敏感的的IT部门,成为ITSM实施的一个真空区。对于国内起步阶段的ITSM(IT服务管理)实施来说,南航的ITSM实施之路是一个借鉴。 南航it环境 在各大航空公司…
lr手工添加关联函数的步骤:
点击“确定”后: 如何修改已经创建好的关联规则:

新闻内容实现分页
/**//// <summary> /// 新闻内容分页 /// </summary> /// <param name"content">新闻内容</param> /// <param name"extension">扩展名(aspx,html..)</param> /// <returns></returns>pub…
使用自己的数据集训练MobileNet、ResNet实现图像分类(TensorFlow)| CSDN博文精选
作者 | pan_jinquan来源 | CSDN博文精选之前写了一篇博客《使用自己的数据集训练GoogLenet InceptionNet V1 V2 V3模型(TensorFlow)》https://panjinquan.blog.csdn.net/article/details/81560537,本博客就是此博客的框架基础上,完…

VC下提前注入进程的一些方法1——远线程不带参数
前些天一直在研究Ring3层的提前注入问题。所谓提前注入,就是在程序代码逻辑还没执行前就注入,这样做一般用于Hook API。(转载请指明出处)自己写了个demo,在此记下。 我的demo使用了两种注入方式:1 远线程&a…

【转】用示例说明索引数据块中出现热块的场景,并给出解决方案
文章转自:http://www.luocs.com/archives/582.html

VC下提前注入进程的一些方法2——远线程带参数
在前一节中介绍了通过远线程不带参数的方式提前注入进程,现在介绍种远线程携带参数的方法。(转载请指明出处) 1.2 执行注入的进程需要传信息给被注入进程 因为同样采用的是远线程注入,所以大致的思路是一样的,只是在细…
芬兰开放“线上AI速成班”课程,全球网民均可免费观看
出品 | AI科技大本营(ID:rgznai100)去年,芬兰推出了一个免费的“人工智能线上速成班”项目,目的是向该国民众教授与新技术有关的知识。现在,作为送给全世界的圣诞节礼物,这个项目已面向全球网民开放访问&am…

deepin开通ssh
1、在终端打入下面命令进行安装sudo apt-get install openssh-server2、启用sshservice ssh start 反馈:start: Rejected send message, 1 matched rules; type"method_call", sender":1.56" (uid1000 pid2272 comm"start ssh ") int…