在我之前的一篇文章里曾经介绍过FluorineFx的ApplicationAdapter的基本步骤,以及使用FluorineFx的ApplicationAdapter来开发及时通信应用的相关知识点。ApplicationAdapter应用最终需要部署到支持FluorineFx服务的Web应用(ASP.NET网站)上,如下图则表示了FluorineFx应用的目录结构。
对于使用过FluorineFx开发应用的朋友来说,这个图在熟悉不过了。要使用FluorineFx开发及时通信应用,项目结构就必须这样搭建,首先看看本篇案例的解决方案截图:
从上面的解决方案截图可以看到,整个项目由两部分构成,一个是FluorineFx的类库,封装了远程服务(RemotingService)接口和及时通信应用程序(ApplicationAdapter)。另一个则是FluorineFx网站(和ASP.NET网站没多大的区别,不同的是其中加入了FluorineFx的一些相关配置)。
为了方便演示案例本篇就直接使用的 Access做为数据库,根据数据库的字段结构建立了一个数据传输对象(DTO),同时为该DTO对象标记[FluorineFx.TransferObject],以使其可以作为FluorineFx的数据传输对象使用。
{
/// <summary>
/// 用户信息(数据传输对象)
/// </summary>
[FluorineFx.TransferObject]
public class UserInfo
{
/// <summary>
/// 构造方法
/// </summary>
public UserInfo() { }
public int ID { get ; set ; }
public string UserName { get ; set ; }
public string NickName { get ; set ; }
public string Password { get ; set ; }
public string HeadImage { get ; set ; }
}
}
远程服务接口(DataService)提供了用户注册,登陆等最基本的通信接口方法,在了解通信接口之前首先学习一个工具类,该类的提供了一个将DataTable类型数据转话为IList<Object>类型返回,详细代码如下:
{
public static class ConvertUtils
{
/// <summary>
/// 提供将DataTable类型对象转换为List集合
/// </summary>
/// <param name="table"></param>
/// <returns></returns>
public static List < T > ConvertToList < T > (DataTable table) where T : new ()
{
// 置为垃圾对象
List < T > list = null ;
if (table != null )
{
DataColumnCollection columns = table.Columns;
int columnCount = columns.Count;
T type = new T();
Type columnType = type.GetType();
PropertyInfo[] properties = columnType.GetProperties();
if (properties.Length == columnCount)
{
list = new List < T > ();
foreach (DataRow currentRow in table.Rows)
{
for ( int i = 0 ; i < columnCount; i ++ )
{
for ( int j = 0 ; j < properties.Length; j ++ )
{
if (columns[i].ColumnName == properties[j].Name)
{
properties[j].SetValue(type, currentRow[i], null );
}
}
}
list.Add(type);
type = new T();
}
}
else
{
list = null ;
}
}
else
{
throw new ArgumentNullException( " 参数不能为空 " );
}
return list;
}
}
}
下面是使用FluorineFx提供的远程服务接口的详细定义:
{
[RemotingService]
public class DataService
{
public DataService()
{
}
/// <summary>
/// 用户注册
/// </summary>
/// <param name="info"></param>
/// <returns></returns>
public bool Register(UserInfo info)
{
info.Password = FormsAuthentication.HashPasswordForStoringInConfigFile(info.Password, " MD5 " );
StringBuilder CommandText = new StringBuilder( " insert into UserInfo values ( " );
CommandText.Append( string .Format( " '{0}','{1}','{2}','{3}') " , info.UserName, info.Password, info.NickName, info.HeadImage));
try
{
int row = DataBase.ExecuteSQL(CommandText.ToString());
if (row > 0 )
return true ;
return false ;
}
catch (Exception ex)
{
return false ;
}
}
/// <summary>
/// 用户登录
/// </summary>
/// <param name="info"></param>
/// <returns></returns>
public UserInfo Login(UserInfo info)
{
info.Password = FormsAuthentication.HashPasswordForStoringInConfigFile(info.Password, " MD5 " );
StringBuilder CommandText = new StringBuilder( " select * from UserInfo where " );
CommandText.Append( string .Format( " UserName='{0}' and Password='{1}' " , info.UserName, info.Password));
try
{
DataTable table = DataBase.ExecuteQuery(CommandText.ToString());
if (table != null )
{
return ConvertUtils.ConvertToList < UserInfo > (table)[ 0 ];
}
return null ;
}
catch (Exception ex)
{
return null ;
}
}
}
}
FluorineFx.Messaging.Adapter.ApplicationAdapter做为新的应用程序的基类,提供了客户端与应用程序之间通信的接口方法、数据流服务以及共享对象服务等。它能够时时处理应用程序的启动、停止、客户端的连接和断开等及时操作。
在本篇案例项目中,我所建立的ApplicationAdapter为ChatRoomApplication,用来提供启动及时应用服务器,接受客户端连接和断开以及视频流服务等功能。这里需要注意一点,在接受客户端连接的时候业务处理流程,在应用程序启动的时候就将会创建一个共享对象,用来保存在线用户列表。
{
// 用户共享对象
CreateSharedObject(application, " OnLineUsers " , false );
return base .AppStart(application);
}
当用户通过客户端连接服务器的时候,将用户信息传递到服务器,服务器端接受到数据后将会对用户数据进行验证(验证用户名和密码是否匹配),如果验证失败则直接返回false表示连接服务器失败,如果验证通过则设置当前连接的属性并连接到共享对象,然后取出共享对象里的在线用户列表数据,判断当前连接到服务器的用户是否在线,如果用户已在线则返回false表示用户登录失败,并给于提示当前用户已经登录。如果用户没有存在于共享里则表示当前连接的用户没有登录,那么就将当前连接的用户信息写入共享对象,客户端会通过异步事件处理函数接收到最新的在线用户列表数据。
{
UserInfo info = ((Dictionary < string , object > )parameters[ 0 ])[ " 0 " ] as UserInfo;
connection.Client.SetAttribute( " UserName " , info.UserName);
// 连接到共享对
ISharedObject users = GetSharedObject(connection.Scope, " OnLineUsers " );
if (users == null )
{
this .CreateSharedObject(connection.Scope, " OnLineUsers " , false );
users = GetSharedObject(connection.Scope, " OnLineUsers " );
}
else
{
List < UserInfo > onLineUserList = users.GetAttribute( " UserInfo " ) as List < UserInfo > ;
if (onLineUserList != null )
{
List < UserInfo > tempList = (from u in onLineUserList
where u.UserName == info.UserName
select u
).ToList();
if (tempList.Count > 0 )
{
// Server RPC
this .CallClientMethod(connection, " onLogined " , " 当前用户已经登录 " );
return false ;
}
else
{
onLineUserList.Add(info);
users.SetAttribute( " UserInfo " , onLineUserList);
}
}
else
{
onLineUserList = new List < UserInfo > { info };
users.SetAttribute( " UserInfo " , onLineUserList);
}
}
return base .AppConnect(connection, parameters);
}
OK,到这里就完成了对用户进行有效验证和接受与拒绝连接的功能,那么如果是用户下线了或是无意之间关闭了浏览器呢?这种情况应当如何去处理,如果不处理的话那么用户是不是会一直卡在线上呢?答案是肯定的。FluorineFx为此也提供了接口来处理用户下线的操作,那么在这里我们需要做什么呢?需要做的就是取出当前退出系统的客户端连接属性值,然后在取出共享对象里的在线用户列表数据,根据用户连接时所设置的属性进行循环判断,找到当前退出系统的用户数据,最后将他从共享对象里删除,服务器端的共享对象一但改变,所有连接到服务器的客户端都会通过共享对象的异步事件得到最新的数据,用来更新客户端的显示列表。
{
string userName = connection.Client.GetAttribute( " UserName " ) as string ;
ISharedObject users = GetSharedObject(connection.Scope, " OnLineUsers " );
if (users != null )
{
List < UserInfo > onLineUserList = users.GetAttribute( " UserInfo " ) as List < UserInfo > ;
if (onLineUserList != null )
{
List < UserInfo > tempList = onLineUserList.FindAll(c => c.UserName.Length > 0 );
foreach (var item in tempList)
{
if (item.UserName.Equals(userName))
{
onLineUserList.Remove(item);
}
}
users.SetAttribute( " UserInfo " , onLineUserList);
}
}
base .AppDisconnect(connection);
}
到这里服务器端的开发就基本完成了,现在就需要将这个ApplicationAdapter配置好,然后启动通过网站启动服务,Flex或者是Flash就可以通过rtmp协议连接了。首先需要配置的是rtmp协议通信信道:
< endpoint uri ="rtmp://{server.name}:2777" class ="flex.messaging.endpoints.RTMPEndpoint" />
< properties >
< idle-timeout-minutes > 20 </ idle-timeout-minutes >
</ properties >
</ channel-definition >
然后就是在应用目录下面配置ApplicationAdapter,指定当前这个应用又那一个Adapter来处理。建立app.config文件进行如下配置:
< configuration >
< application-handler type ="ChatRoom.Services.ChatRoomApplication" />
</ configuration >
在下一篇中我将详细介绍客户端的开发,包括使用RTMP协议连接到服务器,使用远程共享对象做在线用户列表和聊天消息发送,以及通过NetStream建立视频流连接实现视频聊天等相关功能。