《VC++深入详解》学习笔记 第十六章 线程同步与异步套接字编程

1.事件对象

1)事件对象也属于内核对象,它包含以下三个成员

          1.使用计数

         2.用于指明该事件是一个自动重置事件还是一个人工重置事件的布尔值

         3.用于指明该事件处于已通知状态还是未通知状态的布尔值

2)当人工重置事件对象得到通知时,等待该事件对象的所有线程均变为可调度线程,当一个自动重置事件对象得到通知时,等待该事件对象的线程只有一个变为可调度线程

3)创建事件对象:CreateEvent函数(用来创建或打开一个命名或匿名的事件对象)

     函数声明:HANDLE CreateEvent

          LPSECURITY_ATTRIBUTES  lpEventAttributes, //安全性

                 BOOL  bManualReset, //TRUE:人工重置对象FALSE:自动重置对象

如果是人工重置事件对象,当线程等待到该对象的所有权之后,需要调用ResetEvent函数手动将该事件对象设为无信号状态,如果是自动重置对象,系统会自动设为无信号状态

BOOL  bInitialState,//指定事件对象的初始状态,TRUE:有信号

                  LPCTSTR  lpName//事件对象名,NULL:匿名事件对象

);

4)设置事件对象状态:SetEvent,将指定的事件对象设置为有信号状态

      函数声明:BOOL SetEvent(HANDLE hEvent);

      重置事件对象状态:ResetEvent,将指定的事件对象设置为无信号状态

      函数声明:BOOL ResetEvent(HANDLE hEvent);

5WaitForSingleObject()函数:请求事件对象

6)当人工重置事件对象得到通知时,等待该事件对象的所有线程都变成可调度线程,当一个自动重置的事件对象得到通知时,等待该事件对象的线程中只有一个线程变为可调度线程,同时操作系统会将该事件对象设置为无信号状态。这样,当对所保护的代码执行完成后,需要调用SetEvent函数将该事件对象设置为有信号状态。而人工重置事件对象,在一个线程得到该事件对象后,操作系统并不会将该事件设置为无信号状态,除非显示调用ResetEvent函数将其设置为无信号状态,否则该对象会一直是有信号状态

7)保证应用程序只有一个实例运行:命名的事件对象,判断返回值

2.关键代码区(临界区)

1)关键代码段,也称为临界区,工作在用户方式下,它是指一个小代码段,在代码段执行之前,它必须独占对某些资源的访问权。通常把多线程中访问同一资源的部分代码当作关键代码段

2)在进入关键代码段之前,首先需要初始化一个这样的代码段,这可以调用函数InitializeCriticalSection实现

     函数声明:void InitializeCriticalSection(LPCRITICAL_SECTION lpCriticalSection);

//该函数的参数是一个指向结构体的指针,该参数是out类型,即作为返回值使用的,因此,事先要构造一个该结构体类型的对象

3)想要进入关键代码段,首先需要调用EnterCriticalSection函数,以获得指定的临界区对象的所有权(若无法得到所有权,该函数会一直等待,从而导致线程暂停运行)

4)使用完后需要调用LeaveCriticalSection函数,释放指定的临界区对象的所有权

5)当不再需要临界区对象后,要调用DeleteCriticalSection函数释放该对象,该函数将释放一个没有被任何线程拥有的临界区对象的所有资源

3.线程死锁

4.互斥对象、事件对象与关键代码段(临界区)的比较

1)互斥对象和事件对象都属于内核对象,利用内核对象进行线程同步时,速度较慢,但利用互斥对象和事件对象这样的内核对象,可以在多个进程中的各个线程间进行同步

2)关键代码区在用户方式下,同步速度较快,但在使用关键代码段时,很容易进入死锁状态,因为在等待进入关键代码段时无法设定超时值

3)通常,在编写多线程程序并需要实现线程同步时,首选关键代码段,由于它的使用比较简单,但是要注意死锁发生

5.基于消息的异步套接字

1Windows套接字在两种模式下执行I/O操作:阻塞模式和非阻塞模式,在阻塞模式下,在I/O操作完成前,执行操作的Winsock函数会一直等待下去,不会立即返回(也就是不会把控制权交换给程序),而在非阻塞模式下,Winsock函数无论如何都回立即返回,在该函数执行的操作完成之后,系统会采用某种方式将操作结果通知给调用线程,后者根据通知信息可以判断该操作是正常完成了,还是出现错误了。

2Windows Sockets对网络事件采用了基于消息的异步存取策略Windows Sockets异步选择函数WSAAsyncSelect提供了消息机制的网络事件选择,当使用它登记的网络事件发生时,Windows应用程序相应的窗口函数将会收到一个消息,消息中指示了发生的网络事件,以及与该事件相关的一些信息

      因此,可以针对不同的网络事件进行登记,例如:如果登记一个网络读取事件,一旦有数据到来,就会触发这个事件,操作系统就会通过一个消息来通知调用线程,后者就可以在相应的消息响应函数中接受这个数据。因为是在该数据到来之后,操作系统发出的通知,所以这时肯定能够接受到数据,采用异步套接字能够有效地提高应用程序的性能

3)相关函数说明

     1.WSAAsyncSelect函数

    函数原型:int WSAAsyncSelect(SOCKET s,HWND hWnd,unsigned int wMsg,long lEvent)

//s:请求网络事件通知的套接字描述符      hWnd:网络事件发生时接收消息的窗口句柄

//wMsg:指定网络事件发生时窗口将接收到的消息  lEvent:指定应用程序感兴趣的网络事件(网络事件有:FD_READFD_WRITEFD_ACCEPTFD_CONNECTFD_CLOSE等)

//该函数为指定的套接字请求基于Windows消息的网络事件通知,并自动将该套接字设置为非阻塞模式

         2.WSAEnumProtocols函数

        函数原型:int WSAEnumProtocols(LPINT lpiPtotocols,

LPESAPROTOCOL_INFO lpProtocolBuffer,ILPDWORD lpdwBufferLength);

//该函数可以获得系统中安装的网络协议的相关信息

// lpiPtotocols一个以NULL结尾的协议标识号数组,这个参数可选,如果lpiPtotocolsNULL,则该函数返回所有可用协议的信息,否则,只返回数组中列出的协议信息

// lpProtocolBufferout类型的参数,作为返回值使用,用来存放一个指定协议的完整信息

// lpdwBufferLength,指定缓冲区长度

         3.WSAStartup函数

        函数原型:int WSAStarup(WORD wVersionRequested,LPWSADATA lpWSAData);

//该函数将初始化进程使用的WS2_32.DLL

//第一个参数:调用进程可以使用的Windows Sockets支持的最高版本

//第二个参数:out类型的参数,作为返回值使用,用来接受Windows Sockets实现的细节

         4.WSACleanup函数

        函数原型:int WSACleanup(void);//该函数将终止程序对套接字库的使用

         5.WSASocket函数(创建套接字

        函数原型:SOCKET WSASocketint af,int type,int protocol,LPWSAPROTOCOL_INFO lpProtocolInfo,GROUP g,DWORD dwFlags;

//3个参数和前面的socket函数相同。地址族;Socket类型;与地址家族相关的协议(0

// lpProtocolInfo:所创建的套接字的特性

// g 保留

// dwFlags 指定套接字属性的描述,若取值为WSA_FLAG_OVERLAPPED,则创建重叠套接字

         6.WSARecvFrom函数(接受数据报类型的数据,并保存数据发送方的地址)

        函数原型: int WSARecvFrom(

SOCKET  s,//套接字描述符

LPWSABUF  lpBuffers,//指向WSABUF结构体数组,该结构体包含了一个指向缓冲区的指针和该缓冲区的长度

DWORD  dwBufferCount,// lpBuffers数组中WSABUF结构体数目

//定义多个WSABUF结构体变量同时区接受数据是因为:可以针对传送的信息,分别提供不同的缓冲区区接受,然后相应地取出缓冲区中的数据进行处理,这样就避免通过编码区切分字节流

LPDWORD  lpNumberOfBytesRecvd,//out类型参数,如果接收操作立即完成,那么该参数是一个指向本次调用所接受的字节数的指针

LPDWORD lpFlags,//in/out类型的参数,一个指向标志位的指针,这些标志会影响函数的行为(可以利用位操作将这些标志组合起来)

struct sockaddr FAR*  lpFrom,//out类型的参数,指向重叠操作完成后存放源地址的缓冲区

LPINT  lpFromlen,//指向lpFrom指定的缓冲区大小的指针

LPWSAOVERLAPPED lpOverlapped,//指向WSAOVERLAPPED结构体指针

LPWSAOVERLAPPED_COMPLETION_ROUTINE  lpCompletionRoutine//一个指向接受操作完成时调用的完成例程的指针(后两个参数对非重叠套接字则忽略)

);

       TODO:如果创建的是重叠套接字,在使用WSARecvFrom函数时,一定要注意最后两个参数的值,因为这时将采用重叠IO操作,WSARecvFrom函数会立即返回,当接受数据这一操作完成后,操作系统会调用lpCompletionRoutine参数指定的例程来通知调用线程,这个例程实际上就是一个回调函数

      函数原型:void CALLBACK CompletionROUTINE(

                  IN DWORD dwError,

                  IN DWORD cbTransferred,

                  IN LPWSOVERLAPPED lpOverlapped,

                  IN DWORD dwFlags

);

         7.WSASendTo函数

          函数原型:int WSASendTo(

                        SOCKET s,//套接字描述符

                        LPWSABUF lpBuffers,//一个指向WSABUF结构数组的指针,每一个WSABUF结构体包含一个缓冲区的指针和缓冲区的长度

                        DWORD dwBufferCount,//结构体数组中结构体数目

                        LPDWORD lpNumberOfBytesSent,//指向本次调用发送的字节数

                        DWORD dwFlags,//标志位(0

                       const struct sockaddr FAR* lpTo,//指向目标套接字地址

                       int iToLen,//lpTo中地址的长度

                  LPWSAOVERLAPPED  lpOverlapped,//指向WSAOVERLAPPED结构的指针

  LPWSAOVERLAPPED_COMPLETION_ROUTINE  lpCompletionRoutine//一个指向接收操作完成时调用的完成例程的指针

);

         8.WSASendWSARecv函数

         9.WSAIoctl函数

6.网络聊天室程序的实现(WS2_32.lib库,头文件winsock2.h

1)加载套接字库

     MFC函数AfxSocketInit只能加载1.1版本的套接字库,本程序要使用2.0版本所以应调用WSAStartup函数初始化程序所使用的套接字库(CChat2AppInitInstance函数中)

WORD wVersionRequested = MAKEWORD(2,2);

          WSADATA wsaData;

          int err = WSAStartup(wVersionRequested,&wsaData);

          if(err != 0)

          {

                   return false;

          }

          if((LOBYTE(wsaData.wVersion) != 2) || (HIBYTE(wsaData.wVersion) != 2))

          {

                   WSACleanup();

                   return false;

          }

2)创建并初始化套接字

     CChat2Dlg类增加SOCKET类型成员变量m_socket

                增加成员函数InitSocket()

      BOOL CChat2Dlg::InitSocket()

{

   m_socket = WSASocket(AF_INET,SOCK_DGRAM,0,NULL,0,0);

   if(INVALID_SOCKET == m_socket)

   {

             MessageBox("Create socket fail");

             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);

   if(SOCKET_ERROR == bind(m_socket,(SOCKADDR*)&addrSock,sizeof(SOCKADDR)))

   {

             MessageBox("bind fail");

             return false;

   }

  // UM_SOCK 自定义消息

   if(SOCKET_ERROR == WSAAsyncSelect(m_socket,m_hWnd,UM_SOCK,FD_READ))

   {

             MessageBox("注册网络读取事件失败!");

             return false;

   }

   return true;

}

3CChat2DlgOnInitDialog函数中调用InitSocket()函数

4)自定义消息UM_SOCK

5)添加消息响应函数OnSock实现接收端功能

     void CChat2Dlg::OnSock(WPARAM wParam,LPARAM lParam)

{

          switch(LOWORD(lParam)) //lParam的低位字(包含网络事件类型,高位包含无用信息)

          {

          case FD_READ:

                   WSABUF wsabuf;

                   wsabuf.buf = new char[200];

                   wsabuf.len = 200;

                   DWORD dwRead;

                   DWORD dwFlag = 0;

                   SOCKADDR_IN addrFrom;

                   int len = sizeof(SOCKADDR);

                   CString str;

                   CString strTemp;

                   if(SOCKET_ERROR == WSARecvFrom(m_socket,&wsabuf,1,&dwRead,&dwFlag,\

                            (SOCKADDR*)&addrFrom,&len,NULL,NULL)

                   {

                            MessageBox("接受数据失败!");

                            delete[] wsabuf.buf;

                            return;

                   }

                   str.Format("%s 说:%s",inet_ntoa(addrFrom.sin_addr),wsabuf.buf);

                   str += "\r\n";

                   GetDlgItemText(IDC_EDIT1,strTemp);

                   str += strTemp;

                   SetDlgItemText(IDC_EDIT1,str);

                   delete[] wsabuf.buf;

                   break;

          default:

                   break;

          }

}

6)实现发送端功能

     为发送按钮添加响应函数

     void CChat2Dlg::OnButton1()

{

          DWORD dwIP;

          CString strSend;

          WSABUF wsabuf;

          DWORD dwSend;

          int len;

          SOCKADDR_IN addrTo;

 

          ((CIPAddressCtrl*)GetDlgItem(IDC_IPADDRESS1))->GetAddress(dwIP);

 

          addrTo.sin_addr.S_un.S_addr = htonl(dwIP);

          addrTo.sin_family = AF_INET;

          addrTo.sin_port = htons(6000);

 

          GetDlgItemText(IDC_EDIT2,strSend);

          len = strSend.GetLength();

          wsabuf.buf = strSend.GetBuffer(len);

          wsabuf.len= len + 1;

 

          if(SOCKET_ERROR == WSASendTo(m_socket,&wsabuf,1,&dwSend,0,(SOCKADDR*)&addrTo,\

                   sizeof(SOCKADDR),NULL,NULL))

          {

                   MessageBox("发送消息失败!");

                   return;

          }

          SetDlgItemText(IDC_EDIT2,"");

}

7)终止套接字库的使用

     CChat2App的析构函数中添加:WSACleanup();

     CChat2Dlg的析构函数中添加: if(m_socket)  Closesocket(m_socket);

7.利用主机名实现网络访问:gethostbyname函数(将主机名装换为IP地址)

8.在编写网络应用程序时,因为网络状况瞬息万变,所以总是应该对函数的返回值做判断,如果发生错误就要进行相应处理

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值