第15课 多线程与网络编程

一 Win32 提供了一系列的 API函数来完成线程的创建、挂起、恢复、终结以及通信等工作。下面将选取其中的一些重要函数进行说明。
1、HANDLE CreateThread(LPSECURITY_ATTRIBUTES lpThreadAttributes,
DWORD dwStackSize,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter,
DWORD dwCreationFlags,
LPDWORD lpThreadId);
该函数在其调用进程的进程空间里创建一个新的线程,并返回已建线程的句柄,其中各参数说明如下:
· lpThreadAttributes:指向一个 SECURITY_ATTRIBUTES  结构的指针,该结构决定了线程的安全属性,一般置为 NULL;
·  dwStackSize:指定了线程的堆栈深度,一般都设置为0;
·  lpStartAddress:表示新线程开始执行时代码所在函数的地址,即线程的起始地址。一般情况为(LPTHREAD_START_ROUTINE)ThreadFunc,ThreadFunc 是线程函数名;
·  lpParameter:指定了线程执行时传送给线程的32位参数,即线程函数的参数;
·  dwCreationFlags:控制线程创建的附加标志,可以取两种值。如果该参数为0,线程在被创建后就会立即开始执行;如果该参数为CREATE_SUSPENDED,则系统产生线程后,该线程处于挂起状态,并不马上执行,直至函数ResumeThread被调用;
·  lpThreadId:该参数返回所创建线程的ID;
如果创建成功则返回线程的句柄,否则返回 NULL。

二、MFC对多线程编程的支持
MFC中有两类线程,分别称之为工作者线程和用户界面线程。二者的主要区别在于工作者线程没有消息循环,而用户界面线程有自己的消息队列和消息循环。
工作者线程没有消息机制,通常用来执行后台计算和维护任务,如冗长的计算过程,打印机的后台打印等。用户界面线程一般用于处理独立于其他线程执行之外的用户输入,响应用户及系统所产生的事件和消息等。但对于 Win32的 API编程而言,这两种线程是没有区别的,它们都只需线程的启动地址即可启动线程来执行任务。
在 MFC中,一般用全局函数 AfxBeginThread()来创建并初始化一个线程的运行,该函数有两种重载形式,分别用于创建工作者线程和用户界面线程。两种重载函数原型和参数分别说明如下:
 
(1) CWinThread* AfxBeginThread(AFX_THREADPROC pfnThreadProc,
LPVOID pParam,
nPriority=THREAD_PRIORITY_NORMAL,
UINT nStackSize=0,
DWORD dwCreateFlags=0,
LPSECURITY_ATTRIBUTES lpSecurityAttrs=NULL);
PfnThreadProc:指向工作者线程的执行函数的指针,线程函数原型必须声明如下:UINT ExecutingFunction(LPVOID pParam);
请注意,ExecutingFunction()应返回一个UINT类型的值,用以指明该函数结束的原因。一般情况下,返回0表明执行成功。
·  pParam:传递给线程函数的一个32位参数,执行函数将用某种方式解释该值。它可以是数值,或是指向一个结构的指针,甚至可以被忽略;
·  nPriority:线程的优先级。如果为0,则线程与其父线程具有相同的优先级;
·  nStackSize:线程为自己分配堆栈的大小,其单位为字节。如果nStackSize被设为0,则线程的堆栈被设置成与父线程堆栈相同大小;
·  dwCreateFlags:如果为0,则线程在创建后立刻开始执行。如果为CREATE_SUSPEND,则线程在创建后立刻被挂起;
·  lpSecurityAttrs:线程的安全属性指针,一般为NULL;

(2) CWinThread* AfxBeginThread(CRuntimeClass* pThreadClass,
int nPriority=THREAD_PRIORITY_NORMAL,
UINT nStackSize=0,
DWORD dwCreateFlags=0,
LPSECURITY_ATTRIBUTES lpSecurityAttrs=NULL);
pThreadClass 是指向 CWinThread 的一个导出类的运行时类对象的指针,该导出类定义了被创建的用户界面线程的启动、退出等;其它参数的意义同形式1。使用函数的这个原型生成的线程也有消息机制,在以后的例子中我们将发现
同主线程的机制几乎一样。
下面我们对CWinThread类的数据成员及常用函数进行简要说明。
·  m_hThread:当前线程的句柄;
·  m_nThreadID:当前线程的ID;
·  m_pMainWnd:指向应用程序主窗口的指针
BOOL CWinThread::CreateThread(DWORD dwCreateFlags=0,
UINT nStackSize=0,
LPSECURITY_ATTRIBUTES lpSecurityAttrs=NULL);
该函数中的 dwCreateFlags、nStackSize、lpSecurityAttrs参数和 API函数 CreateThread中的对应参数有相同含义,该函数执行成功,返回非 0值,否则返回 0。
一般情况下,调用 AfxBeginThread()来一次性地创建并启动一个线程,但是也可以通过两步法来创建线程:首先创建 CWinThread类的一个对象,然后调用该对象的成员函数 CreateThread()来启动该线程。

1. 多线程介绍,略
保证应用程序只有一个实例运行:
     hMutex=CreateMutex(NULL,FALSE,"tickets");
     if(hMutex)
     {
          if(ERROR_ALREADY_EXISTS==GetLastError())
          {
               cout<<"only instance can run!"<<endl;
               return;
          }
     }

2. 一个简单的多线程程序
MSND 中参数 [in]  [out]的含义要注意
#include <windows.h>
#include <iostream.h>DWORD WINAPI Fun1Proc(
  LPVOID lpParameter   // thread data
);DWORD WINAPI Fun2Proc(
  LPVOID lpParameter   // thread data
);
int index=0;
int tickets=100;
HANDLE hMutex;  互斥对象的句柄
void main()
{
 HANDLE hThread1;
 HANDLE hThread2;
 hThread1=CreateThread(NULL,0,Fun1Proc,NULL,0,NULL);  创建线程 1
 hThread2=CreateThread(NULL,0,Fun2Proc,NULL,0,NULL);  创建线程 2
 CloseHandle(hThread1);  关闭线程的句柄,为什么要关闭?它将线程的使用计数减 1
 CloseHandle(hThread2);  这样当线程结束时,线程内核对象被释放,否则只有当进程结束,才释放线程的内核对象
 /*while(index++<1000)
  cout<<"main thread is running"<<endl;*/
 //hMutex=CreateMutex(NULL,TRUE,NULL);  将第二个参数设为 true后,互斥对象的计数加1
 hMutex=CreateMutex(NULL,TRUE,"tickets");  此段代码可以让系统只一份实例在运行!
 if(hMutex)
 {
  if(ERROR_ALREADY_EXISTS==GetLastError())
  {
   cout<<"only instance can run!"<<endl;
   return;
  }
 }
 WaitForSingleObject(hMutex,INFINITE);  此代码也将互斥对象的计数加 1
 ReleaseMutex(hMutex);  所以要释放两次互斥对象
 ReleaseMutex(hMutex);
 Sleep(4000);  睡眠4000毫秒
// Sleep(10);
}DWORD WINAPI Fun1Proc(
  LPVOID lpParameter   // thread data
)
{
 /*while(index++<1000)
  cout<<"thread1 is running"<<endl;*/
 
 /*while(TRUE)
 {
  //ReleaseMutex(hMutex);
  WaitForSingleObject(hMutex,INFINITE);  等待互斥对象的到来,到来后将互斥对象的计数加 1
  if(tickets>0)
  {
   Sleep(1);
   cout<<"thread1 sell ticket : "<<tickets--<<endl;
  }
  else
   break;
  ReleaseMutex(hMutex);  释放互斥对象,将其计数减 1,这样可以保证,这两句话之间 的代码!的执行连续性!
 }*/ WaitForSingleObject(hMutex,INFINITE);
 cout<<"thread1 is running"<<endl;
 return 0;
}DWORD WINAPI Fun2Proc(
  LPVOID lpParameter   // thread data
)
{
 
 /*while(TRUE)
 {
  //ReleaseMutex(hMutex);
  WaitForSingleObject(hMutex,INFINITE);
  if(tickets>0)
  {
   Sleep(1);
   cout<<"thread2 sell ticket : "<<tickets--<<endl;
  }
  else
   break;
  ReleaseMutex(hMutex);
 }*/
 WaitForSingleObject(hMutex,INFINITE);
 cout<<"thread2 is running"<<endl;
 return 0;
}

3. 多线程聊天程序
  1. 加载套接字库在 InitInstance() 中,调用  AfxSocketInit(),此时可以不加载库文件,但要加入 Afxsock.h"头文件
  2. CChatDlg  中创建成员变量 m_socket,然后增加一个成员函数, IniSocket(),在其中完成m_socket的初始化和绑定。在 OnInitDialog中调用InitSocket 完成初始化工作。
  3. 定义一个结构体,包含两个参数,  sockhwnd ,在OnInitDialog()中初始化这个结构体的对象。
  4. 创建一个线程, CreateThread(), 须将线程函数  RecvProc定义为静态的或者全局函数。
    ::PostMessage()  完成将收到的数据发送给对话框。用自定义的消息,自定义的消息如何写?以前说过,参考下面的代码。注意要将 EDitBoxMultiLine 属性选上。  
     ChatDlg.h #define WM_RECVDATA  WM_USER+1
 afx_msg void OnRecvData(WPARAM wParam,LPARAM lParam);
     ChatDlg.cpp
 ON_MESSAGE(WM_RECVDATA,OnRecvData)
然后实现这个函数
void CChatDlg::OnRecvData(WPARAM wParam,LPARAM lParam)
{
 CString str=(char*)lParam;
 CString strTemp;
 GetDlgItemText(IDC_EDIT_RECV,strTemp);
 str+="\r\n";
 str+=strTemp;
 SetDlgItemText(IDC_EDIT_RECV,str);
}
      最后在DWORD WINAPI CChatDlg::RecvProc(LPVOID lpParameter)
中调用  ::PostMessage(hwnd,WM_RECVDATA,0,(LPARAM)tempBuf);
// 不能用 SendMessage()
  
  4.  对发送按纽的响应代码:
void CChatDlg::OnBtnSend()
{
 // TOD Add your control notification handler code here
 DWORD dwIP;
 ((CIPAddressCtrl*)GetDlgItem(IDC_IPADDRESS1))->GetAddress(dwIP); SOCKADDR_IN addrTo;
 addrTo.sin_family=AF_INET;
 addrTo.sin_port=htons(6000);
 addrTo.sin_addr.S_un.S_addr=htonl(dwIP); CString strSend;
 GetDlgItemText(IDC_EDIT_SEND,strSend);
 sendto(m_socket,strSend,strSend.GetLength()+1,0,
  (SOCKADDR*)&addrTo,sizeof(SOCKADDR));
 SetDlgItemText(IDC_EDIT_SEND,"");
}

猜你喜欢

转载自blog.csdn.net/zhang_zxk/article/details/52401940