深入浅出Win32多线程程序设计之(五):综合实例
2010年09月28日
本章我们将以工业控制和嵌入式系统中运用极为广泛的串口通信为例讲述多线程的典型应用。
而网络通信也是多线程应用最广泛的领域之一,所以本章的最后一节也将对多线程网络通信进行简短的描述。
1.串口通信
在工业控制系统中,工控机(一般都基于PC Windows平台)经常需要与单片机通过串口进行通信。因此,操作和使用PC的串口成为大多数单片机、嵌入式系统领域工程师必须具备的能力。
串口的使用需要通过三个步骤来完成的:
(1) 打开通信端口;
(2) 初始化串口,设置波特率、数据位、停止位、奇偶校验等参数。为了给读者一个直观的印象,下图从Windows的"控制面板->系统->设备管理器->通信端口(COM1)"打开COM的设置窗口:
(3) 读写串口。
在WIN32平台下,对通信端口进行操作跟基本的文件操作一样。
创建/打开COM资源
下列函数如果调用成功,则返回一个标识通信端口的句柄,否则返回-1:
HADLE CreateFile(PCTSTR lpFileName, //通信端口名,如"COM1"
WORD dwDesiredAccess, //对资源的访问类型
WORD dwShareMode, //指定共享模式,COM不能共享,该参数为0
PSECURITY_ATTRIBUTES lpSecurityAttributes,
//安全描述符指针,可为NULL
WORD dwCreationDisposition, //创建方式
WORD dwFlagsAndAttributes, //文件属性,可为NULL
HANDLE hTemplateFile //模板文件句柄,置为NULL
);
获得/设置COM属性
下列函数可以获得COM口的设备控制块,从而获得相关参数:
如果要调整通信端口的参数,则需要重新配置设备控制块,再用WIN32 API SetCommState()函数进行设置:
DCB结构包含了串口的各项参数设置,如下:
typedef struct _DCB
{
// dcb
DWORD DCBlength; // sizeof(DCB)
DWORD BaudRate; // current baud rate
DWORD fBinary: 1; // binary mode, no EOF check
DWORD fParity: 1; // enable parity checking
DWORD fOutxCtsFlow: 1; // CTS output flow control
DWORD fOutxDsrFlow: 1; // DSR output flow control
DWORD fDtrControl: 2; // DTR flow control type
DWORD fDsrSensitivity: 1; // DSR sensitivity
DWORD fTXContinueOnXoff: 1; // XOFF continues Tx
DWORD fOutX: 1; // XON/XOFF out flow control
DWORD fInX: 1; // XON/XOFF in flow control
DWORD fErrorChar: 1; // enable error replacement
DWORD fNull: 1; // enable null stripping
DWORD fRtsControl: 2; // RTS flow control
DWORD fAbortOnError: 1; // abort reads/writes on error
DWORD fDummy2: 17; // reserved
WORD wReserved; // not currently used
WORD XonLim; // transmit XON threshold
WORD XoffLim; // transmit XOFF threshold
BYTE ByteSize; // number of bits/byte, 4-8
BYTE Parity; // 0-4=no,odd,even,mark,space
BYTE StopBits; // 0,1,2 = 1, 1.5, 2
char XonChar; // Tx and Rx XON character
char XoffChar; // Tx and Rx XOFF character
char ErrorChar; // error replacement character
char EofChar; // end of input character
char EvtChar; // received event character
WORD wReserved1; // reserved; do not use
} DCB;
读写串口
在读写串口之前,还要用PurgeComm()函数清空缓冲区,并用SetCommMask ()函数设置事件掩模来监视指定通信端口上的事件,其原型为:
串口上可能发生的事件如下表所示:
值 事件描述
EV_BREAK A break was detected on input.
EV_CTS The CTS (clear-to-send) signal changed state.
EV_DSR The DSR(data-set-ready) signal changed state.
EV_ERR A line-status error occurred. Line-status errors are CE_FRAME, CE_OVERRUN, and CE_RXPARITY.
EV_RING A ring indicator was detected.
EV_RLSD The RLSD (receive-line-signal-detect) signal changed state.
EV_RXCHAR A character was received and placed in the input buffer.
EV_RXFLAG The event character was received and placed in the input buffer. The event character is specified in the device's DCB structure, which is applied to a serial port by using the SetCommState function.
EV_TXEMPTY The last character in the output buffer was sent.
在设置好事件掩模后,我们就可以利用WaitCommEvent()函数来等待串口上发生事件,其函数原型为:
我们可以在发生事件后,根据相应的事件类型,进行串口的读写操作:
BOOL ReadFile(HANDLE hFile, //标识通信端口的句柄
LPVOID lpBuffer, //输入数据Buffer指针
DWORD nNumberOfBytesToRead, // 需要读取的字节数
LPDWORD lpNumberOfBytesRead, //实际读取的字节数指针
LPOVERLAPPED lpOverlapped //指向overlapped结构
);
BOOL WriteFile(HANDLE hFile, //标识通信端口的句柄
LPCVOID lpBuffer, //输出数据Buffer指针
DWORD nNumberOfBytesToWrite, //需要写的字节数
LPDWORD lpNumberOfBytesWritten, //实际写入的字节数指针
LPOVERLAPPED lpOverlapped //指向overlapped结构
);
2.工程实例
下面我们用第1节所述API实现一个多线程的串口通信程序。这个例子工程(工程名为MultiThreadCom)的界面很简单,如下图所示:
它是一个多线程的应用程序,包括两个工作者线程,分别处理串口1和串口2。为了简化问题,我们让连接两个串口的电缆只包含RX、TX两根连线(即不以硬件控制RS-232,串口上只会发生EV_TXEMPTY、EV_RXCHAR事件)。
在工程实例的BOOL CMultiThreadComApp::InitInstance()函数中,启动并设置COM1和COM2,其源代码为:
BOOL CMultiThreadComApp::InitInstance()
{
AfxEnableControlContainer();
//打开并设置COM1
hComm1=CreateFile("COM1", GENERIC_READ|GENERIC_WRITE, 0, NULL ,OPEN_EXISTING, 0,NULL);
if (hComm1==(HANDLE)-1)
{
AfxMessageBox("打开COM1失败");
return false;
}
else
{
DCB wdcb;
GetCommState (hComm1,&wdcb);
wdcb.BaudRate=9600;
SetCommState (hComm1,&wdcb);
PurgeComm(hComm1,PURGE_TXCLEAR);
}
//打开并设置COM2
hComm2=CreateFile("COM2", GENERIC_READ|GENERIC_WRITE, 0, NULL ,OPEN_EXISTING, 0,NULL);
if (hComm2==(HANDLE)-1)
{
AfxMessageBox("打开COM2失败");
return false;
}
else
{
DCB wdcb;
GetCommState (hComm2,&wdcb);
wdcb.BaudRate=9600;
SetCommState (hComm2,&wdcb);
PurgeComm(hComm2,PURGE_TXCLEAR);
}
CMultiThreadComDlg dlg;
m_pMainWnd = &dlg;
int nResponse = dlg.DoModal();
if (nResponse == IDOK)
{
// TODO: Place code here to handle when the dialog is
// dismissed with OK
}
else if (nResponse == IDCANCEL)
{
// TODO: Place code here to handle when the dialog is
// dismissed with Cancel
}
return FALSE;
}
此后我们在对话框CMultiThreadComDlg的初始化函数OnInitDialog中启动两个分别处理COM1和COM2的线程:
BOOL CMultiThreadComDlg::OnInitDialog()
{
CDialog::OnInitDialog();
// Add "About..." menu item to system menu.
// IDM_ABOUTBOX must be in the system command range.
ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
ASSERT(IDM_ABOUTBOX AppendMenu(MF_SEPARATOR);
pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
}
}
// Set the icon for this dialog. The framework does this automatically
// when the application's main window is not a dialog
SetIcon(m_hIcon, TRUE); // Set big icon
SetIcon(m_hIcon, FALSE); // Set small icon
// TODO: Add extra initialization here
//启动串口1处理线程
DWORD nThreadId1;
hCommThread1 = ::CreateThread((LPSECURITY_ATTRIBUTES)NULL, 0,
(LPTHREAD_START_ROUTINE)Com1ThreadProcess, AfxGetMainWnd()->m_hWnd, 0, &nThreadId1);
if (hCommThread1 == NULL)
{
AfxMessageBox("创建串口1处理线程失败");
return false;
}
//启动串口2处理线程
DWORD nThreadId2;
hCommThread2 = ::CreateThread((LPSECURITY_ATTRIBUTES)NULL, 0,
(LPTHREAD_START_ROUTINE)Com2ThreadProcess, AfxGetMainWnd()->m_hWnd, 0, &nThreadId2);
if (hCommThread2 == NULL)
{
AfxMessageBox("创建串口2处理线程失败");
return false;
}
return TRUE; // return TRUE unless you set the focus to a control
}
两个串口COM1和COM2对应的线程处理函数等待串口上发生事件,并根据事件类型和自身缓冲区是否有数据要发送进行相应的处理,其源代码为:
DWORD WINAPI Com1ThreadProcess(HWND hWnd//主窗口句柄)
{
DWORD wEven;
char str[10]; //读入数据
SetCommMask(hComm1, EV_RXCHAR | EV_TXEMPTY);
while (TRUE)
{
WaitCommEvent(hComm1, &wEven, NULL);
if(wEven = 0)
{
CloseHandle(hCommThread1);
hCommThread1 = NULL;
ExitThread(0);
}
else
{
switch (wEven)
{
case EV_TXEMPTY:
if (wTxPos 内存恢复:
CSerialPort::CSerialPort()
{
m_hComm = NULL;
// initialize overlapped structure members to zero
m_ov.Offset = 0;
m_ov.OffsetHigh = 0;
// create events
m_ov.hEvent = NULL;
m_hWriteEvent = NULL;
m_hShutdownEvent = NULL;
m_szWriteBuffer = NULL;
m_bThreadAlive = FALSE;
}
//
// Delete dynamic memory
//
CSerialPort::~CSerialPort()
{
do
{
SetEvent(m_hShutdownEvent);
}
while (m_bThreadAlive);
TRACE("Thread ended\n");
delete []m_szWriteBuffer;
}
3.2.2核心函数:初始化串口
在初始化串口函数中,将打开串口,设置相关参数,并创建串口相关的用户控制事件,初始化临界区(Critical Section),以成队的EnterCriticalSection()、LeaveCriticalSection()函数进行资源的排它性访问:
BOOL CSerialPort::InitPort(CWnd *pPortOwner,
// the owner (CWnd) of the port (receives message)
UINT portnr, // portnumber (1..4)
UINT baud, // baudrate
char parity, // parity
UINT databits, // databits
UINT stopbits, // stopbits
DWORD dwCommEvents, // EV_RXCHAR, EV_CTS etc
UINT writebuffersize) // size to the writebuffer
{
assert(portnr > 0 && portnr read/write types
0, // comm devices must be opened with exclusive access
NULL, // no security attributes
OPEN_EXISTING, // comm devices must use OPEN_EXISTING
FILE_FLAG_OVERLAPPED, // Async I/O
0); // template must be 0 for comm devices
if (m_hComm == INVALID_HANDLE_VALUE)
{
// port not found
delete []szPort;
delete []szBaud;
return FALSE;
}
// set the timeout values
m_CommTimeouts.ReadIntervalTimeout = 1000;
m_CommTimeouts.ReadTotalTimeoutMultiplier = 1000;
m_CommTimeouts.ReadTotalTimeoutConstant = 1000;
m_CommTimeouts.WriteTotalTimeoutMultiplier = 1000;
m_CommTimeouts.WriteTotalTimeoutConstant = 1000;
// configure
if (SetCommTimeouts(m_hComm, &m_CommTimeouts))
{
if (SetCommMask(m_hComm, dwCommEvents))
{
if (GetCommState(m_hComm, &m_dcb))
{
m_dcb.fRtsControl = RTS_CONTROL_ENABLE; // set RTS bit high!
if (BuildCommDCB(szBaud, &m_dcb))
{
if (SetCommState(m_hComm, &m_dcb))
;
// normal operation... continue
else
ProcessErrorMessage("SetCommState()");
}
else
ProcessErrorMessage("BuildCommDCB()");
}
else
ProcessErrorMessage("GetCommState()");
}
else
ProcessErrorMessage("SetCommMask()");
}
else
ProcessErrorMessage("SetCommTimeouts()");
delete []szPort;
delete []szBaud;
// flush the port
PurgeComm(m_hComm, PURGE_RXCLEAR | PURGE_TXCLEAR | PURGE_RXABORT | PURGE_TXABORT);
// release critical section
LeaveCriticalSection(&m_csCommunicationSync);
TRACE("Initialisation for communicationport %d completed.\nUse Startmonitor to communicate.\n", portnr);
return TRUE;
}
3.3.3核心函数:串口线程控制函数
串口线程处理函数是整个类中最核心的部分,它主要完成两类工作:
(1)利用WaitCommEvent函数对串口上发生的事件进行获取并根据事件的不同类型进行相应的处理;
(2)利用WaitForMultipleObjects函数对串口相关的用户控制事件进行等待并做相应处理。
UINT CSerialPort::CommThread(LPVOID pParam)
{
// Cast the void pointer passed to the thread back to
// a pointer of CSerialPort class
CSerialPort *port = (CSerialPort*)pParam;
// Set the status variable in the dialog class to
// TRUE to indicate the thread is running.
port->m_bThreadAlive = TRUE;
// Misc. variables
DWORD BytesTransfered = 0;
DWORD Event = 0;
DWORD CommEvent = 0;
DWORD dwError = 0;
COMSTAT comstat;
BOOL bResult = TRUE;
// Clear comm buffers at startup
if (port->m_hComm)
// check if the port is opened
PurgeComm(port->m_hComm, PURGE_RXCLEAR | PURGE_TXCLEAR | PURGE_RXABORT | PURGE_TXABORT);
// begin forever loop. This loop will run as long as the thread is alive.
for (;;)
{
// Make a call to WaitCommEvent(). This call will return immediatly
// because our port was created as an async port (FILE_FLAG_OVERLAPPED
// and an m_OverlappedStructerlapped structure specified). This call will cause the
// m_OverlappedStructerlapped element m_OverlappedStruct.hEvent, which is part of the m_hEventArray to
// be placed in a non-signeled state if there are no bytes available to be read,
// or to a signeled state if there are bytes available. If this event handle
// is set to the non-signeled state, it will be set to signeled when a
// character arrives at the port.
// we do this for each port!
bResult = WaitCommEvent(port->m_hComm, &Event, &port->m_ov);
if (!bResult)
{
// If WaitCommEvent() returns FALSE, process the last error to determin
// the reason..
switch (dwError = GetLastError())
{
case ERROR_IO_PENDING:
{
// This is a normal return value if there are no bytes
// to read at the port.
// Do nothing and continue
break;
}
case 87:
{
// Under Windows NT, this value is returned for some reason.
// I have not investigated why, but it is also a valid reply
// Also do nothing and continue.
break;
}
default:
{
// All other error codes indicate a serious error has
// occured. Process this error.
port->ProcessErrorMessage("WaitCommEvent()");
break;
}
}
}
else
{
// If WaitCommEvent() returns TRUE, check to be sure there are
// actually bytes in the buffer to read.
//
// If you are reading more than one byte at a time from the buffer
// (which this program does not do) you will have the situation occur
// where the first byte to arrive will cause the WaitForMultipleObjects()
// function to stop waiting. The WaitForMultipleObjects() function
// resets the event handle in m_OverlappedStruct.hEvent to the non-signelead state
// as it returns.
//
// If in the time between the reset of this event and the call to
// ReadFile() more bytes arrive, the m_OverlappedStruct.hEvent handle will be set again
// to the signeled state. When the call to ReadFile() occurs, it will
// read all of the bytes from the buffer, and the program will
// loop back around to WaitCommEvent().
//
// At this point you will be in the situation where m_OverlappedStruct.hEvent is set,
// but there are no bytes available to read. If you proceed and call
// ReadFile(), it will return immediatly due to the async port setup, but
// GetOverlappedResults() will not return until the next character arrives.
//
// It is not desirable for the GetOverlappedResults() function to be in
// this state. The thread shutdown event (event 0) and the WriteFile()
// event (Event2) will not work if the thread is blocked by GetOverlappedResults().
//
// The solution to this is to check the buffer with a call to ClearCommError().
// This call will reset the event handle, and if there are no bytes to read
// we can loop back through WaitCommEvent() again, then proceed.
// If there are really bytes to read, do nothing and proceed.
bResult = ClearCommError(port->m_hComm, &dwError, &comstat);
if (comstat.cbInQue == 0)
continue;
} // end if bResult
// Main wait function. This function will normally block the thread
// until one of nine events occur that require action.
Event = WaitForMultipleObjects(3, port->m_hEventArray, FALSE, INFINITE);
switch (Event)
{
case 0:
{
// Shutdown event. This is event zero so it will be
// the higest priority and be serviced first.
port->m_bThreadAlive = FALSE;
// Kill this thread. break is not needed, but makes me feel better.
AfxEndThread(100);
break;
}
case 1:
// read event
{
GetCommMask(port->m_hComm, &CommEvent);
if (CommEvent &EV_CTS)
::SendMessage(port->m_pOwner->m_hWnd, WM_COMM_CTS_DETECTED, (WPARAM)0, (LPARAM)port->m_nPortNr);
if (CommEvent &EV_RXFLAG)
::SendMessage(port->m_pOwner->m_hWnd, WM_COMM_RXFLAG_DETECTED,(WPARAM)0, (LPARAM)port->m_nPortNr);
if (CommEvent &EV_BREAK)
::SendMessage(port->m_pOwner->m_hWnd, WM_COMM_BREAK_DETECTED,(WPARAM)0, (LPARAM)port->m_nPortNr);
if (CommEvent &EV_ERR)
::SendMessage(port->m_pOwner->m_hWnd, WM_COMM_ERR_DETECTED, (WPARAM)0, (LPARAM)port->m_nPortNr);
if (CommEvent &EV_RING)
::SendMessage(port->m_pOwner->m_hWnd, WM_COMM_RING_DETECTED,(WPARAM)0, (LPARAM)port->m_nPortNr);
if (CommEvent &EV_RXCHAR)
// Receive character event from port.
ReceiveChar(port, comstat);
break;
}
case 2:
// write event
{
// Write character event from port
WriteChar(port);
break;
}
} // end switch
} // close forever loop
return 0;
}
下列三个函数用于对串口线程进行启动、挂起和恢复:
//
// start comm watching
//
BOOL CSerialPort::StartMonitoring()
{
if (!(m_Thread = AfxBeginThread(CommThread, this)))
return FALSE;
TRACE("Thread started\n");
return TRUE;
}
//
// Restart the comm thread
//
BOOL CSerialPort::RestartMonitoring()
{
TRACE("Thread resumed\n");
m_Thread->ResumeThread();
return TRUE;
}
//
// Suspend the comm thread
//
BOOL CSerialPort::StopMonitoring()
{
TRACE("Thread suspended\n");
m_Thread->SuspendThread();
return TRUE;
}
3.3.4读写串口
下面一组函数是用户对串口进行读写操作的接口:
//
// Write a character.
//
void CSerialPort::WriteChar(CSerialPort *port)
{
BOOL bWrite = TRUE;
BOOL bResult = TRUE;
DWORD BytesSent = 0;
ResetEvent(port->m_hWriteEvent);
// Gain ownership of the critical section
EnterCriticalSection(&port->m_csCommunicationSync);
if (bWrite)
{
// Initailize variables
port->m_ov.Offset = 0;
port->m_ov.OffsetHigh = 0;
// Clear buffer
PurgeComm(port->m_hComm, PURGE_RXCLEAR | PURGE_TXCLEAR | PURGE_RXABORT | PURGE_TXABORT);
bResult = WriteFile(port->m_hComm, // Handle to COMM Port
port->m_szWriteBuffer, // Pointer to message buffer in calling finction
strlen((char*)port->m_szWriteBuffer), // Length of message to send
&BytesSent, // Where to store the number of bytes sent
&port->m_ov); // Overlapped structure
// deal with any error codes
if (!bResult)
{
DWORD dwError = GetLastError();
switch (dwError)
{
case ERROR_IO_PENDING:
{
// continue to GetOverlappedResults()
BytesSent = 0;
bWrite = FALSE;
break;
}
default:
{
// all other error codes
port->ProcessErrorMessage("WriteFile()");
}
}
}
else
{
LeaveCriticalSection(&port->m_csCommunicationSync);
}
} // end if(bWrite)
if (!bWrite)
{
bWrite = TRUE;
bResult = GetOverlappedResult(port->m_hComm, // Handle to COMM port
&port->m_ov, // Overlapped structure
&BytesSent, // Stores number of bytes sent
TRUE); // Wait flag
LeaveCriticalSection(&port->m_csCommunicationSync);
// deal with the error code
if (!bResult)
{
port->ProcessErrorMessage("GetOverlappedResults() in WriteFile()");
}
} // end if (!bWrite)
// Verify that the data size send equals what we tried to send
if (BytesSent != strlen((char*)port->m_szWriteBuffer))
{
TRACE("WARNING: WriteFile() error.. Bytes Sent: %d; Message Length: %d\n",
BytesSent, strlen((char*)port->m_szWriteBuffer));
}
}
//
// Character received. Inform the owner
//
void CSerialPort::ReceiveChar(CSerialPort *port, COMSTAT comstat)
{
BOOL bRead = TRUE;
BOOL bResult = TRUE;
DWORD dwError = 0;
DWORD BytesRead = 0;
unsigned char RXBuff;
for (;;)
{
// Gain ownership of the comm port critical section.
// This process guarantees no other part of this program
// is using the port object.
EnterCriticalSection(&port->m_csCommunicationSync);
// ClearCommError() will update the COMSTAT structure and
// clear any other errors.
bResult = ClearCommError(port->m_hComm, &dwError, &comstat);
LeaveCriticalSection(&port->m_csCommunicationSync);
// start forever loop. I use this type of loop because I
// do not know at runtime how many loops this will have to
// run. My solution is to start a forever loop and to
// break out of it when I have processed all of the
// data available. Be careful with this approach and
// be sure your loop will exit.
// My reasons for this are not as clear in this sample
// as it is in my production code, but I have found this
// solutiion to be the most efficient way to do this.
if (comstat.cbInQue == 0)
{
// break out when all bytes have been read
break;
}
EnterCriticalSection(&port->m_csCommunicationSync);
if (bRead)
{
bResult = ReadFile(port->m_hComm, // Handle to COMM port
&RXBuff, // RX Buffer Pointer
1, // Read one byte
&BytesRead, // Stores number of bytes read
&port->m_ov); // pointer to the m_ov structure
// deal with the error code
if (!bResult)
{
switch (dwError = GetLastError())
{
case ERROR_IO_PENDING:
{
// asynchronous i/o is still in progress
// Proceed on to GetOverlappedResults();
bRead = FALSE;
break;
}
default:
{
// Another error has occured. Process this error.
port->ProcessErrorMessage("ReadFile()");
break;
}
}
}
else
{
// ReadFile() returned complete. It is not necessary to call GetOverlappedResults()
bRead = TRUE;
}
} // close if (bRead)
if (!bRead)
{
bRead = TRUE;
bResult = GetOverlappedResult(port->m_hComm, // Handle to COMM port
&port->m_ov, // Overlapped structure
&BytesRead, // Stores number of bytes read
TRUE); // Wait flag
// deal with the error code
if (!bResult)
{
port->ProcessErrorMessage("GetOverlappedResults() in ReadFile()");
}
} // close if (!bRead)
LeaveCriticalSection(&port->m_csCommunicationSync);
// notify parent that a byte was received
::SendMessage((port->m_pOwner)->m_hWnd, WM_COMM_RXCHAR, (WPARAM)RXBuff,(LPARAM)port->m_nPortNr);
} // end forever loop
}
//
// Write a string to the port
//
void CSerialPort::WriteToPort(char *string)
{
assert(m_hComm != 0);
memset(m_szWriteBuffer, 0, sizeof(m_szWriteBuffer));
strcpy(m_szWriteBuffer, string);
// set event for write
SetEvent(m_hWriteEvent);
}
//
// Return the output buffer size
//
DWORD CSerialPort::GetWriteBufferSize()
{
return m_nWriteBufferSize;
}
3.3.5控制接口
应用程序员使用下列一组public函数可以获取串口的DCB及串口上发生的事件:
3.3.6错误处理
//
// If there is a error, give the right message
//
void CSerialPort::ProcessErrorMessage(char *ErrorText)
{
char *Temp = new char[200];
LPVOID lpMsgBuf;
FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
// Default language
(LPTSTR) &lpMsgBuf, 0, NULL);
sprintf(Temp,
"WARNING: %s Failed with the following error: \n%s\nPort: %d\n", (char*)
ErrorText, lpMsgBuf, m_nPortNr);
MessageBox(NULL, Temp, "Application Error", MB_ICONSTOP);
LocalFree(lpMsgBuf);
delete []Temp;
}
仔细分析Remon Spekreijse的CSerialPort类对我们理解多线程及其同步机制是大有益处的,从 http://codeguru.earthweb.com/network/serialport.sh tml我们可以获取CSerialPort类的 介绍与工程实例。另外,电子工业出版社《Visual C++/Turbo C串口通信编程实践》一书的作者龚建伟也编写了一个使用CSerialPort类的例子,可以从http://www.gjwtech.com /scomm/sc2serialportclass.htm获得详情。
4.多线程网络通信
在网络通信中使用多线程主要有两种途径,即主监控线程和线程池。
4.1主监控线程
这种方式指的是程序中使用一个主线程监控某特定端口,一旦在这个端口上发生连接请求,则主监控线程动态使用CreateThread派生出新的子线程处理该请求。主线程在派生子线程后不再对子线程加以控制和调度,而由子线程独自和客户方发生连接并处理异常。
使用这种方法的优点是:
(1)可以较快地实现原型设计,尤其在用户数目较少、连接保持时间较长时有表现较好;
(2)主线程不与子线程发生通信,在一定程度上减少了系统资源的消耗。
其缺点是:
(1)生成和终止子线程的开销比较大;
(2)对远端用户的控制较弱。
这种多线程方式总的特点是"动态生成,静态调度"。
4.2线程池
这种方式指的是主线程在初始化时静态地生成一定数量的悬挂子线程,放置于线程池中。随后,主线程将对这些悬挂子线程进行动态调度。一旦客户发出连接请求,主线程将从线程池中查找一个悬挂的子线程:
(1)如果找到,主线程将该连接分配给这个被发现的子线程。子线程从主线程处接管该连接,并与用户通信。当连接结束时,该子线程将自动悬挂,并进人线程池等待再次被调度;
(2)如果当前已没有可用的子线程,主线程将通告发起连接的客户。
使用这种方法进行设计的优点是:
(1)主线程可以更好地对派生的子线程进行控制和调度;
(2)对远程用户的监控和管理能力较强。
虽然主线程对子线程的调度要消耗一定的资源,但是与主监控线程方式中派生和终止线程所要耗费的资源相比,要少很多。因此,使用该种方法设计和实现的系统在客户端连接和终止变更频繁时有上佳表现。
这种多线程方式总的特点是"静态生成,动态调度"。
发表评论
-
从PC总线的发展看设备驱动程序的发展
2012-01-20 09:03 741从PC总线的发展看设备驱动程序的发展 2010年06月03日 ... -
进程间通信
2012-01-20 09:03 763进程间通信 2010年09月12 ... -
Xen实践手册
2012-01-20 09:03 853Xen实践手册 2011年03月25日 一、Xen的安装 ... -
.net中加密和解密的实现方法
2012-01-20 09:03 555.net中加密和解密的实现 ... -
深入浅出Win2000 (xp)计算机启动/关机脚本
2012-01-19 14:07 663深入浅出Win2000 (xp)计算 ... -
脚本病毒
2012-01-19 14:07 628脚本病毒 2011年04月30日 脚本病毒通常是Java ... -
使用脚本程序管理Windows网络(9)
2012-01-19 14:07 593使用脚本程序管理Windows ... -
开机快速自动联网(一句话的VBS)
2012-01-19 14:07 559开机快速自动联网(一句话的VBS) 2010年10月20日 ... -
使用脚本程序管理Windows网络(3)
2012-01-19 14:07 545使用脚本程序管理Windows ... -
电脑知识
2012-01-17 03:57 643电脑知识 2010年06月23日 ... -
掌握这些电脑知识,你可以玩的很无耻
2012-01-17 03:57 506掌握这些电脑知识,你可以玩的很无耻 2011年09月05日 ... -
2011-12-4
2012-01-17 03:57 5472011-12-4 2011年12月04日 ... -
第一章第一节计算机语言C语言基础教程
2012-01-17 03:57 613第一章第一节计算机语言C语言基础教程 2012年01月02日 ... -
TOAD快速入门(中文经典)
2012-01-17 03:57 650TOAD快速入门(中文经典) ... -
转:开源C++库比较
2012-01-16 02:46 817转:开源C++库比较 2012年01月13日 原文: ... -
00000000000000000
2012-01-16 02:46 44400000000000000000 2012年01月06日 ... -
Peer-to-Peer Sockets 工程入门
2012-01-16 02:46 495Peer-to-Peer Sockets 工程入 ... -
给大学生学习ARM和FPGA的建议(转)
2012-01-16 02:45 862给大学生学习ARM和FPGA的 ... -
提高 Linux 上 socket 性能
2012-01-16 02:45 572提高 Linux 上 socket 性能 ...
相关推荐
想深入学习多线程的一定要看看的哦
《Windows多线程编程技术与实例》-郝文化-源代码 VC多线程编程.CHM Win32多线程.pdf Win32多线程程序设计(完美版).pdf 多线程编程基础.pdf 深入浅出Win32多线程程序设计.pdf
多线程程序设计实例 第2章 C++的重要性质 类及其成员——谈封装(encapsulation) 基类与派生类:谈继承(Inheritance) this指针 虚拟函数与多态(Polymorphism) 类与对象大解剖 Object slicing与虚拟函数 静态...
7. 深入浅出Win32多线程程序设计 8. C++面向对象程序设计-谭浩强 9. linux从入门到精通教程pdf完整版 10.Linux开发指南 11.Linux网络编程 12.Qt编程及应用经典教程 13.史上最牛的Linux内核学习方法论
多线程程序设计实例 第2章 C++的重要性质 类及其成员——谈封装(encapsulation) 基类与派生类:谈继承(Inheritance) this指针 虚拟函数与多态(Polymorphism) 类与对象大解剖 Object slicing与虚拟函数 静态...
多线程程序设计实例 第2章 C++的重要性质 类及其成员——谈封装(encapsulation) 基类与派生类:谈继承(Inheritance) this指针 虚拟函数与多态(Polymorphism) 类与对象大解剖 Object slicing与虚拟函数 静态...
多线程程序设计实例 第2章 C++的重要性质 类及其成员——谈封装(encapsulation) 基类与派生类:谈继承(Inheritance) this指针 虚拟函数与多态(Polymorphism) 类与对象大解剖 Object slicing与虚拟函数 静态...
这本书适合谁 深入浅出MFC是一本介绍MFC(Microsoft ...14章~16 章是第二版新增内容,主题分别是MFC 多线程程序设计、Custom AppWizard、以及如何使用Component Gallery 提供的ActiveX controls 和components。
这本书适合谁 深入淺出 MFC是一本介绍MFC(Microsoft ...14 章~16 章是第二版新增内容,主题分别是MFC 多线程程序设计、Custom AppWizard、 以及如何使用Component Gallery 提供的ActiveX controls 和components。
如题,非常好的界面设计实例,自绘控件的实现。 Mufan MP3播放的实现。 MYICQ 0.8 alpha1 (仿腾讯QQ整套聊天系统) 老版qq系统的实现。 MyIE3.0浏览器源代码 如题。完整的代码,重载控件实现,非常适合初学者。 ...
本书论述深入浅出、用大量实例详解了微软网络API函数的应用。配套光盘包含了所有实例代码,方便读者使用。本书适合中、高级程序设计人员以及网络设计与管理人员参考。包 装: 平装本书通过应用实例由浅入深引入VC++...
本书论述深入浅出、用大量实例详解了微软网络API函数的应用。配套光盘包含了所有实例代码,方便读者使用。本书适合中、高级程序设计人员以及网络设计与管理人员参考。包 装: 平装本书通过应用实例由浅入深引入VC++...
vc++动态链接库(dll)编程深入浅出 内含开发文档。主要是对动态链接库的教程。 vc++动态链接库编程之DLL典型实例源代码下载 VC++仿Dreamweaver取色器源代码 VC++挂机锁屏系统源程序 VC++建立桌面或开始菜单快捷方式 ...
vc++动态链接库(dll)编程深入浅出 内含开发文档。主要是对动态链接库的教程。 vc++动态链接库编程之DLL典型实例源代码下载 VC++仿Dreamweaver取色器源代码 VC++挂机锁屏系统源程序 VC++建立桌面或开始菜单快捷方式 ...
vc++动态链接库(dll)编程深入浅出 内含开发文档。主要是对动态链接库的教程。 vc++动态链接库编程之DLL典型实例源代码下载 VC++仿Dreamweaver取色器源代码 VC++挂机锁屏系统源程序 VC++建立桌面或开始菜单快捷方式 ...
vc++动态链接库(dll)编程深入浅出 内含开发文档。主要是对动态链接库的教程。 vc++动态链接库编程之DLL典型实例源代码下载 VC++仿Dreamweaver取色器源代码 VC++挂机锁屏系统源程序 VC++建立桌面或开始菜单快捷方式 ...
vc++动态链接库(dll)编程深入浅出 内含开发文档。主要是对动态链接库的教程。 vc++动态链接库编程之DLL典型实例源代码下载 VC++仿Dreamweaver取色器源代码 VC++挂机锁屏系统源程序 VC++建立桌面或开始菜单快捷方式 ...
深入剖析ASP.NET组件设计]一书第三章关于ASP.NET运行原理讲述的补白 asp.net 运行机制初探(httpModule加载) 利用反射来查看对象中的私有变量 关于反射中创建类型实例的两种方法 ASP.Net应用程序的多进程模型 NET委托...