`
werwolf
  • 浏览: 93324 次
  • 性别: Icon_minigender_1
  • 来自: 广州
社区版块
存档分类
最新评论

windows下局域网内的实时数据收发

阅读更多

    最近做了一个项目,是一个关于局域网内的网络分发程序,程序采用C/S结构,简单的描述就是:

       服务器端从一个USB设备中采集数据,然后要分发给三个客户端,其中一个客户端是本机,这三个客户端和服务器是在同一个局域网内。服务器端每秒从USB设备中采集90次数据,发给每个客户端30次数据,服务器端根据客户端连接的IP地址不同来发送不同的数据,同时,程序的实时性要求比较高,服务器从USB设备上采集到数据后要及时的发送到客户端,每次发送的数据量是32字节。

 

程序的逻辑是:

服务器端:

       异步非阻塞,当客户端连接上后,服务器端只需要向socket中写入数据即可,对于每一个连接上的socket,每秒服务器端向socket中写入30次数据。所以服务器端只需要写就行。服务器端有界面。

 

客户端:

       为了保证使用的简单和可移植性,客户端做成dll动态链接库,应用程序再去使用这个dll,根据服务器端发送数据的频率,在客户端每秒钟需要解析30次数据,但是应用程序通过dll获取数据的频率有时会超过30次,所以如果采用recv函数,每次应用程序都会被阻塞,直到socket中有数据为止。

 

客户端有几种设计思路:

方案1:设计成多线程程序,定义一个全局变量,一个线程专门负责从socket中读数据,数据解析后放入这个全局变量中,这里使用轮训即可,即没有数据就阻塞住,一旦socket中有数据就马上读出;应用程序的主线程一旦需要通过dll获取数据,直接返回这个全局变量中的值。

(这个也是最后采用的方案,但是这里会有一个潜在的问题,这里对全局变量没有加锁,这就可能造成数据的不一致,但是考虑到系统本身,因为相邻两次数据变化不大,而且频率比较高,每秒中30次,这样做问题不大,最终的实际效果也证明了这点。)

 

方案2:客户端采用非阻塞的方式,使用单线程,一旦应用程序通过dll获取数据,则马上到socket中读取数据,并将数据放入一个全局变量中,如果socket中没有数据,则返回上一次的数据。

(对于这个方案,如果应用程序通过dll获取数据的频率超过30/s,那么程序没有问题;但是一旦频率小于30次,那么socket就可能会拥堵,这样实时性就不能保证,可取的做法是每次应用程序通过dll获取数据,都需要把socket中的数据读空为止,然后返回最新的值,这样理论上就不会造成拥塞。这个方案也是可行的,只是最后因为第一种方案的实现更简单,最后就没有采用该方案。)

 

测试过程中碰到的问题:

上面的设计方案看起来很完美,但是最终实现过后总会有延迟现象发生,在经过艰苦的测试后,最终发现原来是TCP协议本身的问题。

TCP/IP默认情况下采用Nagle算法,Nagle算法通过将未确认的数据存入缓冲区直到蓄足一个包一起发送的方法,来减少主机发送的零碎小数据包的数目,如果一定时间内包的数量还不够大,那么默认情况下为200ms发送一次(给数据包打上时间戳后发现的)。

对于Nagle算法,如果是因特网上程序,这样做无疑减少了数据收发的次数,提高了网络性能。但是对于实时性要求较高的应用,这样做无疑就会造成延迟,而且程序本身处于局域网中,并且数据量不大,因此我们可以通过setsockopt()来禁止使用Nagle算法,代码如下:

 

BOOL bVal= TRUE;

setsockopt(remoteSocket[0], IPPROTO_TCP,TCP_NODELAY,(char *) &bVal,sizeof(BOOL));

至此,就解决了数据发送接收实时性的问题。

(这个问题耗费了我和另外几个哥们大量的时间)

 

其实,对于该问题,在《TCP/IP卷一》和《UNIX网络编程卷一》当中都有明确的说明,只是以前做项目并没有做到这么底层,所以给忽略了,经过这个项目,经验值暴增。

 

下面是具体的服务器端的实现,对于客户端,这里就略掉了。

 

对于这样一个项目需求,首先要选择的是网络模型。Windows的网络模型很大程度上参考了Berkeleysocket实现,很多Unix上的socket系统调用也能在Windows下使用,但是Windows也提供了一些自身特有的模型,查看《Windows网络编程技术》可以知道,主要有下面几种I/O模型:

选择(Select)、异步选择 WSAAsyncSelect)、事件选择(WSAEventSelect)、重叠I/OOverlapped I/O)和完成端口(Completion Port)共五种I/O模型。

到目前为止,使用过的有Select模型和WSAAsyncSelect模型,以前实习的时候见到别人的代码使用过Completion Port模型,WSAEventSelectOverlapped I/O则没有使用过。

 

因为服务器端有窗口,而且需要异步非阻塞的发送,于是就采用WSAAsyncSelect来进行实现。

 

下面是该模型的一个使用框架,这个从网上摘录的。

8.2.2 WSAAsyncSelect
Wi n s o c k
提供了一个有用的异步I / O模型。利用这个模型,应用程序可在一个套接字上,接收以Wi n d o w s消息为基础的网络事件通知。具体的做法是在建好一个套接字后,调用W S A A s y n c S e l e c t函数。该模型最早出现于Wi n s o c k1 . 1版本中,用于帮助应用程序开发者面向一些早期的1 6Wi n d o w s平台(如Windows for Wo r k g r o u p s),适应其落后的多任务消息环境。应用程序仍可从这种模型中得到好处,特别是它们用一个标准的Wi n d o w s例程(常称为“ w i n p r o c”),对窗口消息进行管理的时候。该模型亦得到了Microsoft Foundation Class(微软基本类,M F C)对象C S o c k e t的采纳。

消息通知
要想使用W S A A s y n c S e l e c t模型,在应用程序中,首先必须用C r e a t e Wi n d o w函数创建一个窗口,再为该窗口提供一个窗口例程支持函数( Wi n p r o c)。亦可使用一个对话框,为其提供一个对话例程,而非窗口例程,因为对话框本质也是窗口。考虑到我们的目的,我们打算用一个简单的窗口来演示这种模 型,采用的是一个支持窗口例程。设置好窗口的框架后,便可开始创建套接字,并调用W S A A s y n c S e l e c t函数,打开窗口消息通知。该函数的定义如下:

int WSAAsyncSelect(
           SOCKET s,
           HWND hWnd,
           unsigned int wMsg,
           long lEvent
          );

中, s参数指定的是我们感兴趣的那个套接字。h W n d参数指定的是一个窗口句柄,它对应于网络事件发生之后,想要收到通知消息的那个窗口或对话框。w M s g参数指定在发生网络事件时,打算接收的消息。该消息会投递到由h W n d窗口句柄指定的那个窗口。通常,应用程序需要将这个消息设为比Wi n d o w sW M _ U S E R大的一个值,避免网络窗口消息与预定义的标准窗口消息发生混淆与冲突。最后一个参数是l E v e n t,它指定的是一个位掩码,对应于一系列网络事件的组合(请参考表8 - 3),应用程序感兴趣的便是这一系列事件。大多数应用程序通常感兴趣的网络事件类型包括: F D _ R E A DF D _ W R I T EF D _ A C C E P TF D _ C O N N E C TF D _ C L O S E。当然,到底使用F D _ A C C E P T,还是使用F D _ C O N N E C T类型,要取决于应用程序的身份到底是一个客户机呢,还是一个服务器。如应用程序同时对多个网络事件有兴趣,只需对各种类型执行一次简单的按位O R(或)运算,然后将它们分配给l E v e n t就可以了。举个例子来说:

WSAAsyncSelect(s,hWnd,WM_SOCKET,FD_CONNECT|FD_READ|FD_WRITE|FD_CLOSE);

这样一来,我们的应用程序以后便可在套接字s上,接收到有关连接、发送、接收以及套
接字关闭这一系列网络事件的通知。特别要注意的是,多个事件务必在套接字上一次注册!
外还要注意的是,一旦在某个套接字上允许了事件通知,那么以后除非明确调用c l o s e s o c k e t命令,或者由应用程序针对那个套接字调用了W S A A s y n c S e l e c t,从而更改了注册的网络事件类型,否则的话,事件通知会永远有效!若将l E v e n t参数设为0,效果相当于停止在套接字上进行的所有网络事件通知。

若应用程序针对一个套接字调用了W S A A s y n c S e l e c t,那么套接字的模式会从锁定自动变成非锁定,我们在前面已提到过这一点。这样一来,假如调用了像W S A R e c v这样的Wi n s o c k I / O函数,但当时却并没有数据可用,那么必然会造成调用的失败,并返回W S A E W O U L D B L O C K错误。为防止这一点,应用程序应依赖于由W S A A s y n c S e l e c tu M s g参数指定的用户自定义窗口消息,来判断网络事件类型何时在套接字上发生;而不应盲目地进行调用。

 

8-3 用于W S A A s y n c S e l e c t函数的网络事件类型

F D _ R E A D             应用程序想要接收有关是否可读的通知,以便读入数据
F D _ W R I T E          
应用程序想要接收有关是否可写的通知,以便写入数据
F D _ O O B                
应用程序想接收是否有带外( O O B)数据抵达的通知
F D _ A C C E P T       
应用程序想接收与进入连接有关的通知
F D _ C O N N E C T    
应用程序想接收与一次连接或者多点j o i n操作完成的通知
F D _ C L O S E             
应用程序想接收与套接字关闭有关的通知
F D _ Q O S            
应用程序想接收套接字服务质量Q o S)发生更改的通知
F D _ G R O U P _ Q O S             
应用程序想接收套接字组服务质量发生更改的通知(现在没什么用处,为未来套接字组的使用保留)
F D _ R O U T I N G _ I N T E R FA C E _ C H A N G E       
应用程序想接收在指定的方向上,与路由接口发生变化的通知
F D _ A D D R E S S _ L I S T _ C H A N G E       
应用程序想接收针对套接字的协议家族,本地地址列表发生变化的通知

应用程序在一个套接字上成功调用了W S A A s y n c S e l e c t之后,应用程序会在与h W n d窗口句柄参数对应的窗口例程中,以Windows消息的形式,接收网络事件通知。窗口例程通常定义如下:

LRESULT CALLBACK WindProc(
              HWND hWnd,
              UINT uMsg,
              WPARAM wParam,
              LPARAM lParam
              );
中,h W n d参数指定一个窗口的句柄,对窗口例程的调用正是由那个窗口发出的。u M s g参数指定需要对哪些消息进行处理。就我们的情况来说,感兴趣的是W S A A s y n c S e l e c t调用中定义的消息。w P a r a m参数指定在其上面发生了一个网络事件的套接字。假若同时为这个窗口例程分配了多个套接字,这个参数的重要性便显示出来了。在l P a r a m参数中,包含了两方面重要的信息。其中, l P a r a m的低字(低位字)指定了已经发生的网络事件,而l P a r a m的高字(高位字)包含了可能出现的任何错误代码。
络事件消息抵达一个窗口例程后,应用程序首先应检查l P a r a m的高字位,以判断是否在套接字上发生了一个网络错误。这里有一个特殊的宏: W S A G E T S E L E C T E R R O R,可用它返回高字位包含的错误信息。若应用程序发现套接字上没有产生任何错误,接着便应调查到底是哪个网络事件类型,造成了这条Wi n d o w s消息的触发具体的做法便是读取l P a r a m之低字位的内容。此时可使用另一个特殊的宏:W S A G E T S E L E C T E V E N T,用它返回l P a r a m的低字部分。
在程序清单8 - 5中,我们向大家演示了如何使用W S A A s y n c S e l e c t这种I / O模型,来实现窗口消息的管理。在源程序中,我们着重强调的是开发一个基本服务器应用要涉及到的基本步骤,忽略了开发一个完整的Wi n d o w s应用需要涉及到的大量编程细节。

程序清单8-5 WSAAsyncSelect服务器示范代码

#define WM_SOCKET WM_USER+1
#inlude <windows.h>

int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,

LPSTR lpCmdline,int nCmdShow)
{

SOCKET listen;
HWND window;
//create a window and assign the serverWinProc blew to it
window = CreateWindow();
//start winsock and create a socket

WSAStarup(...);
listen = Socket();
//Bind the socket to port 5150
// and begin listening for connection

InternetAddr.sin_family = AF_INET;
InternetAddr.sin_addr.s_addr=htonl(INADDR_ANY);
InternetAddr.sin_port = htons(5150);
bind(listen,(PSOCKETADDR)&InternetAddr,sizeof(InternetAddr));

//set up window message notification on the new socket using the WM_SOCKET define above
WSAAsyncSelect(listen,window,WM_SOCKET,FD_ACCEPT|FD_CLOSE);

listen(listen,5);

//translate and dispatch window messages
//until the appliation terminates
}             


BOOL CALLBACK ServerWinProc(HWND hDlg,WORD wMsg,WORD wParam,WORD lParam)
{
SOCKET accept;
switch(wMsg)
{
   case WM_PAINT:
      break;
   case WM_SOCKET:
      //determine whether an error occured on the socket
      //by using the WSAGETSELECTERROR() macro
     
      if(WSAGETSELECTERROR(lWparam))
      {
       //display the error and close the socket
       closesocket(wParam);
       break;
      }
      //determine what event occured on the socket
     
      switch(WSAGETSELECTEVENT(lParam)
      {
       case FD_ACCEPT:
          //ACCEPT an incoming connection
          Accept = accept(wParam,NULL,NULL);
         
          //prepare accepted socket for read
          //write,and close notifation
         
          WSAAsyncSelect(Accept,hWnd,WM_SOCKET,FD_READ|FD_WRITE|FD_CLOSE);
       case FD_READ:
          //RECEIVE data from the socket in wParam
          break;
       case FD_WRITE:
          //THE socket in wParam is ready for sending data
         
          break;
       case FD_CLOSE:
          //THE connection is now closed
          closesocket(wParam);
          break;
         
      }
      break;
}
return TRUE;
}

最后一个特别有价值的问题是应用程序如何对F D _ W R I T E事件通知进行处理。只有在三种条件下,才会发出F D _ W R I T E通知:
使用c o n n e c tW S A C o n n e c t,一个套接字首次建立了连接。
使用a c c e p tW S A A c c e p t,套接字被接受以后。
s e n dW S A S e n ds e n d t oW S A S e n d To操作失败,返回了W S A E W O U L D B L O C K错误, 而且缓冲区的空间变得可用因此,作为一个应用程序,自收到首条F D _ W R I T E消息开始,便应认为自己必然能在一个套接字上发出数据,直至一个s e n dW S A S e n ds e n d t oW S A S e n d To返回套接字错误W S A E W O U L D B L O C K。经过了这样的失败以后,要再用另一条F D _ W R I T E通知应用程序再次发送数据

 

 

track:http://hi.baidu.com/fanjialin17/blog/item/1e297377669edf13b151b95e.html

分享到:
评论

相关推荐

    公司局域网内办公文件传递、信息共享、邮件收发

    本文将围绕“公司局域网内办公文件传递、信息共享、邮件收发”这一主题,详细阐述相关知识点,旨在为读者提供有价值的参考和学习材料。 首先,局域网(LAN)内的文件传递是日常办公中的基础操作。员工通常需要在...

    局域网内的消息发送工具

    TCP/IP协议负责数据在网络中的传输,而套接字是网络通信的接口,允许程序收发数据。 2. **多线程技术**:为了实现即时的消息发送和接收,工具通常会使用多线程。一个线程用于监听接收端口,等待新消息的到来;另一...

    MFC实现局域网内点对点的大文件传输

    8. **安全性**:虽然局域网内的传输相对安全,但仍需关注数据的安全性,防止未授权的访问和数据篡改。可以使用SSL/TLS加密保护传输过程,或者设置访问权限,只允许特定设备参与文件传输。 9. **性能优化**:为了...

    局域网文件传输和短信收发源代码

    通过使用Windows API中的Netapi32库,可以查询局域网内的所有计算机,并将其信息(如主机名、IP地址等)展示给用户。这一步骤是建立局域网通信的基础,它确保了用户能够发现并选择目标设备进行交互。 其次,短信...

    ASP.NET基于局域网的信息收发系统的设计与实现(源代码).rar

    通过创建socket服务或使用.NET Framework提供的NetworkStream、TcpClient和TcpListener类,可以实现在局域网内的数据传输。此外,还可以利用WCF(Windows Communication Foundation)来构建更高级的服务接口。 3. ...

    ASP.NET基于局域网的信息收发系统的设计与实现(源代码+论文).rar

    在局域网内设计信息收发系统,需要考虑网络拓扑、安全策略、数据传输效率等因素。通过ASP.NET,开发者可以创建一个集中式的信息平台,允许局域网内的用户共享和交换信息。 三、系统设计 1. 用户认证与授权:系统应...

    局域网内聊天及文件传输源代码

    在IT领域,网络编程是一项核心技能,特别是在局域网(LAN)环境中,它涉及设备间的通信、数据交换和资源共享。本项目"局域网内聊天及文件传输源代码"是用VC++(Visual C++)编写的,为初学者提供了一个很好的实践...

    局域网内邮件服务器的架构与部署

    总的来说,构建局域网内的Exchange 2003邮件服务器需要对Windows Server 2003操作系统有深入理解,掌握Exchange的安装、配置和管理技巧。通过这样的部署,组织可以享受到高效、安全的内部邮件服务,实现快速的信息...

    局域网文件接收发送系统

    1. 文件发送:用户可以选择本地文件或文件夹,通过系统快速发送到同一局域网内的其他设备。 2. 文件接收:接收端设备能够实时监控网络中的文件发送请求,并选择性接收。 3. 多文件传输:支持一次性发送多个文件,...

    ASP.NET基于局域网的信息收发系统的设计与实现(源代码+论文).zip

    总之,这个项目展示了如何利用ASP.NET框架构建一个局域网内的信息收发系统,涵盖了网络通信、数据安全、数据库管理和系统架构等多个方面的知识点,对于深入理解和实践ASP.NET开发具有很高的参考价值。

    易语言局域网通信EasyChat(自制,开源)附源码

    在易语言中,开发者通常需要定义网络套接字,进行连接建立、数据收发及断开连接的操作。这些操作可以通过易语言提供的系统命令如“创建套接字”、“发送数据”和“接收数据”等来完成。同时,为了实现多用户之间的...

    计算机软件毕业设计_DOT.NET源码局域网的信息收发系统的设计与实现_计算机毕业设计源码_计算机毕业设计源代码.rar

    标题中的“计算机软件毕业设计_DOT.NET源码局域网的信息收发系统的设计与实现”表明这是一个基于DOT.NET框架开发的毕业设计项目,专注于在局域网内实现信息的发送和接收功能。这个系统可能涉及客户端-服务器架构,...

    基于ASP.net的局域网信息收发系统源码.zip

    【标题】:“基于ASP.NET的局域网信息收发系统源码” 该源码项目是为实现局域网内信息高效、安全传输而设计的,它利用了ASP.NET这一强大的Web应用程序开发框架,结合C#编程语言,构建了一个功能完善的局域网通信...

    ASP.NET基于局域网的信息收发系统的设计与实现(源代码+毕设).zip

    在这个特定的项目“ASP.NET基于局域网的信息收发系统的设计与实现”中,开发者使用了ASP.NET技术来创建一个能够在局域网内进行信息交换的应用。 首先,让我们深入了解一下C/S(客户端/服务器)架构。在这种架构中,...

    ASP.NET基于局域网的信息收发系统的设计与实现(源代码+论文)

    在这个“ASP.NET基于局域网的信息收发系统的设计与实现”项目中,开发者利用ASP.NET的强大功能,创建了一个专用于局域网内的信息传递平台。这个系统能够高效地在局域网内的用户之间进行数据交换,提供了便捷的信息...

    基于UDP局域网内聊天工具

    【基于UDP局域网内聊天工具】是一种网络通信应用程序,专为在局域网环境下的用户提供即时通讯功能。它利用用户数据报协议(UDP)进行数据传输,这是一种无连接的、不可靠的传输协议,其特点是速度快、延迟低,特别...

    局域网通信软件.doc

    随着计算机技术的发展,局域网内的数据交换需求日益增长,传统的文件传输方法如使用移动存储设备拷贝,不仅耗时且容易传播计算机病毒。此外,操作系统内置的数据共享功能往往操作复杂,不便于普通用户使用。因此,...

Global site tag (gtag.js) - Google Analytics