对于许多初学者来说,网络通信程序的开发,普遍的一个现象就是觉得难以入手。许多概念,诸如:同步(Sync)/异步(Async),阻塞(Block)/非阻塞(Unblock)等,初学者往往迷惑不清,只知其所以而不知起所以然。

  异步方式指的是发送方不等接收方响应,便接着发下个数据包的通信方式;而同步指发送方发出数据后,等收到接收方发回的响应,才发下一个数据包的通信方式。
  阻塞套接字是指执行此套接字的网络调用时,直到成功才返回,否则一直阻塞在此网络调用上,比如调用recv()函数读取网络缓冲区中的数据,如果没有数据到达,将一直挂在recv()这个函数调用上,直到读到一些数据,此函数调用才返回;而非阻塞套接字是指执行此套接字的网络调用时,不管是否执行成功,都立即返回。比如调用recv()函数读取网络缓冲区中数据,不管是否读到数据都立即返回,而不会一直挂在此函数调用上。在实际Windows网络通信软件开发中,异步非阻塞套接字是用的最多的。平常所说的C/S(客户端/服务器)结构的软件就是异步非阻塞模式的。
  对于这些概念,初学者的理解也许只能似是而非,我将用一个最简单的例子说明异步非阻塞Socket的基本原理和工作机制。目的是让初学者不仅对Socket异步非阻塞的概念有个非常透彻的理解,而且也给他们提供一个用Socket开发网络通信应用程序的快速入门方法。操作系统是Windows 98(或NT4.0),开发工具是Visual C++6.0。
  MFC提供了一个异步类CAsyncSocket,它封装了异步、非阻塞Socket的基本功能,用它做常用的网络通信软件很方便。但它屏蔽了Socket的异步、非阻塞等概念,开发人员无需了解异步、非阻塞Socket的原理和工作机制。因此,建议初学者学习编网络通信程序时,暂且不要用MFC提供的类,而先用Winsock2     API,这样有助于对异步、非阻塞Socket编程机制的理解。
  为了简单起见,服务器端和客户端的应用程序均是基于MFC的标准对话框,网络通信部分基于Winsock2 API实现。
  先做服务器端应用程序。
  用MFC向导做一个基于对话框的应用程序SocketSever,注意第三步中不要选上Windwos Sockets选项。在做好工程后,创建一个SeverSock,将它设置为异步非阻塞模式,并为它注册各种网络异步事件,然后与自定义的网络异步事件联系上,最后还要将它设置为监听模式。在自定义的网络异步事件的回调函数中,你可以得到各种网络异步事件,根据它们的类型,做不同的处理。下面将详细介绍如何编写相关代码。
  在SocketSeverDlg.h文件的类定义之前增加如下定义:
#define     NETWORK_EVENT     WM_USER+166     file://定义网络事件
   
SOCKET ServerSock; file://服务器端Socket
在类定义中增加如下定义:
class CSocketSeverDlg : CDialog
{
public:
       SOCKET ClientSock[CLNT_MAX_NUM]; file://存储与客户端通信的Socket的数组

/*各种网络异步事件的处理函数*/
       void OnClose(SOCKET CurSock);      file://对端Socket断开
       void OnSend(SOCKET CurSock);      file://发送网络数据包
       void OnReceive(SOCKET CurSock); file://网络数据包到达
       void OnAccept(SOCKET CurSock);     file://客户端连接请求

BOOL InitNetwork();     file://初始化网络函数
       void OnNetEvent(WPARAM wParam, LPARAM lParam); file://异步事件回调函数
                   …
};
        
在SocketSeverDlg.cpp文件中增加消息映射,其中OnNetEvent是异步事件回调函数名:
       ON_MESSAGE(NETWORK_EVENT,OnNetEvent)
定义初始化网络函数,在SocketSeverDlg.cpp文件的OnInitDialog()中调此函数即可。
BOOL CSocketSeverDlg::InitNetwork()
{
       WSADATA wsaData;

//初始化TCP协议
       BOOL ret = WSAStartup(MAKEWORD(2,2), &wsaData);
       if(ret != 0)
       {
           MessageBox('初始化网络协议失败!');
           return FALSE;
       }

//创建服务器端套接字
       ServerSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
       if(ServerSock == INVALID_SOCKET)
       {
           MessageBox('创建套接字失败!');
           closesocket(ServerSock);
           WSACleanup();
           return FALSE;
       }

//绑定到本地一个端口上
       sockaddr_in localaddr;
       localaddr.sin_family = AF_INET;
       localaddr.sin_port = htons(8888);     //端口号不要与其他应用程序冲突
       localaddr.sin_addr.s_addr = 0;
       if(bind(ServerSock ,(struct sockaddr*)&localaddr,sizeof(sockaddr))
                                             = = SOCKET_ERROR)
       {
           MessageBox('绑定地址失败!');
           closesocket(ServerSock);
           WSACleanup();
           return FALSE;
       }
       //将SeverSock设置为异步非阻塞模式,并为它注册各种网络异步事件,其中m_hWnd      
       //为应用程序的主对话框或主窗口的句柄
       if(WSAAsyncSelect(ServerSock, m_hWnd, NETWORK_EVENT, FD_ACCEPT | FD_CLOSE | FD_READ | FD_WRITE) == SOCKET_ERROR)
       {
           MessageBox('注册网络异步事件失败!');
           WSACleanup();
           return FALSE;
       }
       listen(ServerSock, 5); file://设置侦听模式
       return TRUE;
}

下面定义网络异步事件的回调函数
void CSocketSeverDlg::OnNetEvent(WPARAM wParam, LPARAM lParam)
{
       //调用Winsock API函数,得到网络事件类型
       int iEvent = WSAGETSELECTEVENT(lParam);
       //调用Winsock API函数,得到发生此事件的客户端套接字
       SOCKET CurSock= (SOCKET)wParam;

switch(iEvent)
       {
           case FD_ACCEPT:         //客户端连接请求事件
               OnAccept(CurSock);
               break;
           case FD_CLOSE:          //客户端断开事件:
               OnClose(CurSock);
               break;
           case FD_READ:           //网络数据包到达事件
               OnReceive(CurSock);
               break;
            case FD_WRITE:         //发送网络数据事件
               OnSend(CurSock);
               break;
            default: break;
        }
}
   
  以下是发生在相应Socket上的各种网络异步事件的处理函数,其中OnAccept传进来的参数是服务器端创建的套接字,OnClose()、OnReceive()和OnSend()传进来的参数均是服务器端在接受客户端连接时新创建的用与此客户端通信的Socket。
void CSocketSeverDlg::OnAccept(SOCKET CurSock)
{
       //接受连接请求,并保存与发起连接请求的客户端进行通信Socket
       //为新的socket注册异步事件,注意没有Accept事件
}
void CSocketSeverDlg::OnClose(SOCET CurSock)
{
       //结束与相应的客户端的通信,释放相应资源
}

void CSocketSeverDlg::OnSend(SOCET CurSock)
{
       //在给客户端发数据时做相关预处理
}

void CSocketSeverDlg::OnReceive(SOCET CurSock)
{
       //读出网络缓冲区中的数据包
}       
       
  用同样的方法建立一个客户端应用程序。初始化网络部分,不需要将套接字设置为监听模式。注册异步事件时,没有FD_ACCEPT,但增加了FD_CONNECT事件,因此没有OnAccept()函数,但增加了OnConnect()函数。向服务器发出连接请求时,使用connect()函数,连接成功后,会响应到OnConnect()函数中。下面是OnConnect()函数的定义,传进来的参数是客户端Socket和服务器端发回来的连接是否成功的标志。
void CSocketClntDlg::OnConnect(SOCKET CurSock, int error)
{
       if(0 = = error)
       {
           if(CurSock = = ClntSock)
           MessageBox('连接服务器成功!');
       }
}
  定义OnReceive()函数,处理网络数据到达事件;
  定义OnSend()函数,处理发送网络数据事件;
  定义OnClose()函数,处理服务器的关闭事件。
            
  以上就是用基于Windows消息机制的异步I/O模型做服务器和客户端应用程序的基本方法。另外还可以用事件模型、重叠模型或完成端口模型,读者可以参考有关书籍。
  在实现了上面的例子后,你将对Winsock编网络通信程序的机制有了一定的了解。接下来你可以进行更精彩的编程, 不仅可以在网上传输普通数据,而且还以传输语音、视频数据,你还可以自己做一个网络资源共享的服务器软件,和你的同学在实验室的局域网里可以共同分享你的成果。

同步服务器套接字挂起应用程序的执行,直到套接字上接收到连接请求。同步服务器套接字不适用于在操作中大量使用网络的应用程序,但它们可能适用于简单的网络应用程序。使用 Bind 和 Listen 方法设置 Socket 以在终结点上侦听之后,Socket 就可以随时使用 Accept 方法接受传入的连接请求了。应用程序被挂起,直到调用 Accept 方法时接收到连接请求。

接收到连接请求时,Accept 返回一个与连接客户端关联的新 Socket 实例。下面的示例读取客户端数据,在控制台上显示该数据,然后将该数据回显到客户端。Socket 不指定任何消息协议,因此字符串“<EOF>”标记消息数据的结尾。它假定一个名为 listener 的 Socket 已初始化,并绑定到一个终结点。

Console.WriteLine("Waiting for a connection...");
Socket handler = listener.Accept();
String data = null;

while (true) {
     bytes = new byte[1024];
     int bytesRec = handler.Receive(bytes);
     data += Encoding.ASCII.GetString(bytes,0,bytesRec);
     if (data.IndexOf("<EOF>") > -1) {
         break;
     }
}

Console.WriteLine( "Text received : {0}", data);

byte[] msg = Encoding.ASCII.GetBytes(data);
handler.Send(msg);
handler.Shutdown(SocketShutdown.Both);
handler.Close();

基于MFC的socket编程(异步非阻塞通信)的更多相关文章

  1. 基于MFC的socket编程

    网络编程 1.windows 套接字编程(开放的网络编程接口)添加头文件#include<windows.h> 2.套接字及其分类 socket分为两种:(1)数据报socket:无连接套 ...

  2. IO多路复用与异步非阻塞

    1.基于socket,发送http请求 import socket import requests # 方式一 list=['li','gh ','nn'] for i in list: ret=re ...

  3. [转]Socket编程中,阻塞与非阻塞的区别

    阻塞:一般的I/O操作可以在新建的流中运用.在服务器回应前它等待客户端发送一个空白的行.当会话结束时,服务器关闭流和客户端socket.如果在队列中没有请示将会出现什么情况呢?那个方法将会等待一个的到 ...

  4. Socket编程中,阻塞与非阻塞的区别

    阻塞:一般的I/O操作可以在新建的流中运用.在服务器回应前它等待客户端发送一个空白的行.当会话结束时,服务器关闭流和客户端socket.如果在队列中没有请示将会出现什么情况呢?那个方法将会等待一个的到 ...

  5. Python的异步编程[0] -> 协程[1] -> 使用协程建立自己的异步非阻塞模型

    使用协程建立自己的异步非阻塞模型 接下来例子中,将使用纯粹的Python编码搭建一个异步模型,相当于自己构建的一个asyncio模块,这也许能对asyncio模块底层实现的理解有更大的帮助.主要参考为 ...

  6. 服务器编程心得(四)—— 如何将socket设置为非阻塞模式

    1. windows平台上无论利用socket()函数还是WSASocket()函数创建的socket都是阻塞模式的: SOCKET WSAAPI socket( _In_ int af, _In_ ...

  7. NIO:异步非阻塞I/O,AIO,BIO

    Neety的基础使用及说明 https://www.cnblogs.com/rrong/p/9712847.html BIO(缺乏弹性伸缩能力,并发量小,容易出现内存溢出,出现宕机 每一个客户端对应一 ...

  8. java的高并发IO原理,阻塞BIO同步非阻塞NIO,异步非阻塞AIO

    原文地址: IO读写的基础原理 大家知道,用户程序进行IO的读写,依赖于底层的IO读写,基本上会用到底层的read&write两大系统调用.在不同的操作系统中,IO读写的系统调用的名称可能不完 ...

  9. Linux-同步异步非阻塞阻塞的解析

    一.理解同步.异步.阻塞.非阻塞 出场人物:老张,水壶两把(普通水壶,简称水壶:会响的水壶,简称响水壶). 1 老张把水壶放到火上,立等水开.(同步阻塞) 老张觉得自己有点傻. 2 老张把水壶放到火上 ...

随机推荐

  1. 重置MySQL的root用户密码(Window)

    1.首先要停止Mysql服务.打开CMD,键入命令 net stop mysql 默认的mysql服务名就是mysql,如果你修改过服务名,请自行对照修改命令. 2.在CMD中进入mysql的bin目 ...

  2. 杂记之web篇

    问题1:通过POST方式提交给后台的数据出现了乱码,用部分浏览器测试却是好的. 解决办法: 在web.config文件中加上 <globalization responseEncoding=&q ...

  3. Property type 'id<tabBarDelegate>' is incompatible with type 'id<UITabBarDelegate> _Nullable' inherited from 'UITabBar'

    iOS报错:Property type 'id' is incompatible with type 'id _Nullable' inherited from 'UITabBar' 如图: 可能原因 ...

  4. 学习OpenSeadragon之五(工具条toolbar与自定义按钮)

    OpenSeadragon简介:学习OpenSeadragon之一(一个显示多层图片的开源JS库) 一.工具条toolbar设置 OpenSeadragon为我们提供了现成的工具条toolBar,工具 ...

  5. display:inline,display:inline-block,display:block 区别

    之前一直迷惑于display:inline/inline-block/block的异同,在度娘谷哥的帮助下,突然有了一点思路. 按照网上的介绍,inline将对象转化为内联元素,block将对象转化为 ...

  6. html表格table设置边框

    对于很多初学HTML的人来说,表格<table>是最常用的标签了,但对于表格边框的控制,很多初学者却不甚其解. 一般我们用表格的时候总会给它个border属性,比如:<table b ...

  7. apache2.2 虚拟主机配置详解

    一.修改httpd.conf 打开appserv的安装目录,找到httpd.conf文件,分别去掉下面两行文字前面的#号. #LoadModule vhost_alias_module modules ...

  8. CWnd类

    CWnd类的成员 .数据成员 m_hWnd 指明与这个CWnd对象相关联的HWND句柄 .构造和析构 CWnd 构造一个CWnd对象 DestroyWindow 销毁相关联的Windows窗口 .初始 ...

  9. Qt中如何固定窗口的大小?

    这个是从网上转载过来的,我第一次看到的在如下网页:http://blog.csdn.net/cgb0210/article/details/5712980  这里我记录一下,留以后查阅. 一种方法是设 ...

  10. PHP 中变量的间接引用

    请看以下代码: <?php $name="Yshy"; $$name="Yanshiying"; echo $Yshy; ?> 在浏览器端将会输出: ...