本文主要是介绍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对象。
...
m_UDP = GenesisCore.InterfaceFactory.CreateGenesisUDP( " ChatServer " );
注意如何使用InterfaceFactory类来创建一个Genesis的实例。
使用GetLocalAddresses方法来返回一个本地IP地址的列表,把这个列表填到聊天服务器程序的一个ComboBox中。
...
要处理Genesis通讯事件,必须按下面各行注册事件处理:
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属性来得到认证信息。
... {
...
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.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和相应数据段的命令包。在这个例子中,数据段包含着昵称和服务器密码。
... {
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:当收到一个远程节点发来的连接请求时被调用,还记得在聊天服务器中我们用它来认证客户端么。聊天客户端不能接受连接请求,所以这里用一小段程序来拒绝连接并发回一个原因。如果客户端没有注册这个事件,连接仍然会被拒绝,但是就不会返回一个拒绝原因。
... {
//Clients don't accept connections.
e.AllowConnection = false;
e.DisallowReason = "Can't connect directly to a chat client";
}
一个关键是从客户端如何建立连接,在聊天客户端中它是按如下代码开始的:
/// 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方法被断开。
注意建立连接是一个异步操作,要得知连接是否被成功建立需要使用两个事件,OnConnectionRequestTimedOut和OnConnectionStateChanged。如果连接请求超时,前者会被调用。如果试图连接成功,后者会被调用,参数中会表明一个连接被建立。这两个事件对可以获得远程节点的地址,以及在RequestConnect方法中生成的请求ID,这样可以根据不同的连接来不同处理。这在GenesisChatClient项目中可以看到。
向远程节点发送数据
Genesis把这个功能包装的非常容易使用。IConnection对象有两个方法,如下所示:
int SendReliableCommand( byte flags, string opcode, string [] fields);
这两个方法可以向IConnection对象相应的远程节点发送一个命令包。操作码和数据段可以是任何数据,flags的有效值如下所示(在Constants.cs中)
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 SendReliableCommandToAll(BroadcastFilter filter, byte flags, string opcode, string [] fields);
这两个方法和上面的两个工作的一模一样,只不过他们增加了一个广播过滤标志,在Constants.cs中定义如下:
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 。
这篇关于Genesis UDP 服务端 和 客户端的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!