介绍开源的.net通信框架NetworkComms框架 源码分析(十八 ) ConnectionListenerBase
原文网址: http://www.cnblogs.com/csdev
Networkcomms 是一款C# 语言编写的TCP/UDP通信框架 作者是英国人 以前是收费的 目前作者已经开源 许可是:Apache License v2
开源地址是:https://github.com/MarcFletcher/NetworkComms.Net
连接监听器基类
/// <summary>
/// A base class that the listener of each connection type inherits from.
/// This allows NetworkComms.Net to manage listeners at the general connection level.
/// 连接监听器基类
/// </summary>
public abstract class ConnectionListenerBase
{
#region Public Properties
/// <summary>
/// The send receive options associated with this listener.
/// 监听器使用的收发参数类
/// </summary>
public SendReceiveOptions ListenerDefaultSendReceiveOptions { get; protected set; }
/// <summary>
/// The connection type that this listener supports.
/// 连接类型
/// </summary>
public ConnectionType ConnectionType { get; protected set; }
/// <summary>
/// The application layer protocol status for this listener.
/// 应用层协议
/// </summary>
public ApplicationLayerProtocolStatus ApplicationLayerProtocol { get; protected set; }
/// <summary>
/// True if this listener is listening.
/// 是否在监听中
/// </summary>
public bool IsListening { get; protected set; }
/// <summary>
/// True if this listener will be advertised via peer discovery
/// 设置为True 则可以通过Peer discovery被发现
/// </summary>
public bool IsDiscoverable { get; protected set; }
/// <summary>
/// The local IPEndPoint that this listener is associated with.
/// 本地IP端点
/// </summary>
public EndPoint LocalListenEndPoint { get; protected set; }
#endregion
#region Private Properties
/// <summary>
/// Thread safety locker which is used when accessing <see cref="incomingPacketHandlers"/>
/// and <see cref="incomingPacketUnwrappers"/>
/// 同步锁 用于线程安全
/// </summary>
private object delegateLocker = new object();
/// <summary>
/// By default all incoming objects are handled using ListenerDefaultSendReceiveOptions. Should the user want something else
/// those settings are stored here
///字典 用于存储 ( 消息类型与 数据包展开器)
/// 数据包展开器的作用是 对应消息类型与收发参数类
/// </summary>
private Dictionary<string, PacketTypeUnwrapper> incomingPacketUnwrappers = new Dictionary<string, PacketTypeUnwrapper>();
/// <summary>
/// A listener specific incoming packet handler dictionary. These are called before any global handlers
///字典 用于存储 ( 消息类型 与对应的处理器列表)
/// </summary>
private Dictionary<string, List<IPacketTypeHandlerDelegateWrapper>> incomingPacketHandlers = new Dictionary<string, List<IPacketTypeHandlerDelegateWrapper>>();
#endregion
/// <summary>
/// Create a new listener instance
/// 创建一个监听器实例
/// </summary>
/// <param name="connectionType">连接类型 The connection type to listen for.</param>
/// <param name="sendReceiveOptions">收发参数 The send receive options to use for this listener</param>
/// <param name="applicationLayerProtocol">应用层协议 If enabled NetworkComms.Net uses a custom
/// application layer protocol to provide useful features such as inline serialisation,
/// transparent packet transmission, remote peer handshake and information etc. We strongly
/// recommend you enable the NetworkComms.Net application layer protocol.</param>
/// <param name="allowDiscoverable">是否允许被其他端点发现 Determines if the newly created <see cref="ConnectionListenerBase"/> will be discoverable if <see cref="Tools.PeerDiscovery"/> is enabled.</param>
protected ConnectionListenerBase(ConnectionType connectionType,
SendReceiveOptions sendReceiveOptions,
ApplicationLayerProtocolStatus applicationLayerProtocol,
bool allowDiscoverable)
{
if (connectionType == ConnectionType.Undefined) throw new ArgumentException("ConnectionType.Undefined is not valid when calling this method.", "connectionType");
if (sendReceiveOptions == null) throw new ArgumentNullException("sendReceiveOptions", "Provided send receive option may not be null.");
if (applicationLayerProtocol == ApplicationLayerProtocolStatus.Undefined) throw new ArgumentException("ApplicationLayerProtocolStatus.Undefined is not valid when calling this method.", "applicationLayerProtocol");
//Validate SRO options if the application layer protocol is disabled
//如果应用层协议禁用 验证收发参数类
if (applicationLayerProtocol == ApplicationLayerProtocolStatus.Disabled)
{
if (sendReceiveOptions.Options.ContainsKey("ReceiveConfirmationRequired"))
throw new ArgumentException("Attempted to create an unmanaged connection when the provided send receive" +
" options specified the ReceiveConfirmationRequired option. Please provide compatible send receive options in order to successfully" +
" instantiate this unmanaged connection.", "sendReceiveOptions");
if (sendReceiveOptions.DataSerializer != DPSManager.GetDataSerializer<NullSerializer>())
throw new ArgumentException("Attempted to create an unmanaged connection when the provided send receive" +
" options serialiser was not NullSerializer. Please provide compatible send receive options in order to successfully" +
" instantiate this unmanaged connection.", "sendReceiveOptions");
)
throw new ArgumentException("Attempted to create an unmanaged connection when the provided send receive" +
" options contains data processors. Data processors may not be used with unmanaged connections." +
" Please provide compatible send receive options in order to successfully instantiate this unmanaged connection.", "sendReceiveOptions");
}
if (NetworkComms.LoggingEnabled) NetworkComms.Logger.Info("Created new connection listener (" + connectionType.ToString() + "-" + (applicationLayerProtocol == ApplicationLayerProtocolStatus.Enabled ? "E" : "D") + ").");
this.ConnectionType = connectionType;
this.ListenerDefaultSendReceiveOptions = sendReceiveOptions;
this.ApplicationLayerProtocol = applicationLayerProtocol;
this.IsDiscoverable = allowDiscoverable;
}
/// <summary>
/// Returns a clean string containing the current listener state
/// 返回当前监听器的监听状态
/// </summary>
/// <returns></returns>
public override string ToString()
{
string returnString = "[" + ConnectionType.ToString() + "-" + (ApplicationLayerProtocol == ApplicationLayerProtocolStatus.Enabled ? "E" : "D") + "] ";
if (IsListening && LocalListenEndPoint != null)
return returnString + "Listening - " + LocalListenEndPoint.ToString();
else
return returnString + "Not Listening";
}
#region Start and Stop Listening
/// <summary>
/// Start listening for incoming connections.
/// 开始监听进入的连接
/// </summary>
/// <param name="desiredLocalListenEndPoint">Try to start listening on this EndPoint.</param>
/// <param name="useRandomPortFailOver">If the request EndPoint is unavailable fail over to a random port.</param>
internal abstract void StartListening(EndPoint desiredLocalListenEndPoint, bool useRandomPortFailOver);
/// <summary>
/// Stop listening for incoming connections.
/// 停止监听
/// </summary>
internal abstract void StopListening();
#endregion
#region Listener Specific Packet Handlers
/// <summary>
/// Append a listener specific packet handler using the listener default SendReceiveOptions
/// 添加一个监听器指定的数据包处理器 使用默认的收发参数
/// </summary>
/// <typeparam name="incomingObjectType">The type of incoming object</typeparam>
/// <param name="packetTypeStr">The packet type for which this handler will be executed</param>
/// <param name="packetHandlerDelgatePointer">The delegate to be executed when a packet of packetTypeStr is received</param>
public void AppendIncomingPacketHandler<incomingObjectType>(string packetTypeStr, NetworkComms.PacketHandlerCallBackDelegate<incomingObjectType> packetHandlerDelgatePointer)
{
AppendIncomingPacketHandler<incomingObjectType>(packetTypeStr, packetHandlerDelgatePointer, ListenerDefaultSendReceiveOptions);
}
/// <summary>
/// Append a listener specific packet handler
/// 添加一个监听器指定的数据包处理器 使用指定的收发参数
/// </summary>
/// <typeparam name="incomingObjectType">进入的数据 The type of incoming object</typeparam>
/// <param name="packetTypeStr">消息类型 The packet type for which this handler will be executed</param>
/// <param name="packetHandlerDelgatePointer">处理器 The delegate to be executed when a packet of packetTypeStr is received</param>
/// <param name="options">收发参数 The <see cref="SendReceiveOptions"/> to be used for the provided packet type</param>
public void AppendIncomingPacketHandler<incomingObjectType>(string packetTypeStr, NetworkComms.PacketHandlerCallBackDelegate<incomingObjectType> packetHandlerDelgatePointer, SendReceiveOptions options)
{
if (packetTypeStr == null) throw new ArgumentNullException("packetTypeStr", "Provided packetType string cannot be null.");
if (packetHandlerDelgatePointer == null) throw new ArgumentNullException("packetHandlerDelgatePointer", "Provided NetworkComms.PacketHandlerCallBackDelegate<incomingObjectType> cannot be null.");
if (options == null) throw new ArgumentNullException("options", "Provided SendReceiveOptions cannot be null.");
//If we are adding a handler for an unmanaged packet type the data serializer must be NullSerializer
//Checks for unmanaged packet types
//如果我们给“非托管”的数据类型添加序列化器,必须是NullSerializer
//检查“非托管”数据包类型 “非托管”主要在与其他语言的通信场景中使用
if (packetTypeStr == Enum.GetName(typeof(ReservedPacketType), ReservedPacketType.Unmanaged))
{
if (options.DataSerializer != DPSManager.GetDataSerializer<NullSerializer>())
throw new ArgumentException("Attempted to add packet handler for an unmanaged packet type when the provided send receive options serializer was not NullSerializer.");
)
throw new ArgumentException("Attempted to add packet handler for an unmanaged packet type when the provided send receive options contains data processors. Data processors may not be used inline with unmanaged packet types.");
}
lock (delegateLocker)
{
if (incomingPacketUnwrappers.ContainsKey(packetTypeStr))
{
//Make sure if we already have an existing entry that it matches with the provided
//如果收发参数前后不一致 抛出异常
if (!incomingPacketUnwrappers[packetTypeStr].Options.OptionsCompatible(options))
throw new PacketHandlerException("The provided SendReceiveOptions are not compatible with existing SendReceiveOptions already specified for this packetTypeStr.");
}
else
incomingPacketUnwrappers.Add(packetTypeStr, new PacketTypeUnwrapper(packetTypeStr, options));
//Ad the handler to the list
//添加处理器到列表中
if (incomingPacketHandlers.ContainsKey(packetTypeStr))
{
//Make sure we avoid duplicates
//确保不重复
PacketTypeHandlerDelegateWrapper<incomingObjectType> toCompareDelegate = new PacketTypeHandlerDelegateWrapper<incomingObjectType>(packetHandlerDelgatePointer);
bool delegateAlreadyExists = false;
foreach (var handler in incomingPacketHandlers[packetTypeStr])
{
if (handler == toCompareDelegate)
{
delegateAlreadyExists = true;
break;
}
}
if (delegateAlreadyExists)
throw new PacketHandlerException("This specific packet handler delegate already exists for the provided packetTypeStr.");
incomingPacketHandlers[packetTypeStr].Add(new PacketTypeHandlerDelegateWrapper<incomingObjectType>(packetHandlerDelgatePointer));
}
else
incomingPacketHandlers.Add(packetTypeStr, new List<IPacketTypeHandlerDelegateWrapper>() { new PacketTypeHandlerDelegateWrapper<incomingObjectType>(packetHandlerDelgatePointer) });
if (NetworkComms.LoggingEnabled) NetworkComms.Logger.Info("Added listener specific incoming packetHandler for '" + packetTypeStr + "' packetType on listener " + ToString());
}
}
/// <summary>
/// Append a listener specific unmanaged packet handler
/// 为“非托管”数据包添加处理器
/// </summary>
/// <param name="packetHandlerDelgatePointer">The delegate to be executed when an unmanaged packet is received</param>
public void AppendIncomingUnmanagedPacketHandler(NetworkComms.PacketHandlerCallBackDelegate<byte[]> packetHandlerDelgatePointer)
{
AppendIncomingPacketHandler<byte[]>(Enum.GetName(typeof(ReservedPacketType), ReservedPacketType.Unmanaged), packetHandlerDelgatePointer, new SendReceiveOptions<NullSerializer>());
}
/// <summary>
/// Returns true if an unmanaged packet handler exists on this listener
/// 如果“非托管”数据包存在对应的处理器 返回True
/// </summary>
/// <param name="packetTypeStr">The packet type for which to check incoming packet handlers</param>
/// <returns>True if a packet handler exists</returns>
public bool IncomingPacketHandlerExists(string packetTypeStr)
{
lock (delegateLocker)
return incomingPacketHandlers.ContainsKey(packetTypeStr);
}
/// <summary>
/// Returns true if a listener specific unmanaged packet handler exists, on this listener.
/// 如果监听器指定的“未托管”数据包处理器存在 返回True
/// </summary>
/// <returns>True if an unmanaged packet handler exists</returns>
public bool IncomingUnmanagedPacketHandlerExists()
{
lock (delegateLocker)
return incomingPacketHandlers.ContainsKey(Enum.GetName(typeof(ReservedPacketType), ReservedPacketType.Unmanaged));
}
/// <summary>
/// Returns true if the provided listener specific packet handler has been added for the provided packet type, on this listener.
/// 如果某消息类型 对应的处理器已经存在 返回True
/// </summary>
/// <param name="packetTypeStr">消息类型 The packet type within which to check packet handlers</param>
/// <param name="packetHandlerDelgatePointer">处理器 The packet handler to look for</param>
/// <returns>True if a listener specific packet handler exists for the provided packetType</returns>
public bool IncomingPacketHandlerExists(string packetTypeStr, Delegate packetHandlerDelgatePointer)
{
lock (delegateLocker)
{
if (incomingPacketHandlers.ContainsKey(packetTypeStr))
{
foreach (var handler in incomingPacketHandlers[packetTypeStr])
if (handler.EqualsDelegate(packetHandlerDelgatePointer))
return true;
}
}
return false;
}
/// <summary>
/// Returns true if the provided listener specific unmanaged packet handler has been added, on this listener.
/// 如果“未托管”数据包对应的处理器已经存在 返回True
/// </summary>
/// <param name="packetHandlerDelgatePointer">The packet handler to look for</param>
/// <returns>True if a listener specific unmanaged packet handler exists</returns>
public bool IncomingUnmanagedPacketHandlerExists(Delegate packetHandlerDelgatePointer)
{
lock (delegateLocker)
{
if (incomingPacketHandlers.ContainsKey(Enum.GetName(typeof(ReservedPacketType), ReservedPacketType.Unmanaged)))
{
foreach (var handler in incomingPacketHandlers[Enum.GetName(typeof(ReservedPacketType), ReservedPacketType.Unmanaged)])
if (handler.EqualsDelegate(packetHandlerDelgatePointer))
return true;
}
}
return false;
}
/// <summary>
/// Remove the provided listener specific packet handler for the specified packet type, on this listener.
/// 根据参数中的消息类型 删除相应的处理器
/// </summary>
/// <param name="packetTypeStr">消息类型 Packet type for which this delegate should be removed</param>
/// <param name="packetHandlerDelgatePointer">处理器 The delegate to remove</param>
public void RemoveIncomingPacketHandler(string packetTypeStr, Delegate packetHandlerDelgatePointer)
{
lock (delegateLocker)
{
if (incomingPacketHandlers.ContainsKey(packetTypeStr))
{
//Remove any instances of this handler from the delegates
//The bonus here is if the delegate has not been added we continue quite happily
//从委托列表中删除这个处理器的任何实例
//如果处理器尚未添加也很好
IPacketTypeHandlerDelegateWrapper toRemove = null;
foreach (var handler in incomingPacketHandlers[packetTypeStr])
{
if (handler.EqualsDelegate(packetHandlerDelgatePointer))
{
toRemove = handler;
break;
}
}
if (toRemove != null)
incomingPacketHandlers[packetTypeStr].Remove(toRemove);
)
{
incomingPacketHandlers.Remove(packetTypeStr);
//Remove any entries in the unwrappers dict as well as we are done with this packetTypeStr
//从字典中删除相应消息的处理器
if (incomingPacketHandlers.ContainsKey(packetTypeStr))
incomingPacketHandlers.Remove(packetTypeStr);
if (NetworkComms.LoggingEnabled) NetworkComms.Logger.Info("Removed a listener specific packetHandler for '" + packetTypeStr + "' packetType. No handlers remain on listener " + ToString());
}
else
if (NetworkComms.LoggingEnabled) NetworkComms.Logger.Info("Removed a listener specific packetHandler for '" + packetTypeStr + "' packetType. Handlers remain on listener " + ToString());
}
}
}
/// <summary>
/// Remove the provided listener specific unmanaged packet handler, on this listener.
/// 删除“为托管”数据包处理器
/// </summary>
/// <param name="packetHandlerDelgatePointer">The delegate to remove</param>
public void RemoveIncomingUnmanagedPacketHandler(Delegate packetHandlerDelgatePointer)
{
RemoveIncomingPacketHandler(Enum.GetName(typeof(ReservedPacketType), ReservedPacketType.Unmanaged), packetHandlerDelgatePointer);
}
/// <summary>
/// Removes all listener specific packet handlers for the provided packet type, on this listener.
/// 根据消息类型 删除处理器
/// </summary>
/// <param name="packetTypeStr">消息类型 Packet type for which all delegates should be removed</param>
public void RemoveIncomingPacketHandler(string packetTypeStr)
{
lock (delegateLocker)
{
//We don't need to check for potentially removing a critical reserved packet handler here because those cannot be removed.
if (incomingPacketHandlers.ContainsKey(packetTypeStr))
{
incomingPacketHandlers.Remove(packetTypeStr);
if (NetworkComms.LoggingEnabled) NetworkComms.Logger.Info("Removed all listener specific incoming packetHandlers for '" + packetTypeStr + "' packetType on listener " + ToString());
}
}
}
/// <summary>
/// Removes all unmanaged packet handlers, on this listener.
/// 删除所有的“为托管”数据包处理器
/// </summary>
public void RemoveIncomingUnmanagedPacketHandler()
{
RemoveIncomingPacketHandler(Enum.GetName(typeof(ReservedPacketType), ReservedPacketType.Unmanaged));
}
/// <summary>
/// Removes all packet handlers for all packet types, on this listener.
/// 删除所有的数据包处理器
/// </summary>
public void RemoveIncomingPacketHandler()
{
lock (delegateLocker)
{
incomingPacketHandlers = new Dictionary<string, List<IPacketTypeHandlerDelegateWrapper>>();
if (NetworkComms.LoggingEnabled) NetworkComms.Logger.Info("Removed all listener specific incoming packetHandlers for all packetTypes on listener " + ToString());
}
}
/// <summary>
/// Add all listener specific packet handlers to the provided connection
/// 添加所有的数据包处理器到指定的连接上
/// </summary>
/// <param name="connection">连接 The connection to which packet handlers should be added</param>
internal void AddListenerPacketHandlersToConnection(Connection connection)
{
lock (delegateLocker)
{
foreach (string packetType in incomingPacketHandlers.Keys)
{
foreach (IPacketTypeHandlerDelegateWrapper handler in incomingPacketHandlers[packetType])
connection.AppendIncomingPacketHandler(packetType, handler, incomingPacketUnwrappers[packetType].Options);
}
}
if (NetworkComms.LoggingEnabled) NetworkComms.Logger.Info("Appended connection specific packet handlers from listener '" + ToString() + "' to connection '" + connection.ToString() + "'.");
}
#endregion
}
介绍开源的.net通信框架NetworkComms框架 源码分析(十八 ) ConnectionListenerBase的更多相关文章
- DotNetty网络通信框架学习之源码分析
DotNetty网络通信框架学习之源码分析 有关DotNetty框架,网上的详细资料不是很多,有不多的几个博友做了简单的介绍,也没有做深入的探究,我也根据源码中提供的demo做一下记录,方便后期查阅. ...
- 深入理解分布式调度框架TBSchedule及源码分析
简介 由于最近工作比较忙,前前后后花了两个月的时间把TBSchedule的源码翻了个底朝天.关于TBSchedule的使用,网上也有很多参考资料,这里不做过多的阐述.本文着重介绍TBSchedule的 ...
- 设计模式(十五)——命令模式(Spring框架的JdbcTemplate源码分析)
1 智能生活项目需求 看一个具体的需求 1) 我们买了一套智能家电,有照明灯.风扇.冰箱.洗衣机,我们只要在手机上安装 app 就可以控制对这些家电工作. 2) 这些智能家电来自不同的厂家,我们不想针 ...
- 设计模式(二十一)——解释器模式(Spring 框架中SpelExpressionParser源码分析)
1 四则运算问题 通过解释器模式来实现四则运算,如计算 a+b-c 的值,具体要求 1) 先输入表达式的形式,比如 a+b+c-d+e, 要求表达式的字母不能重复 2) 在分别输入 a ,b, c, ...
- $Django cbv源码分析 djangorestframework框架之APIView源码分析
1 CBV的源码分析 #视图 class login (View): pass #路由 url(r'^books/$', views.login.as_view()) #阅读源码: #左侧工程栏--- ...
- ④NuPlayer播放框架之Renderer源码分析
[时间:2016-11] [状态:Open] [关键词:android,nuplayer,开源播放器,播放框架,渲染器,render] 0 导读 之前我们分析了NuPlayer的实现代码,本文将重点聚 ...
- ⑤NuPlayer播放框架之GenericSource源码分析
[时间:2017-01] [状态:Open] [关键词:android,nuplayer,开源播放器,播放框架,GenericSource] 0 导读 GenericSource是NuPlayer:: ...
- ③NuPlayer播放框架之类NuPlayer源码分析
[时间:2016-10] [状态:Open] [关键词:android,nuplayer,开源播放器,播放框架] 0 引言 差不多一个月了,继续分析AOSP的播放框架的源码.这次我们需要深入分析的是N ...
- Laravel开发:Laravel框架门面Facade源码分析
前言 这篇文章我们开始讲 laravel 框架中的门面 Facade,什么是门面呢?官方文档: Facades(读音:/fəˈsäd/ )为应用程序的服务容器中可用的类提供了一个「静态」接口.Lara ...
- Android 应用框架层 SQLite 源码分析
概述 Android 在应用框架层为开发者提供了 SQLite 相关操作接口,其归属于android.database.sqlite包底下,主要包含SQLiteProgram, SQLiteDat ...
随机推荐
- 学习Scala01 环境安装
Scala是一门运行在jvm上的多范式语言,作为一个java程序员,使用Scala来写写程序,既不用担心会没有java强大的库支持,又能快速地写出简短强悍的代码,除此之外scala还为我们提供了强大的 ...
- Javascript函数节流
最近在做网页的时候有个需求,就是浏览器窗口改变的时候需要改一些页面元素大小,于是乎很自然的想到了window的resize事件,于是乎我是这么写的 <!DOCTYPE html> < ...
- Cocos2d-x 3.X手游开发实例详解
Cocos2d-x 3.X手游开发实例详解(最新最简Cocos2d-x手机游戏开发学习方法,以热门游戏2048.卡牌为例,完整再现手游的开发过程,实例丰富,代码完备,Cocos2d-x作者之一林顺和泰 ...
- css图片叠加和底部定位
css图片叠加和底部定位 css图片叠加 两张图片需要叠在一起显示,如何定位 容器先对定位 第一张图片正常摆放 第二张图片绝对定位,top:0px 这样便实现了两张图片叠加在一起了,设置z-index ...
- Struts2中Action取得表单数据的几种方法
Struts2中Action取得表单数据的几种方法 Struts2中Action获得表单数据的几种方法struts2 Action获取表单传值 1.通过属性驱动式JSP: <form act ...
- C++11模板类使用心得
1.推荐使用std::shared_ptr<TaskT>代替指针TaskT*使用,shared_ptr是一种智能指针,能自主销毁释放内存,在c++11中被引入,在多线程编程中有很大的用处, ...
- Java EE开发平台随手记3——Mybatis扩展2
忙里偷闲,继续上周的话题,记录Mybatis的扩展. 扩展5:设置默认的返回结果类型 大家知道,在Mybatis的sql-mapper配置文件中,我们需要给<select>元素添加resu ...
- Constraint1:主键约束,唯一性约束和唯一索引
1,主键约束创建索引 作为Primay Key的列是唯一的,非空的,Sql Server在创建主键约束时,自动为主键列创建一个唯一索引,并且索引列不允许为null. create table dbo. ...
- Ext.util.TaskRunner定时执行任务
Ext.util.TaskRunner能够提供多线程的定时任务,该类提供了对多线程任务的管理,可以通过Ext.TaskManager来创建Ext.util.TaskRunner的一个实例,也可以自行创 ...
- ASP.NET MVC 4中如何为不同的浏览器自适应布局和视图
在ASP.NET MVC 4中,可以很简单地实现针对不同的浏览器自适应布局和视图.这个得归功于MVC中的"约定甚于配置"的设计理念. 默认的自适应 MVC 4自动地为移动设备浏览器 ...