Genesis UDP 服务端 和 客户端

来源:_侧卫基地_CSDN分基地 http://blog.csdn.net/flankerfc/archive/2007/08/18/1749246.aspx

下载源代码(104KB):http://www.codeproject.com/cs/internet/Genesis/genesis.zip 

原文地址:http://www.codeproject.com/cs/internet/Genesis.asp

简介

屏幕截图

Genesis UDP 项目是一个使用.NET Sockets来实现的轻量级UDP服务端和客户端的类库,它使用UDP以使得在网络上传输的数据量较低,并且提供了简单加密、有序传输和可靠链路的特色。

Genesis通过“命令包”来相互通讯,命令包是一个或多个UDP包,它包括2字节的操作码和多个的字符串数据段。不可靠包可以最多达到512字节大小,而不限长度的可靠包会被Genesis分割来按序传输。Genesis中自定义了一些内置的操作码,除此之外,包、操作码和数据段如何来处理完全由开发者自己来决定。

Genesis还提供了可选的加密系统,并不是很高级,但是可以对每一个客户端提供一个随机的320位长的key,并在一个异或加密算法中使用这个密钥。如果初始化连接的包没有被别人监测到,这样还是相对安全的,因为每个客户端的密钥是不一样的。在类库中也可以加入一些公钥私钥加密,还可以选择哪些连接需要加密或者不需要加密。

服务端和客户端

Genesis工作的基础是每一个节点都可以作为一个客户端或者一个服务端。这两者之间的界限是模糊的,任何使用Genesis的程序都可以连接一个服务端或者接受来自客户端的连接。当然,仅使用默认连接是没有任何功能的,在程序中必须为连接加入相应的事件处理。服务端是一个可以接受连接的远程主机,客户端则是一个可以发起连接的远程主机。因为一个Genesis实例即可以发起也可以接受连接,所以它即可以做服务端也可以做客户端。服务端和客户端统称为节点。

结构图

上图表示了Genesis中的服务端和客户端是如何工作的。每一个方框表示一个Genesis类库的实例,没有一个特定的客户端或者服务端,每个节点都可以发起连接或接受连接。只有根据Genesis实例的内涵来定义服务端和客户端。我们看“Genesis1”,它连接到了服务端2和服务端3,并且4作为一个客户端和它连接,这是由箭头的方向判断的(箭头表明是谁发起的连接)。如果我们看“Genesis2”,它有两个客户端:1和3。如果我们看“Genesis4”,它有一个服务端“Genesis1”。注意,根据环境不同,“Genesis1”即可以做为服务端也可以作为客户端。

背景

Genesis的概念是源自联机第一人称射击游戏的网络协议上,比如Quake和Half-Life,它们使用UDP在服务器和连接的客户端上进行快速通讯。这些实现都可以发送可靠的有序的数据,Genesis也实现了这一点。Genesis的原本意图是作为一个用.NET开发的游戏引擎的网络通讯API,现在作为一个独立项目提供给大家使用。

使用代码

这里有三个肯定要用到的接口,它们是:

  • IConnection
  • ICommand
  • IGenesisUDP

IConnection对象包含有一个远程连接的信息,不论是对服务器还是客户端的。ICommand包含一个命令包的信息,包括操作码和数据段。IGenesisUDP是一个实际通讯的类来实现具体的功能。

在源代码中包含有两个项目“GenesisChatServer”和“GenesisChatClient”,这两个项目实现了一个网络聊天系统。用这两个项目可以帮助我们更好的来理解Genesis类库。

建立服务端

让我们来看GenesisChatServer,这个项目说明了Genesis如何来作为一个服务端来给其他客户端提供服务。

首先,我们需要在程序中声明并建立一个Genesis对象。

private  GenesisCore.IGenesisUDP m_UDP;

... 

m_UDP 
=  GenesisCore.InterfaceFactory.CreateGenesisUDP( " ChatServer " );

注意如何使用InterfaceFactory类来创建一个Genesis的实例。

使用GetLocalAddresses方法来返回一个本地IP地址的列表,把这个列表填到聊天服务器程序的一个ComboBox中。

string [] addresses  =  m_UDP.GetLocalAddresses( );
...

要处理Genesis通讯事件,必须按下面各行注册事件处理:

// Hook genesis core events
m_UDP.OnListenStateChanged  +=   new  ListenHandler(m_UDP_OnListenStateChanged);
m_UDP.OnConnectionAuth 
+=   new  ConnectionAuthHandler(m_UDP_OnConnectionAuth);
m_UDP.OnCommandReceived 
+=   new  IncomingCommandHandler(m_UDP_OnCommandReceived);
m_UDP.OnConnectionStateChanged 
+=   new  
   ConnectionStateChangeHandler(m_UDP_OnConnectionStateChanged);

OnListenStateChanged:当Genesis通讯状态改变时会被调用,它有两种状态:“Listen”和“Closed”,表示开启和关闭通讯。如果Closed,Genesis会断开所有的远程连接并关闭Socket。

OnConnectionAuth:当一个客户端发来认证信息时会被调用,如果登陆信息不被接受,那么可以拒绝该客户端。在这个聊天服务器例子中,如果发来的客户端昵称太短或者服务器密码不匹配,那么就会拒绝客户端。注意如何给客户端发回一个拒绝原因。通过修改客户端发来的ConnectionAuthEventArgs对象来控制,通过它的AuthCommand属性来得到认证信息。

 

private   void  OnConnectionAuth( object  o, ConnectionAuthEventArgs e)
{
    ... 
 
    
if(e.AuthCommand.Fields[1].Length < 3)
      
{
        e.AllowConnection 
= false;
        e.DisallowReason 
= "Nickname too short.";
        
return;
      }

      
else if(e.AuthCommand.Fields[1].Length > 15)
      
{
        e.AllowConnection 
= false;
        e.DisallowReason 
= "Nickname too long.";
        
return;
        }


    ...
}

OnCommandReceived:当收到一个远程节点发来的命令时会被调用。这个事件的CommandEventArgs可以来获得ICommand对象(通过它的SentCommand属性),包含有发来命令的相关信息以及操作码和数据段。也可以获得发送命令的远程主机的相关信息(通过它的Sender属性)。这个事件只会在认证的节点上产生,聊天服务器通过这个事件来处理接受的聊天消息和请求用户列表。

OnConnectionStateChanged:当与一个远程节点连接或断开连接时会被调用。EventArgs包含有远程节点的信息、连接建立还是断开以及一个断开的原因。聊天服务器使用这个事件来发送用户列表给新连接的客户端。

建立客户端

现在让我们来看这个聊天系统的客户端“GenesisChatClient”,它和服务端程序很相似,先建立一个Genesis实例并注册事件,不过有些新的事件需要注册:

m_UDP.OnListenStateChanged  +=   new  ListenHandler(m_UDP_OnListenStateChanged);
m_UDP.OnLoginRequested 
+=   new  SendLoginHandler(m_UDP_OnLoginRequested);
m_UDP.OnAuthFeedback 
+=   new  AuthenticatedHandler(m_UDP_OnAuthFeedback);
m_UDP.OnConnectionStateChanged 
+=   new  
  ConnectionStateChangeHandler(m_UDP_OnConnectionStateChanged);
m_UDP.OnCommandReceived 
+=   new  IncomingCommandHandler(m_UDP_OnCommandReceived);
m_UDP.OnConnectionAuth 
+=   new  ConnectionAuthHandler(m_UDP_OnConnectionAuth);
m_UDP.OnSocketError 
+=   new  SocketErrorHandler(m_UDP_OnSocketError);
m_UDP.OnConnectionRequestTimedOut 
+=   new  
  RequestTimedOutHandler(m_UDP_OnConnectionRequestTimedOut);

OnLoginRequested:当服务端请求发送登陆信息时被调用。客户端必须发回一个操作码是OPCODE_LOGINDETAILS和相应数据段的命令包。在这个例子中,数据段包含着昵称和服务器密码。

private   void  OnLoginRequested( object  o, LoginSendEventArgs e)
{
    
if(e.Connected)
    
{
        e.ServerConnection.SendUnreliableCommand(
0
               GenesisConsts.OPCODE_LOGINDETAILS, 
               
new string[] {txtServerPW.Text, txtNickName.Text} );
        spState.Text 
= "Sending login details...";
    }

    
else
    
{
        spState.Text 
= "Connection rejected - " + e.Reason;
    }

}

如果服务器没有接受连接,LoginSendEventArgs对象的Connected属性是false,比如服务器已经到了最大服务能力。在Reason属性中可以得到拒绝的原因。

OnAuthFeedback:当服务端决定接受或拒绝登陆时会被调用。EventArgs包含一个表示是否成功登陆的值,和一个登陆失败的原因。

OnConnectionAuth:当收到一个远程节点发来的连接请求时被调用,还记得在聊天服务器中我们用它来认证客户端么。聊天客户端不能接受连接请求,所以这里用一小段程序来拒绝连接并发回一个原因。如果客户端没有注册这个事件,连接仍然会被拒绝,但是就不会返回一个拒绝原因。

 

private   void  m_UDP_OnConnectionAuth( object  o, ConnectionAuthEventArgs e)
{
    
//Clients don't accept connections.
    e.AllowConnection = false;
    e.DisallowReason 
= "Can't connect directly to a chat client";
}

 

一个关键是从客户端如何建立连接,在聊天客户端中它是按如下代码开始的:

/// <summary>
/// Connect to server button was clicked
/// </summary>

private   void  btnConnect_Click( object  sender, System.EventArgs e)
{
    server_ip 
= txtServerIP.Text;
    m_UDP.RequestConnect(
ref server_ip, 
       Convert.ToInt32(txtServerPort.Text), 
       
out server_req_id);
    spState.Text 
= "Connecting...";
    btnConnect.Enabled 
= true;
}

方法RequestConnect初始化连接。注意服务器IP是ref传入的,这是因为有可能传入的字符串是一个主机名,然后这个字符串会被改为实际解析出来的IP地址。这样可以使程序方便的使用主机名解析,并且Genesis的其它部分可以使用IP地址。还要注意的是最后一个参数,这是一个out参数,返回的是这个连接的请求ID。这个请求ID是唯一的,来标示不同的连接请求。连接可以通过CancelConnect方法被断开。

注意建立连接是一个异步操作,要得知连接是否被成功建立需要使用两个事件,OnConnectionRequestTimedOutOnConnectionStateChanged。如果连接请求超时,前者会被调用。如果试图连接成功,后者会被调用,参数中会表明一个连接被建立。这两个事件对可以获得远程节点的地址,以及在RequestConnect方法中生成的请求ID,这样可以根据不同的连接来不同处理。这在GenesisChatClient项目中可以看到。


向远程节点发送数据

Genesis把这个功能包装的非常容易使用。IConnection对象有两个方法,如下所示:

int  SendUnreliableCommand( byte  flags,  string  opcode,  string [] fields);
int  SendReliableCommand( byte  flags,  string  opcode,  string [] fields);

这两个方法可以向IConnection对象相应的远程节点发送一个命令包。操作码和数据段可以是任何数据,flags的有效值如下所示(在Constants.cs中)

// Command packet flags
public   static   byte  FLAGS_NONE  =   0 ;
public   static   byte  FLAGS_CONNECTIONLESS  =   1 ;
public   static   byte  FLAGS_ENCRYPTED  =   2 ;
public   static   byte  FLAGS_COMPOUNDPIECE  =   4 ;
public   static   byte  FLAGS_COMPOUNDEND  =   8 ;
public   static   byte  FLAGS_RELIABLE  =   16 ;
public   static   byte  FLAGS_SEQUENCED  =   32 ;

在Constants.cs记录的有效flags中,只有一个需要在调用方法时被手工使用,即FLAGS_SEQUENCED,它表示在不可靠包中的有序性。其他的flags都是被Genesis自动加入的,并不需要在程序中改变。

也可以向多个远程节点广播一个命令包,使用下面列出的IGenesisUDP接口的方法:

int  SendUnreliableCommandToAll(BroadcastFilter filter,  byte  flags,  string  opcode,  string [] fields);
int  SendReliableCommandToAll(BroadcastFilter filter,  byte  flags,  string  opcode,  string [] fields);

这两个方法和上面的两个工作的一模一样,只不过他们增加了一个广播过滤标志,在Constants.cs中定义如下:

[Flags]
public   enum  BroadcastFilter :  int
{
    None     
= 0,  //Filter out everything
    Servers  = 1,  //Send to servers we are connected to.
    Clients  = 2,  //Send to clients connected to us.
    All      = Servers | Clients,
                  
//Send to both servers
                  
//and clients (every connection).
    AuthedOnly = 4,//Only send to authed clients or servers we are authed with.
}

这样可以限定广播给服务端、客户端或者那些成功认证的节点。

兴趣点

需要非常注意的是Genesis中的事件都是在一个单独的线程中调用的,而并不是UI线程。这表示如果要改变UI元素的话,需要使用Invoke方法来传送给UI线程。这个聊天程序已经使用此技术来改变UI。

当调用StopListen方法时,Genesis自动地发送一个断开连接的命令包给所有连接的远程节点,通知它们自己已经关闭。

在整个Genesis中唯一一个可以接受主机名作参数的是RequestConnect方法。其他的方法都需要显式的IP地址。

历史

v1.00 - 初次修订

关于原作者

Rob Harwood 在这里查看他的档案 http://www.codeproject.com/script/profile/whos_who.asp?vt=arts&id=1770761

关于我 

这是小弟第一次发表技术文章,虽然是翻译的,而且自己水平有限,不论是技术还是英文都需要努力,希望大家多多指正,有什么错误还请留言说明,或发邮箱联系:flankerfc at gmail dot com 。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Genesis Python是一种基于Python编程语言开发的开源框架。它旨在提供一种高效且易于使用的方式来构建和管理复杂的软件项目。Genesis Python的设计理念是简洁、可扩展、高性能和易维护。 Genesis Python框架提供了一系列的工具和库,用于快速开发各种类型的应用程序,包括Web应用、数据分析应用、机器学习和人工智能等。它提供了一套丰富的模块和插件,可以加快开发过程,减少重复劳动,提高生产效率。 Genesis Python的特点之一是它的简洁性。它使用清晰而简洁的代码结构,使得开发者可以更容易地理解和维护项目。同时,它提供了大量的文档和示例,帮助开发者快速上手并使用框架进行开发。 另一个重要的特点是Genesis Python的可扩展性。它采用模块化的架构,允许开发者根据实际需要选择和使用所需的模块和插件。这样可以避免项目中不必要的依赖,降低系统的复杂性,并提高系统的灵活性和可扩展性。 Genesis Python还注重高性能和可靠性。它通过优化代码和使用高效的算法来提高系统的运行效率。同时,它提供了强大的错误处理和调试工具,使开发者能够更容易地检测和修复问题。 总的来说,Genesis Python是一个功能强大且易于使用的开源框架。它通过提供一套丰富的工具和库,简化了复杂项目的开发过程,提高了开发效率和代码质量。无论是初学者还是有经验的开发者,都可以从Genesis Python中受益。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值