基于MFC的socket编程(异步非阻塞通信)

时间:2022-05-19 10:05:52
 
 对于许多初学者来说,网络通信程序的开发,普遍的一个现象就是觉得难以入手。许多概念,诸如:同步(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. &lbrack;转&rsqb;Socket编程中,阻塞与非阻塞的区别

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

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

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

  5. Python的异步编程&lbrack;0&rsqb; -&gt&semi; 协程&lbrack;1&rsqb; -&gt&semi; 使用协程建立自己的异步非阻塞模型

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

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

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

  7. NIO:异步非阻塞I&sol;O,AIO,BIO

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

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

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

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

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

随机推荐

  1. PHP中的&lowbar;&lowbar;toString方法&lpar;实现JS里的链式操作&rpar;

    _toString方法是在打印对象时自动调用的魔术方法,如果不声明会报以下错 Catchable fatal error: Object of class String could not be co ...

  2. js &OpenCurlyDoubleQuote;&plus;” 连接字符串&amp&semi;数字相加 数字相加出现多位小数 函数调用单引号双引号嵌套和转义字符的使用

    一.机制 JavaScript中,加号不仅表示相加还表示字符串连接 当加号两边存在字符串时,加号代表连接,实际上是将两侧都转为了字符串,如 "1" + 1 = "11&q ...

  3. ES6 Set &amp&semi; Map

    ES6 Set & Map OK ES6 Map https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Globa ...

  4. Spring的IOC理解(转载)

    学习过Spring框架的人一定都会听过Spring的IoC(控制反转) .DI(依赖注入)这两个概念,对于初学Spring的人来说,总觉得IoC .DI这两个概念是模糊不清的,是很难理解的,今天和大家 ...

  5. Flask&lowbar;WTF升级到最新版本

    用pip $ pip install -U Flask-WTF 看熊哥的后端管理系统时候报错ImportError: cannot import name FlaskForm. 那一行的代码:from ...

  6. 大数加法(SDUT&OpenCurlyDoubleQuote;斐波那契”串)4335

    题目链接:https://acm.sdut.edu.cn/onlinejudge2/index.php/Home/Contest/contestproblem/cid/2697/pid/4335.ht ...

  7. 前端学习之路之CSS (二)

    Infi-chu: http://www.cnblogs.com/Infi-chu/ id选择器id 选择器可以为标有特定 id 的 HTML 元素指定特定的样式,CSS 中 id 选择器以 &quo ...

  8. java基础知识(初学)

    (小记) 文本文档方式可以下载notepad 在设置-新建-修改默认语言为java 编码为ANSI! java关键字特点:1.完全小写字母.如:public. java标识符:方法的名称,类的名称,变 ...

  9. 如何理解一台服务器可以绑定多个ip,一个ip可以绑定多个域名

    一个域名只能对应一个IP的意思是域名在DNS服务器里做解析的时候 一条记录只能指向一个IP地址.这个是死规定,试想一下,如果一个子域名指向了2个ip ,当访问者打开这个域名的时候,浏览器是展示哪个IP ...

  10. 斑马Zebra ZPLII指令集中文说明解释

      我们最常用的斑马(Zebra)条码打印机,应用ZPLII命令来控制打印,说明书中有每条指令的详细说明及相关示例,下面是各指令的中文释义: ^A 对Zebra内置点阵字体缩放 ^A(可缩放/点阵字体 ...