学习笔记:第15课 多线程和聊天室程序

来源:互联网 发布:双拼域名购买 编辑:程序博客网 时间:2024/06/17 01:59
 
      内核对象:操作系统分配的内存块,该内存块是一种数据结构,这个数据结构只能被内核访问。
      进程:通常被定义为一个正在运行的程序的实例,是一个程序在其自身的地址空间中的一次执行活动。
      进程是资源申请、调度和独立运行的单位,因此,它使用系统中的运行资源;而程序不能申请系统资源,不能被系统调度,也不能作为独立运行的单位,因此,它不占用系统的运行资源。
      进程由两个部分组成:
     1、操作系统用来管理进程的内核对象。内核对象也是系统用来存放关于进程的统计信息的地方。
2、地址空间。它包含所有可执行模块或DLL模块的代码和数据。它还包含动态内存分配的空间。如线程堆栈和堆分配空间。
      进程是不活泼的。进程从来不执行任何东西,它只是线程的容器。若要使进程完成某项操作,它必须拥有一个在它的环境中运行的线程,此线程负责执行包含在进程的地址空间中的代码。
      每个进程至少拥有一个线程,来执行进程的地址空间中的代码。当创建一个进程时,操作系统会自动创建这个进程的第一个线程,称为主线程。此后,该线程可以创建其他的线程。
      系统赋予每个进程独立的虚拟地址空间。对于32位进程来说,这个地址空间是4GB。
      每个进程有它自己的私有地址空间。进程A可能有一个存放在它的地址空间中的数据结构,地址是0x12345678,而进程B则有一个完全不同的数据结构存放在它的地址空间中,地址是0x12345678。当进程A中运行的线程访问地址为0x12345678的内存时,这些线程访问的是进程A的数据结构。当进程B中运行的线程访问地址为0x12345678的内存时,这些线程访问的是进程B的数据结构。进程A中运行的线程不能访问进程B的地址空间中的数据结构,反之亦然。
      4GB是虚拟的地址空间,只是内存地址的一个范围。在你能成功地访问数据而不会出现非法访问之前,必须赋予物理存储器,或者将物理存储器映射到各个部分的地址空间。
      4GB虚拟地址空间中,2GB是内核方式分区,供内核代码、设备驱动程序、设备I/O高速缓冲、非页面内存池的分配和进程页面表等使用,而用户方式分区使用的地址空间约为2GB,这个分区是进程的私有地址空间所在的地方。一个进程不能读取、写入、或者以任何方式访问驻留在该分区中的另一个进程的数据。对于所有应用程序来说,该分区是维护进程的大部分数据的地方。
      线程由两个部分组成:
     1、线程的内核对象,操作系统用它来对线程实施管理。内核对象也是系统用来存放线程统计信息的地方。
     2、线程堆栈,它用于维护线程在执行代码时需要的所有参数和局部变量。
                  当创建线程时,系统创建一个线程内核对象。该线程内核对象不是线程本身,而是操作系统用来管理线程的较小的数据结构。可以将线程内核对象视为由关于线程的统计信息组成的一个小型数据结构。
      线程总是在某个进程环境中创建。系统从进程的地址空间中分配内存,供线程的堆栈使用。新线程运行的进程环境与创建线程的环境相同。因此,新线程可以访问进程的内核对象的所有句柄、进程中的所有内存和在这个相同的进程中的所有其他线程的堆栈。这使得单个进程中的多个线程确实能够非常容易地互相通信。
      线程只有一个内核对象和一个堆栈,保留的记录很少,因此所需要的内存也很少。
      因为线程需要的开销比进程少,因此在编程中经常采用多线程来解决编程问题,而尽量避免创建新的进程。
      操作系统为每一个运行线程安排一定的CPU时间 —— 时间片。系统通过一种循环的方式为线程提供时间片,线程在自己的时间内运行,因时间片相当短,因此,给用户的感觉,就好像线程是同时运行的一样。
      如果计算机拥有多个CPU,线程就能真正意义上同时运行了。
      创建线程使用CreateThread,此函数返回一个线程的句柄
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和一个计数器。
      ID用于标识系统中的哪个线程当前拥有互斥对象,计数器用于指明该线程拥有互斥对象的次数。
◇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
);
//返回WAIT_OBJECT_0表示有信号状态
BOOL ReleaseMutex(
  HANDLE hMutex   // handle to mutex object
);
 
 
◇以下是存在漏洞的火车站售票系统代码:
#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;
}
 
◇互斥对象请求几次就要释放几次;
如 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())
         {
              cout<<"only instance can run!"<<endl;
              return;
         }
     }
 
      聊天室程序的编写:
      AfxSocketInit用于加载套接字库,来版本协商。
      初期化套界字:
BOOL CChatDlg::InitSocket()
{
     m_socket=socket(AF_INET,SOCK_DGRAM,0);
     if(INVALID_SOCKET==m_socket)
     {
         MessageBox("套接字创建失败!");
         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);
         MessageBox("绑定失败!");
         return FALSE;
     }
     return TRUE;
}
然后创建一个结构体,在该聊天室启动的时候调用
struct RECVPARAM
{
     SOCKET sock;
     HWND hwnd;
};
调用:
     InitSocket();
     RECVPARAM *pRecvParam=new RECVPARAM;
     pRecvParam->sock=m_socket;
     pRecvParam->hwnd=m_hWnd;
// RecvProc线程函数如果是类的成员函数,那么必须定义STATIC类型
     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;
     delete lpParameter;    //视频讲述时,遗忘了释放内存的操作。sunxin
 
     SOCKADDR_IN addrFrom;
     int len=sizeof(SOCKADDR);
 
     char recvBuf[200];
     char tempBuf[300];
     int retval;
     while(TRUE)
     {
         retval=recvfrom(sock,recvBuf,200,0,(SOCKADDR*)&addrFrom,&len);
         if(SOCKET_ERROR==retval)
              break;
         sprintf(tempBuf,"%s说: %s",inet_ntoa(addrFrom.sin_addr),recvBuf);
         //将消息传给对话框,在头文件中也做相应的对应,#define   WM_RECVDATA        WM_USER+1
         ::PostMessage(hwnd,WM_RECVDATA,0,(LPARAM)tempBuf);
     }
     return 0;
}
然后添加消息响应函数,和消息映射
afx_msg void OnRecvData(WPARAM wParam,LPARAM lParam);
BEGIN_MESSAGE_MAP(CChatDlg, CDialog)
ON_MESSAGE(WM_RECVDATA,OnRecvData)//这个就是消息影射
END_MESSAGE_MAP()
最后就是消息响应函数的实现:
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::OnBtnSend()
{
     // TODO: Add your control notification handler code here
     DWORD dwIP;
     //得到IP地址(主机字节序的地址)
     ((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,"");
}
 
原创粉丝点击