孙鑫VC++视频笔记(15)多线程与聊天室的创建

来源:互联网 发布:银河号事件 知乎 编辑:程序博客网 时间:2024/05/16 18:28

程序、进城和线程
——程序和进程

程序是计算机指令的集合,它以文件的形式存储在磁盘上。
进程:通常被定义为一个正在运行的程序的实例,是一个程序在其自身的地址空间中的一次执行活动。
进程是资源申请、调度和独立运行的单位,因此,它使用系统中的运行资源;而程序不能申请系统资源,不能被系统调度,也不能作为独立的运行的单位,因此,他不占用系统的运行资源。
进程由两个部分组成:
1、操作系统用来管理进程的内核对象。内核对象是操作系统内部分配的一个内存块,内核对象也是系统用来存放关于进程的统计信息的地方。
2、地址空间。它包含所有可执行模块或DLL模块的代码和数据。他还包含动态内存分配的空间。如线程堆栈和堆分配空间。
程序、进程和线程
——进程
进程是不活泼的。进程从来不执行任何东西,它只是线程的容器。若要使进程完成某项操作,它必须拥有一个在它的环境中运行的线程,此线程负责执行包含在进程的地址空间中的代码。
单个进程可能包含若干个线程,这些线程都“同时”执行进程地址空间中的代码。
每个进程至少拥有一个线程,来执行进程的地址空间中的代码。当创建一个进程时,操作系统会自动创建这个进程的一个线程,称为主线程。此后,该线程可以创建其他的线程

 

进程、程序和线程
——线程
线程有两个部分组成:
1。线程的内核对象,操作系统用它来对线程实施管理,内核对象也是系统用来存放线程统计信息的地方。
2。线程堆栈,它用于维护线程在执行代码时需要的所有参数和局部变量。
当创建线程时,系统创建一个线程内核对象。该线程内核对象不是线程本身,而是操作系统用来管理线程的较小的数据结构。可以将线程内核对象视为由关于线程的统计信息组成的一个小型数据结构。
线程总是在某个进程环境中创建。系统从进程的地址空间中分配内存,供线程的堆栈使用。新线程运行的进程环境与创建线程的环境相同。因此,新线程可以访问进程的内核对象的所有句柄、进程中的所有内存和在这个相同的进程中的所有其他线程的堆栈。这使得单个进程中的多个线程确实能够非常容易的互相通信。
线程只有一个内核对象和一个堆栈,保留的记录很少,因此所需要的内存也很少。
因为线程需要的开销比进程少,因此在编程中经常采用多线程来解决编程问题,而尽量避免创建新的进程。
程序、进程和线程
——线程运行
操作系统为每一个运行线程安排一定的CPU时间——时间片。系统通过一种循环的方式为线程提供时间片,线程在自己的时间内运行,因时间片相当短,因此,给用户的感觉,就好像线程是同时进行的一样。
如果计算机拥有多个CPU,线程就能真正意义上运行了
多线程共享同一个进程的地址空间

创建线程使用CreateThread
The CreateThread function creates a thread to execute within the address space of the calling process.

HANDLE CreateThread(
  LPSECURITY_ATTRIBUTES lpThreadAttributes,  // pointer to security attributes
  DWORD dwStackSize,                         // initial thread stack size
  LPTHREAD_START_ROUTINE lpStartAddress,     // pointer to thread function
  LPVOID lpParameter,                        // argument for new thread
  DWORD dwCreationFlags,                     // creation flags
  LPDWORD lpThreadId                         // pointer to receive thread ID
);
此函数返回一个线程的句柄

互斥对象(mutex)属于内核对象,它能够确保线程拥有对单个资源的互斥访问权。
互斥对象包含一个使用数量,一个线程和一个计数器。
ID用于标示系统中的那个线程当前拥有互斥对象,计数器用于指明该县城拥有互斥对象的次数。


以下是存在漏洞的火车站售票系统代码:
#include "windows.h"
#include "iostream.h"

DWORD WINAPI FunProc1(LPVOID lpParameter);
DWORD WINAPI FunProc2(LPVOID lpParameter);

int index=0;
int ticket=100;
void main()
{
 HANDLE hThread1;
 HANDLE hThread2;
 hThread=CreateThread(NULL,0,FunProc1,NULL,0,NULL);
 hThread=CreateThread(NULL,0,FunProc2,NULL,0,NULL);

 CloseHandle(hThread1);  
/*关闭线程句柄,线程仍运行;关闭句柄时,系统会递减新线程内核对象使用计数,线程运行一次,也会递减内核对象使用计数,使用计数为零时,系统释放内核对象。如果不在主程序中关闭句柄,使用计数无法为零,系统不会释放内核对象,造成浪费。只有在程序运行结束时,系统才会清理残留内核对象*/
 CloseHandle(hThread2)
 Sleep(4000);
}

DWORD WINAPI FunProc1(LPVOID lpParameter)
{
 while(TRUE)
 {
  if(ticket>0)
  {
   cout<<"ticket 1:"<<ticket--<<endl;
  }
  else
   break;
 }
 return 0;
}

DWORD WINAPI FunProc2(LPVOID lpParameter)
{
 while(TRUE)
 {
  if(ticket>0)
  {
   cout<<"ticket 2:"<<ticket--<<endl;
  }
  else
   break;
 }
 return 0;
}

在极巧合的情况下,有可能会出现卖出第零张票的情况。这是因为ticket=1时,有可能在当线程1的时间片用完时,它正好刚执行完if语句的条件语句,接下来线程2开始执行,使ticket为0。此时线程1从断点处恢复运行,打印0。解决办法是增加互斥条件。

HANDLE CreateMutex(
  LPSECURITY_ATTRIBUTES lpMutexAttributes,
                       // pointer to security attributes
  BOOL bInitialOwner,  // flag for initial ownership 为false表示无线程占用
  LPCTSTR lpName       // pointer to mutex-object name
);

DWORD WaitForSingleObject(
  HANDLE hHandle,        // handle to object to wait for
  DWORD dwMilliseconds   // time-out interval in milliseconds
);

BOOL ReleaseMutex(
  HANDLE hMutex   // handle to mutex object
);


 while(TRUE)
 {
  WaitForSingleObject(hMutex,INFINITE);
  if(ticket>0)
  {
   cout<<"ticket 2:"<<ticket--<<endl;
  }
  else
   break;
  ReleaseMutex(hMutex);  //不管是主线程还是兄弟线程,谁拥有,谁释放。
 }

互斥对象请求几次就要释放几次;
如 hMutex=CreateMutex(NULL,TRUE,NULL);
 WaitForSingleObject(hMutex,INFINITE);
 ReleaseMutex(hMutex);  //第一次释放,仍有信号
 ReleaseMutex(hMutex);  //再一次释放,使信号为零

如果操作系统发现线程已经正常终止,它会自动把线程申请的互斥对象信号量设为0

通过命名互斥来保证应用程序只能同时运行一个实例。如下:
 hMutex=CreateMutex(NULL,TRUE,"tickets");  //第三个参数必须赋值
 if(hMutex)
 {
  if(ERROR_ALREADY_EXISTS==GetLastError())
//ERROR_ALREADY_EXISTS :Cannot create a file when that file already exists.
  {
   cout<<"There can run only one instance!"<<endl;
   return;
  }
 }

stdafx.h是一个预编译头文件,在这个头文件中包含了MFC运行必要的头文件和标准组件:
#include <afxwin.h>         // MFC core and standard components
#include <afxext.h>         // MFC extensions
#include <afxdisp.h>        // MFC Automation classes
#include <afxdtctl.h>  // MFC support for Internet Explorer 4 Common Controls


 if(!AfxSocketInit())//用来加载套接字库,进行版本协商,默认加载1.1版本套接字库
//还可以确保程序终止之前,调用::WSACleanup,终止套接字库的使用
 {
  AfxMessageBox("加载套接字失败!");
  return FALSE;
 }
需要加载头文件#include "AfxSock.h",旧版本MSDN中没有写,记住。

初始化套接字:
BOOL CChatApp::InitInstance()
{
 if(!AfxSocketInit())//用来加载套接字库,进行版本协商
 {
  AfxMessageBox("加载套接字失败!");
  return FALSE;
 }

BOOL CChatDlg::InitSocket()
{
 m_socket=socket(AF_INET,SOCK_DGRAM,0);
 if(INVALID_SOCKET==m_socket)
 {
  AfxMessageBox("套接字创建失败");
  return FALSE;  
 }
 SOCKADDR_IN addrSock;
 addrSock.sin_family=AF_INET;
 addrSock.sin_port=htons(6000);
 addrSock.sin_addr.S_un.S_addr=htonl(INADDR_ANY);

 int retval;
 retval=bind(m_socket,(SOCKADDR*)&addrSock,sizeof(SOCKADDR));
 if(SOCKET_ERROR==retval)
 {
  closesocket(m_socket);
  AfxMessageBox("绑定失败");
  return FALSE;
 }
 return TRUE;
}
retval=bind(m_socket,(SOCKADDR*)&addrSock,sizeof(SOCKADDR));别忘记加&号
addrSock.sin_addr.S_un.S_addr=htonl(INADDR_ANY);此处INADDR_ANY表示接收任意IP地址。

之后在BOOL CChatDlg::OnInitDialog()中调用BOOL CChatDlg::InitSocket(),这样初始化的工作才算完成。

所有与窗口有关的类都有一个数据成员m_hWnd,它保存了和这个类相关的窗口句柄

要调用类的成员函数,必须先产生一个对象,然后通过对象调用
如果把类中的成员函数声明为static,它就不属于对象,而直接属于类,然后就可以直接调用了。

在BOOL CChatDlg::OnInitDialog()中建立线程,首先要构造结构体以给CreateThread()传递两个参数:
struct RECVPARAM
{
 SOCKET sock;
 HWND hwnd;
};

建立线程:

RECVPARAM *pRecvParam=new RECVPARAM;
pRecvParam->hwnd=m_hWnd;
pRecvParam->sock=m_socket;
HANDLE hThread=CreateThread(NULL,0,RecvProc,(LPVOID)pRecvParam,0,NULL);
CloseHandle(hThread);

接下来创建线程函数,代码如下:
DWORD WINAPI CChatDlg::RecvProc(LPVOID lpParameter)
{
 SOCKET sock=((RECVPARAM*)lpParameter)->sock;
 HWND hwnd=((RECVPARAM*)lpParameter)->hwnd;

 SOCKADDR_IN addrFrom;
 int len=sizeof(SOCKADDR);

 char recvBuf[200];
 char tempBuf[400];

 int retval;
 while(TRUE)
 {
  retval=recvfrom(sock,recvBuf,200,NULL,(SOCKADDR*)&addrFrom,&len);
  if(SOCKET_ERROR==retval)
   break;
  sprintf(tempBuf,"%s said:%s",inet_ntoa(addrFrom.sin_addr),recvBuf);
  ::PostMessage(hwnd,WM_RECVDATA,0,(LPARAM)tempBuf);
 //通过发送消息方式把数据传给对话框,
 //传递一个自定义消息WM_RECVDATA
 //将数据通过lParam进行传递。
 }
 
 return 0;
}
在ChatDlg.h中要把线程函数设为static,另一种方法是把线程函数定义为全局线程函数。

实现消息响应函数步骤:
定义消息WM_RECVDATA:#define WM_RECVDATA  WM_USER+1
做一个消息相应函数原形的声明:
 afx_msg void OnRecvData(WPARAM wParam,LPARAM lParam);
定义消息映射
 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);
}

最后为按钮添加消息实现代码:
void CChatDlg::OnSend()
{
 DWORD dwIP;
 ((CIPAddressCtrl*)GetDlgItem(IDC_IPADDRESS))->GetAddress(dwIP);
 
 SOCKADDR_IN addrTo;
 addrTo.sin_addr.S_un.S_addr=htonl(dwIP);
 addrTo.sin_family=AF_INET;
 addrTo.sin_port=htons(6000);

 CString strSend;
 GetDlgItemText(IDC_EDIT_SEND,strSend);

 sendto(m_socket,strSend,strSend.GetLength()+1,0,
  (SOCKADDR*)&addrTo,sizeof(SOCKADDR));
 SetDlgItemText(IDC_EDIT_SEND,"");  //把发送框设为空白
}

为使文本框支持多行,在属性里选中Multiline。如果我们要在输入结束后以回车发送数据,只需把发送按钮的default button按钮选中即可