Threads and Pipes in Console Apps
控制台程序中的线程和管道
问题是:如何创建一个可能阻塞的程序,但在阻塞的时候能当数据可读的时候从stdour和stderr中接收数据。本文的目的是展示如何在控制台程序中使用多线程。
子进程程序:
{
for(int i = 0; i < 100; i++)
{ /* test */
int r = rand();
if(r & 0x100)
{ /* stderr */
_ftprintf(stderr, _T("%4d: error\n"), i);
fflush(stderr);
} /* stderr */
else
{ /* stdout */
_ftprintf(stdout, _T("%4d: output\n"), i);
fflush(stdout);
} /* stdout */
int w = rand() % 500;
Sleep(200 + w);
} /* test */
return 0;
}
{
public:
CommandLine() { HTML = FALSE; IsUnicode = FALSE; program = NULL; }
BOOL HTML;//是否使用html
BOOL IsUnicode;//是否使用unicode
LPTSTR program;//目标程序名称
}; // class CommandLine
父进程程序:
{
CommandLine cmd;
for(int i = 1; i < argc; i++)
{ /* scan args */
CString arg = argv[i];
if(arg[0] == _T('-'))
{ /* option */
if(arg == _T("-u"))
{ /* unicode */
cmd.IsUnicode = TRUE;//使用unicode
continue;
} /* unicode */
if(arg == _T("-html"))
{ /* html */
cmd.HTML = TRUE;//使用html
continue;
} /* html */
_ftprintf(stderr, _T("Unrecognized option \"%s\"\n"), arg);
return Result::INVALID_ARGUMENT;
} /* option */
if(cmd.program != NULL)
{ /* two files */
_ftprintf(stderr, _T("Two command directives given:\n [1] \"%s\"\n [2]\"%s\"\n"),
cmd,
arg);
return Result::TWO_COMMANDS;
} /* two files */
cmd.program = argv[i];//目标程序名称
} /* scan args */
if(cmd.program == NULL)
{ /* no args */
_ftprintf(stderr, _T("need program to run\n"));
return Result::NO_PROGRAM;
} /* no args */
SmartHandle iocp = ::CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);//创建IO完成端口
if(iocp == NULL)
{ /* failed iocp */
DWORD err = ::GetLastError();
_ftprintf(stderr, _T("CreateIoCompletionPort failed, error %s\n"), ErrorString(err));
return Result::IOCP_FAILED;
} /* failed iocp */
SECURITY_ATTRIBUTES sa = {sizeof(SECURITY_ATTRIBUTES), NULL, TRUE};
SmartHandle stdout_read;
SmartHandle stdout_write;
SmartHandle stderr_read;
SmartHandle stderr_write;
static const UINT PIPE_BUFFER_SIZE = 32;//管道缓冲区大小
//创建管道
if(!::CreatePipe((LPHANDLE)stdout_read, (LPHANDLE)stdout_write, &sa, PIPE_BUFFER_SIZE))
{ /* failed stdout */
DWORD err = ::GetLastError();
_tprintf(_T("stdout pipe failure: %s\n"), ErrorString(err));
return Result::STDOUT_CREATION_FAILED;
} /* failed stdout */
//创建管道
if(!::CreatePipe((LPHANDLE)stderr_read, (LPHANDLE)stderr_write, &sa, PIPE_BUFFER_SIZE))
{ /* failed stderr */
DWORD err = ::GetLastError();
_tprintf(_T("stderr pipe failure: %s\n"), ErrorString(err));
return Result::STDERR_CREATION_FAILED;
} /* failed stderr */
STARTUPINFO startup = {sizeof(STARTUPINFO)};
startup.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
startup.wShowWindow = SW_HIDE;
//子进程使用的标准输入和标准输出是父进程的写管道
startup.hStdOutput = stdout_write;
startup.hStdError = stderr_write;
PROCESS_INFORMATION procinfo;
//创建子进程,重定向标准输出和标准错误
if(!::CreateProcess(NULL, cmd.program, NULL, NULL, TRUE, CREATE_NEW_CONSOLE, NULL, NULL, &startup, &procinfo))
{ /* failed */
DWORD err = ::GetLastError();
_tprintf(_T("CreateProcess failed for \"%s\": %s"), cmd.program, ErrorString(err));
return Result::CREATEPROCESS_FAILED;
} /* failed */
::CloseHandle(procinfo.hProcess); // handle will never be needed
::CloseHandle(procinfo.hThread); // handle will never be needed
//在父进程这端关闭子进程要使用的管道端口
stdout_write.Close(); // Close our end of the pipe
stderr_write.Close(); // Close our end of the pipe
unsigned id;
//创建处理stdout的子线程
SmartHandle stdoutThread = (HANDLE)_beginthreadex(NULL, 0, reader, new ThreadParms(stdout_read, SourceFlags::StdOut, iocp, cmd.IsUnicode), 0, &id);
if(stdoutThread == NULL)
{ /* thread create failed */
DWORD err = ::GetLastError();
_ftprintf(stderr, _T("Thread creation for stdout failed, error %s\n"), ErrorString(err));
return Result::THREAD_FAILURE;
} /* thread create failed */
stdoutThread.Close(); // handle will never be used
//创建处理stderr的子线程
SmartHandle stderrThread = (HANDLE)_beginthreadex(NULL, 0, reader, new ThreadParms(stderr_read, SourceFlags::StdErr, iocp, cmd.IsUnicode), 0, &id);
if(stderrThread == NULL)
{ /* thread create failed */
DWORD err = ::GetLastError();
_ftprintf(stderr, _T("Thread creation for stderr failed, error %s\n"), ErrorString(err));
return Result::THREAD_FAILURE;
} /* thread create failed */
stderrThread.Close(); // handle will never be used
SourceFlags::FlagType broken = SourceFlags::None;
Result::Type result = Result::SUCCESS;
while(broken != (SourceFlags::StdOut | SourceFlags::StdErr))
{ /* watch pipes */
//异步方式监控管道
OVERLAPPED * ovl;
DWORD bytesRead;
ULONG_PTR key;
//获取完成端口队列状态,获取从两个子线程到来的数据,key表明来自哪个线程已经完成了I/O操作,bytesRead表明数据来源
BOOL ok = ::GetQueuedCompletionStatus(iocp, &bytesRead, &key, &ovl, INFINITE);
if(!ok)
{ /* failed */
DWORD err = ::GetLastError();
result = Result::IOCP_ERROR;
_ftprintf(stderr, _T("GetQueuedCompletionStatus failed, error %s\n"), ErrorString(err));
break;
} /* failed */
broken = (SourceFlags::FlagType)(broken | (int)key);
//终止信号到来
if(key != 0)
continue; // termination notifications contain no data
//输出数据
CString * s = (CString *)ovl;
WriteToOutput(*s, (SourceFlags::FlagType)bytesRead, cmd);
delete s;
} /* watch pipes */
stdout_read.Close();
stderr_read.Close();
return result;
} // _tmain
智能句柄类
用来处理句柄的生存期问题:
{//智能句柄类
public:
SmartHandle() { handle = NULL; }
SmartHandle(HANDLE h) { handle = h; }
virtual ~SmartHandle() { Close();}
public:
operator HANDLE() { return handle; }
operator LPHANDLE() { return & handle; }
bool operator==(HANDLE h) { return handle == h; }
SmartHandle & operator=(HANDLE h) { handle = h; return *this; }
public:
void Close() { if(handle != NULL) ::CloseHandle(handle); handle = NULL; }
protected:
HANDLE handle;
};
线程参数类:
{//线程参数
public:
ThreadParms(HANDLE h, SourceFlags::FlagType f, HANDLE io, BOOL uni)
{
stream = h;
flags = f;
iocp = io;
IsUnicode = uni;
}
public:
HANDLE stream;//管道读端口
SourceFlags::FlagType flags;//指明数据源
HANDLE iocp;//I/O完成端口
BOOL IsUnicode;//是否使用unicode
};
|
|
|
Meaning |
|
0 |
|
|
|
0 |
|
|
0 |
|
|
|
0 |
|
|
|
读线程工作函数
{//读线程工作函数
ThreadParms * parms = (ThreadParms *)p;
PipeReader pipe(parms->stream, parms->IsUnicode);//创建管道读者
CString Prefix;
while(TRUE)
{ /* processing loop */
//读管道数据
if(!pipe.Read())
{ /* failed stream */
break;
} /* failed stream */
FormatAndOutput(pipe.GetString(), Prefix, parms);
} /* processing loop */
if(!Prefix.IsEmpty())
{ /* write out last line */
CString text(_T("\r\n"));
FormatAndOutput(text, Prefix, parms);
} /* write out last line */
//posts an I/O completion packet to an I/O completion port
//发出完成信息,没有数据,因此第二个参数为,第三个参数表明完成的是哪个
::PostQueuedCompletionStatus(parms->iocp, 0, parms->flags, NULL);
delete parms;
return 0;
} // reader
class PipeReader
{//管道读者
protected:
static const UINT MAX_BUFFER = 1024;//缓存大小
public:
PipeReader(HANDLE str, BOOL uni) { Init(); stream = str; IsUnicode = uni; }
CString GetString()
{
if(IsUnicode)
return CString((LPCWSTR)buffer);
else
return CString((LPCSTR)buffer);
}
BOOL Read()
{//从管道中读取数据
if(Offset == 1)
buffer[0] = reread;
if(!ReadFile(stream, &buffer[Offset], MAX_BUFFER - (IsUnicode ? sizeof(WCHAR) : sizeof(char)), &bytesRead, NULL))
return FALSE;
if(IsUnicode)
{ /* unicode pipe */
if((Offset + bytesRead) & 1)
{ /* odd bytes read */
Offset = 1; // offset for next read
reread = buffer[Offset + bytesRead - 1]; // force reread
buffer[Offset + bytesRead - 1] = 0; // remove from current buffer
bytesRead--; // pretend we didn't see it
} /* odd bytes read */
else
{ /* even bytes read */
Offset = 0; // offset for next read
} /* even bytes read */
buffer[Offset + bytesRead] = 0;
buffer[Offset + bytesRead + 1] = 0; // create Unicode NUL
} /* unicode pipe */
else
{ /* ANSI pipe */
buffer[bytesRead] = '\0';
} /* ANSI pipe */
return TRUE;
} // PipeReader::Read
protected:
void Init() { stream = NULL; Offset = 0; IsUnicode = FALSE; }
BOOL IsUnicode;//是否使用unicode
HANDLE stream;//管道读端口
protected:
BYTE buffer[MAX_BUFFER];//缓冲区
DWORD Offset;
BYTE reread;
DWORD bytesRead;
}; // class PipeReader
小结
作者:洞庭散人
出处:http://phinecos.cnblogs.com/
posted on 2008-07-09 22:31 Phinecos(洞庭散人) 阅读(581) 评论(0) 编辑 收藏 举报