在这篇文章里,我们从源代码的角度重点分析Session的创建、缓存、销毁、管理。

  通常我们说的Session指的是在控制器中使用的Session字段,该字段的类型是HttpSessionState。可以获取SessionID,可以存储数据,可以增加删除数据等等。Session字段中使用的HttpSessionState对象,就是在下面介绍的模块中产生的。

  在.Net Framework内部,来自浏览器的请求像工厂的流水线加工产品一样,在不同的部位进行不同的处理,最终被加工为一件成品。我们的请求也是如此,内部有许多的模块(实现了接口IHttpModule)对我们的请求进行处理,比如映射路由的模块(UrlRoutingModule),该模块的作用是从用户请求的url中获取信息,找到能处理这个IHttpHandler对象,用于后续处理处理请求。而我们这里讨论的则是关于会话Session的一个模块--SessionStateModule。

  在这里模块里,有诸如这样的功能,生成SessionID、生成用于HttpSessionState内部存储数据的ISessionStateItemCollection对象。然后将这样的不同对象封装起来。

  在IIS中,Session的存储生成有4中模式。

  1-InProc进程内模式,将Session存储在web应用程序进程内部。这也是系统默认的模式。这种方式优点显而易见---存取速度很快,缺点也显而易见--一旦因为某种原因应用程序被关闭,不管是否重启应用程序,所有用户的Session全部都会丢失,这是不能接受的。

而且因为是进行内存储,当我们想做分布式的时候,Session无法共享。

  2-StateServer状态服务器模式。在这种模式下,应用程序会启动一个会话状态服务器,专门用于存储Session。这种方式解决了重启web应用程序后session丢失的问题,但状态服务器本身会有问题,如:不支持故障转移等。

  3-SQLServer数据库模式。在这种模式下,所有的会话数据都被存储在SQLServer数据库中,但是要求session中存储的时候是可以序列化的。在这种模式下,数据库强大的存储、搜索、故障转移群集等稳定功能,会给session带来强大的支持。

  4-Custom自定义模式。在这种模式下,用户可以定义自己的session存储方式。

  这些内容仅在这里做简单介绍,有兴趣的朋友可以查看微软官方的文档。https://msdn.microsoft.com/zh-cn/library/ms178586(v=vs.100).aspx

  

         void InitModuleFromConfig(HttpApplication app, SessionStateSection config) {
if (config.Mode == SessionStateMode.Off) {
return;
} app.AddOnAcquireRequestStateAsync(
new BeginEventHandler(this.BeginAcquireState),
new EndEventHandler(this.EndAcquireState)); app.ReleaseRequestState += new EventHandler(this.OnReleaseState);
app.EndRequest += new EventHandler(this.OnEndRequest); _partitionResolver = InitPartitionResolver(config); switch (config.Mode) {
case SessionStateMode.InProc:
if (HttpRuntime.UseIntegratedPipeline) {
s_canSkipEndRequestCall = true;
}
_store = new InProcSessionStateStore();
_store.Initialize(null, null);
break; case SessionStateMode.StateServer:
if (HttpRuntime.UseIntegratedPipeline) {
s_canSkipEndRequestCall = true;
}
_store = new OutOfProcSessionStateStore();
((OutOfProcSessionStateStore)_store).Initialize(null, null, _partitionResolver);
break; case SessionStateMode.SQLServer:
_store = new SqlSessionStateStore();
((SqlSessionStateStore)_store).Initialize(null, null, _partitionResolver);
break; case SessionStateMode.Custom:
_store = InitCustomStore(config);
break;
default:
break;
} // 依赖SessionIDManager管理session id,所以在这里对管理器进行初始化
_idManager = InitSessionIDManager(config); if ((config.Mode == SessionStateMode.InProc || config.Mode == SessionStateMode.StateServer) &&
_usingAspnetSessionIdManager) {
//如果我们使用InProc模式或者StateServer模式,并且也使用我们自己的会话ID模块,
//我们知道我们不关心在所有会话状态存储读/写和会话ID读/写中的模拟。
_ignoreImpersonation = true;
}
}

模块初始化进行的设置之一

模块进行初始化时,会进行许多设置,上面的方法时其中的设置之一。上面的方法中,如果会话状态的模式是关闭,则直接返回,不再进行任何操作。

  然后向HttpApplication对象注册事件,当这个对象需要获取会话状态时,就调用这里注册的方法,进行会话状态的创建等共走。供后面的步骤使用。

  然后根据会话状态模式加载不同的会话状态存储提供程序,默认的模式是SessionStateMode.InProc,则加载的提供程序是类是InProcSessionStateStore。

  InProcSessionStateStore类内部实现的存储会话状态的方式是存储在运行时的缓存中。

  

  从缓存中读取会话状态:

   HttpRuntime.CacheInternal.Get(key)。

  

  将回话状态写入缓存:

  HttpRuntime.CacheInternal.UtcInsert(

  key,/*缓存的key,此处缓存会话,所以是在sessionid的基础上进行了其他操作组合而成。*/

  state,/*这就是我们的会话状态*/

  null,/*依赖属性,这里不需要,所以传空值*/

  Cache.NoAbsoluteExpiration,/*绝对过期时间。在这里我们不希望有绝对过期时间,所以传递了Cache.NoAbsoluteExpiration,该字段的值是 DateTime.MaxValue;*/

  new TimeSpan(0, state._timeout, 0),/*滑动到期时间,传递的是会话状态的过期值。*/

  CacheItemPriority.NotRemovable,/*缓存优先级,告诉运行时缓存,在进行内存优化时,不能删除这个缓存值*/

  _callback /*缓存项被移除的回调方法。正是因为InProcSessionStateStore类内部使用运行时缓存且运行时缓存带有缓存项移除回调方法,所以在SessionStateMode.InProc模式下,会话状态过期时,会调用在Global.asax中注册的Session_End方法*/

  );

  继续看代码。

  紧接着,会根据配置文件对SessionIDManager进行初始化,默认情况下会初始化一个SessionIDManager的对象。我们使用这个会话管理器进行sessionid的管理。创建sessionid,验证sessionid的合法性等功能。

  模块的初始化工作已经介绍完毕,这里介绍的只是最基础的一部分,其他还涉及的会话状态的锁定、优化等扩展开来则过于复杂。


  下面我们按一个请求的处理过程为顺序,介绍会话状态的相关处理。从获得会话状态开始。上代码。

         IAsyncResult BeginAcquireState(Object source, EventArgs e, AsyncCallback cb, Object extraData) {
bool requiresState;
bool isCompleted = true;
bool skipReadingId = false; _acquireCalled = true;
_releaseCalled = false;
ResetPerRequestFields(); _rqContext = ((HttpApplication)source).Context;
_rqAr = new HttpAsyncResult(cb, extraData); ChangeImpersonation(_rqContext, false); try { /* Notify the store we are beginning to get process request */
_store.InitializeRequest(_rqContext); /* determine if the request requires state at all */
requiresState = _rqContext.RequiresSessionState; // SessionIDManager may need to do a redirect if cookieless setting is AutoDetect
if (_idManager.InitializeRequest(_rqContext, false, out _rqSupportSessionIdReissue)) {
_rqAr.Complete(true, null, null);
return _rqAr;
} // See if we can skip reading the session id. See inline doc of s_allowInProcOptimization
// for details.
if (s_allowInProcOptimization &&
!s_sessionEverSet &&
(!requiresState || // Case 1
!((SessionIDManager)_idManager).UseCookieless(_rqContext)) ) { // Case 2 skipReadingId = true;
}
else {
/* Get sessionid */
_rqId = _idManager.GetSessionID(_rqContext);
} if (!requiresState) {
if (_rqId == null) {
}
else {
// Still need to update the sliding timeout to keep session alive.
// There is a plan to skip this for perf reason. But it was postponed to
// after Whidbey.
_store.ResetItemTimeout(_rqContext, _rqId);
}
_rqAr.Complete(true, null, null);
return _rqAr;
} _rqExecutionTimeout = _rqContext.Timeout; if (_rqExecutionTimeout == DEFAULT_DBG_EXECUTION_TIMEOUT) {
_rqExecutionTimeout = s_configExecutionTimeout;
} /* determine if we need just read-only access */
_rqReadonly = _rqContext.ReadOnlySessionState; if (_rqId != null) {
/* get the session state corresponding to this session id */
isCompleted = GetSessionStateItem();
}
else if (!skipReadingId) {
/* if there's no id yet, create it */
bool redirected = CreateSessionId(); _rqIdNew = true; if (redirected) {
if (s_configRegenerateExpiredSessionId) {
// See inline comments in CreateUninitializedSessionState()
CreateUninitializedSessionState();
}
_rqAr.Complete(true, null, null);
return _rqAr;
}
} if (isCompleted) {
CompleteAcquireState();
_rqAr.Complete(true, null, null);
} return _rqAr;
}
finally {
RestoreImpersonation();
}
}

BeginAcquireState

  这是获取Session的入口,每当有请求到达时,这是必须要有的步骤,经过这个方法处理后,请求上下文会拥有一个与当前请求匹配的Session。

  下面我们具体分析方法中的一些代码。

  requiresState = _rqContext.RequiresSessionState;

  上面这行代码判断请求是否需要会话状态。主要取决于两方面,一方面是我们的控制器是否需要会话状态.控制器本身是否设置了SessionStateAttribute特性或者用于处理请求的实现了IHttpHandler接口的类是否标记了IRequiresSessionState这个接口。

  

当我们没有给控制器标记SessionStateAttribute特性时,会默认使用SessionStateBehavior.Default这个选项。这个选项的意思参考第一个截图。

我把这个属性的内部代码贴出来,有兴趣的可以看看。

internal bool RequiresSessionState {
get {
switch (SessionStateBehavior) {
case SessionStateBehavior.Required:
case SessionStateBehavior.ReadOnly:
return true;
case SessionStateBehavior.Disabled:
return false;
case SessionStateBehavior.Default:
default:
return _requiresSessionStateFromHandler;
}
}
}
//从上面的代码中可以看出,当设置SessionStateBehavior.Default时,
//会返回_requiresSessionStateFromHandler这个变量的值。
//我们再看看这个值是如何被设置的.
public IHttpHandler Handler {
get { return _handler;}
set {
_handler = value;
_requiresSessionStateFromHandler = false;
_readOnlySessionStateFromHandler = false;
InAspCompatMode = false;
if (_handler != null) {
if (_handler is IRequiresSessionState) {
_requiresSessionStateFromHandler = true;
}
if (_handler is IReadOnlySessionState) {
_readOnlySessionStateFromHandler = true;
}
Page page = _handler as Page;
if (page != null && page.IsInAspCompatMode) {
InAspCompatMode = true;
}
}
}
}
//从上面代码中的可以看出,
// if (_handler is IRequiresSessionState) {
// _requiresSessionStateFromHandler = true;
// }
//而我们的MvcHandler是扩展了IRequiresSessionState这个接口的,所以_requiresSessionStateFromHandler是true;

_rqContext.RequiresSessionState属性的内部

BeginAcquireState方法中的代码主要是用来判断是否满足优化的目的。比如是否要延迟获取一个session id,是否要加载session等。

我们假设是第一次请求,走一次完整的流程。那么,我们的代码就要进入CompleteAcquireState这个方法中了。

在介绍这个方法前,首先介绍一个点。

在控制器中访问的Session共分两个部分,一个是用来存储数据的部分,例如这样的形式--Session["key1"] = 123;

一个是Session的id---Session.SessionID

他们虽然都归属在Session下面,但是它们的生成、存储、删除等机制完全不同。

         // Called when AcquireState is done.  This function will add the returned
// SessionStateStore item to the request context.
void CompleteAcquireState() {
bool delayInitStateStoreItem = false;
try {
//_rqItem就是session中用来存储数据的
//当第一次访问时,这个值肯定是空值,所以会走else的分支
if (_rqItem != null) {
_rqSessionStateNotFound = false; if ((_rqActionFlags & SessionStateActions.InitializeItem) != ) {
_rqIsNewSession = true;
}

CompleteAcquireState

该函数将把返回的SessionStateStore项添加到请求上下文中。

这个方法的主要作用是向请求上下文中(HttpContext)添加一个会话状态存储,Session中存储数据的功能就是靠这个对象实现的。

这个方法中,添加会话状态存储的功能是靠InitStateStoreItem这个方法来实现的。我们也看看这个方法的内部。

         internal void InitStateStoreItem(bool addToContext) {
try { if (_rqItem == null) {
_rqItem = _store.CreateNewStoreData(_rqContext, s_timeout);
} _rqSessionItems = _rqItem.Items;
if (_rqSessionItems == null) {
throw new HttpException(SR.GetString(SR.Null_value_for_SessionStateItemCollection));
} // No check for null because we allow our custom provider to return a null StaticObjects.
_rqStaticObjects = _rqItem.StaticObjects; _rqSessionItems.Dirty = false; _rqSessionState = new HttpSessionStateContainer(
this,
_rqId, // could be null if we're using InProc optimization
_rqSessionItems,
_rqStaticObjects,
_rqItem.Timeout,
_rqIsNewSession,
s_configCookieless,
s_configMode,
_rqReadonly); if (addToContext) {
SessionStateUtility.AddHttpSessionStateToContext(_rqContext, _rqSessionState);
}
}
finally {
RestoreImpersonation();
}
}
public static class SessionStateUtility {
// Called by custom session state module
static public void AddHttpSessionStateToContext(HttpContext context, IHttpSessionState container) {
HttpSessionState sessionState = new HttpSessionState(container); //在这个方法中,将用于存取数据的数据结构存储在了context.Items中。
//存储的key是变量SESSION_KEY
try {
context.Items.Add(SESSION_KEY, sessionState);
}
catch (ArgumentException) {
throw new HttpException(SR.GetString(SR.Cant_have_multiple_session_module));
}
}
} //这是HttpContext中Session的内部代码
public HttpSessionState Session {
get {
if (HasWebSocketRequestTransitionCompleted) {
// Session is unavailable at this point
return null;
}
//省略了部分代码 //访问session时,从context.Items中取出这一数据结构
//存储的key是变量SESSION_KEY
return(HttpSessionState)Items[SessionStateUtility.SESSION_KEY];
}
}

InitStateStoreItem

经过这个方法后,我们的请求上下文真正有了用了可以让我们用于访问,用于存取数据的对象。


经过上面的两个大方法,我们的请求上下文终于获得了session,然后代码进入了我们的控制器,我们在控制器中会具体的处理的代码,与session相关的可能就是从session中读取数据,但是这个数据只是存储在这个用来存储数据的对象中了,并没有存储在缓存中。当我们再次请求时,数据从哪里来呢?下面介绍的方法就是负责session相关的收尾工作---OnReleaseState。

  当控制器中的代码处理完毕后。应用程序就会进行其他的一些收尾步骤,比如将session中的数据存储起来,再次查询时使用。

  在下面的代码中,对部分关键代码做了相应的注释。

  这个方法的主要作用就是判断是否要把会话存储放进系统缓存中,是否把session id继续保存在cookie中

         void OnReleaseState(Object source, EventArgs eventArgs) {
HttpApplication app;
HttpContext context;
bool setItemCalled = false; _releaseCalled = true; app = (HttpApplication)source;
context = app.Context; ChangeImpersonation(context, false); try {
if (_rqSessionState != null) {
bool delayedSessionState = (_rqSessionState == s_delayedSessionState);
SessionStateUtility.RemoveHttpSessionStateFromContext(_rqContext, delayedSessionState); if (
//如果会话状态是新的,并且没有被访问过,
//那么这样的会话状态存储不用保存到系统的缓存中,保存了没有意义。
//所以不做任何处理(不保存在系统缓存中)
_rqSessionStateNotFound
&& _sessionStartEventHandler == null
// Nothing has been stored in session state
&& (delayedSessionState || !_rqSessionItems.Dirty)
&& (delayedSessionState || _rqStaticObjects == null || _rqStaticObjects.NeverAccessed)
) {
}
//如果会话被丢弃了,即我们在控制器中调用了Session.Abandon(),常用于注销操作。
else if (_rqSessionState.IsAbandoned) {
if (_rqSessionStateNotFound) {
// The store provider doesn't have it, and so we don't need to remove it from the store. // However, if the store provider supports session expiry, and we have a Session_End in global.asax,
// we need to explicitly call Session_End.
if (_supportSessionExpiry) {
if (delayedSessionState) {
InitStateStoreItem(false /*addToContext*/);
}
_onEndTarget.RaiseSessionOnEnd(ReleaseStateGetSessionID(), _rqItem);
}
}
else {
// Remove it from the store because the session is abandoned.
_store.RemoveItem(_rqContext, ReleaseStateGetSessionID(), _rqLockId, _rqItem);
}
}
else if (!_rqReadonly ||
(_rqReadonly &&
_rqIsNewSession &&
_sessionStartEventHandler != null &&
!SessionIDManagerUseCookieless)) { // We save it only if there is no error, and if something has changed (unless it's a new session)
if ( context.Error == null // no error
&& ( _rqSessionStateNotFound
|| _rqSessionItems.Dirty // SessionItems has changed.
|| (_rqStaticObjects != null && !_rqStaticObjects.NeverAccessed) // Static objects have been accessed
|| _rqItem.Timeout != _rqSessionState.Timeout // Timeout value has changed
)
) { if (delayedSessionState) {
InitStateStoreItem(false /*addToContext*/);
}
if (_rqItem.Timeout != _rqSessionState.Timeout) {
_rqItem.Timeout = _rqSessionState.Timeout;
}
//该标识设置为true,说明该模块设置过session,该标识用于一些优化
s_sessionEverSet = true;
//为true说明我们已经把当前的session保存进系统缓存中了
setItemCalled = true;
//将这个会话状态存储放入系统缓存中,在这个代码中,我们看到使用了ReleaseStateGetSessionID()这个方法去获取session id
//有这样的意思。如果已经有session id了,则直接使用,如果没有则会创建一个session id,且写入cookie ,则浏览器端就有了这个id
//如果这是一个全新的请求,而且也没有在控制器中也没有Session.SessionID这样的访问,那么到了此处,session id就是空值
_store.SetAndReleaseItemExclusive(_rqContext, ReleaseStateGetSessionID(), _rqItem, _rqLockId, _rqSessionStateNotFound);
}
else {
// Can't save it because of various reason. Just release our exclusive lock on it.
if (!_rqSessionStateNotFound) {
_store.ReleaseItemExclusive(_rqContext, ReleaseStateGetSessionID(), _rqLockId);
}
}
}
}
//上面是决定是否保存会话状态存储的代码,下面是决定是否将session id保存在cookie中的代码
//有这样一种情况,在一次全新的请求中,如果我们在控制器中有了Session.SessionID这样的访问
//那么,此时会创建一个SessionID且写入cookie中,但是如果我们没有向Session中存储数据
//那么这样的SessionID继续保存在cookie中,就是没有意义的,所以就从cookie中删除这个id
if (_rqAddedCookie && !setItemCalled && context.Response.IsBuffered()) {
_idManager.RemoveSessionID(_rqContext);
}
}
finally {
RestoreImpersonation();
} bool implementsIRequiresSessionState = context.RequiresSessionState;
if (HttpRuntime.UseIntegratedPipeline
&& (context.NotificationContext.CurrentNotification == RequestNotification.ReleaseRequestState)
&& (s_canSkipEndRequestCall || !implementsIRequiresSessionState)) {
context.DisableNotifications(RequestNotification.EndRequest, /*postNotifications*/);
_acquireCalled = false;
_releaseCalled = false;
ResetPerRequestFields();
}
}

OnReleaseState


  上面对于初次的访问关于session的获取等已经介绍完毕,而对于非初次访问(已经想session中存储过数据),则简单很多。

  因为已经存储过数据,所以cookie中有session id,系统缓存中数据。

  则先从cookie中读取session id,然后根据此id去缓存中读取数据,然后返回。


  结束语:SessionStateModule模块已经介绍完毕,这个模块的主要作用,就是在正式处理请求前,为我们生成存储数据用的session内部对象,请求处理完毕后,将

      存储数据的对象保存在系统缓存中。这些功能也是这个模块的部分功能,还有其他很重要的功能。比如用于同步的锁机制,比如生成sessionid的类等。这些内容                      就放在其他文章里再做介绍。

源码分析Session的台前幕后(Asp .Net MVC5)的更多相关文章

  1. TOMCAT8源码分析——SESSION管理分析(上)

    前言 对于广大java开发者而已,对于J2EE规范中的Session应该并不陌生,我们可以使用Session管理用户的会话信息,最常见的就是拿Session用来存放用户登录.身份.权限及状态等信息.对 ...

  2. Tomcat源码分析——Session管理分析(下)

    前言 在<TOMCAT源码分析——SESSION管理分析(上)>一文中我介绍了Session.Session管理器,还以StandardManager为例介绍了Session管理器的初始化 ...

  3. Tomcat源码分析——Session管理分析(上)

    前言 对于广大java开发者而已,对于J2EE规范中的Session应该并不陌生,我们可以使用Session管理用户的会话信息,最常见的就是拿Session用来存放用户登录.身份.权限及状态等信息.对 ...

  4. Orchard源码分析(7):ASP.NET MVC相关

    概述 Orchard归根结底是一个ASP.NET MVC(以后都简称为MVC)应用,但在前面的分析中,与MVC相关内容的涉及得很少.MVC提供了非常多的扩展点,本文主要关注Orchard所做的扩展.主 ...

  5. tomcat 源码分析

    Tomcat源码分析——Session管理分析(下)    Tomcat源码分析——Session管理分析(上)     Tomcat源码分析——请求原理分析(下)     Tomcat源码分析——请 ...

  6. Flask系列之源码分析(一)

    目录: 涉及知识点 Flask框架原理 简单示例 路由系统原理源码分析 请求流程简单源码分析 响应流程简单源码分析 session简单源码分析 涉及知识点 1.装饰器 闭包思想 def wapper( ...

  7. [asp.net core 源码分析] 01 - Session

    1.Session文档介绍 毋庸置疑学习.Net core最好的方法之一就是学习微软.Net core的官方文档:https://docs.microsoft.com/zh-cn/aspnet/cor ...

  8. 一个由正则表达式引发的血案 vs2017使用rdlc实现批量打印 vs2017使用rdlc [asp.net core 源码分析] 01 - Session SignalR sql for xml path用法 MemCahe C# 操作Excel图形——绘制、读取、隐藏、删除图形 IOC,DIP,DI,IoC容器

    1. 血案由来 近期我在为Lazada卖家中心做一个自助注册的项目,其中的shop name校验规则较为复杂,要求:1. 英文字母大小写2. 数字3. 越南文4. 一些特殊字符,如“&”,“- ...

  9. ASP.NET MVC 4源码分析之如何定位控制器

    利用少有的空余时间,详细的浏览了下ASP.NET MVC 4的源代码.照着之前的步伐继续前进(虽然博客园已经存在很多大牛对MVC源码分析的博客,但是从个人出发,还是希望自己能够摸索出这些).首先有一个 ...

随机推荐

  1. [WinForm]dataGridView导出到EXCEL

    方法一: SaveFileDialog dlg = new SaveFileDialog(); dlg.Filter = "Execl files (*.xls)|*.xls"; ...

  2. 《java入门第一季》之类StringBuffer类初步

    /* * 线程安全(多线程分析) * 安全 -- 同步 -- 数据是安全的 * 不安全 -- 不同步 -- 效率高一些 * 安全和效率问题是永远困扰我们的问题. * 安全:医院的网站,银行网站 * 效 ...

  3. 栈的顺序存储 - 设计与实现 - API实现

    Stack基本概念 栈是一种 特殊的线性表 栈仅能在线性表的一端进行操作 栈顶(Top):允许操作的一端 栈底(Bottom):不允许操作的一端 Stack的常用操作 创建栈 销毁栈 清空栈 进栈 出 ...

  4. Java进阶(十四)实现每天定时对数据库的操作

    Java实现每天定时对数据库操作 现在有一个很棘手的问题:客户要求实现一个功能,就是每日凌晨自动计算慢性病订单是否有需要在今日提醒的,如果有则生成一条提醒记录到lm_notice之中. 如何在Web工 ...

  5. JavaScript单线程的疑问与解答

    问: JavaScript是单线程的,有任务队列,比如使用setTimeou(func,secs)来在secs毫秒后向任务队列添加func.但是,setTimeout后面跟一个死循环,那么死循环导致任 ...

  6. Developing RIA Web Applications with Oracle ADF

      Developing RIA Web Applications with Oracle ADF Purpose This tutorial shows you how to build a ric ...

  7. SharePoint&quot;在数据表中编辑&quot;不可用

    报错: 没有安装与 Windows SharePoint Services 兼容的数据表组件 浏览器不支持 ActiveX 控件 或者禁用了对 ActiveX 控件的支持 第一反应,就是什么东西没装, ...

  8. MATLAB坐标系中绘制图片

    MATLAB坐标系中绘制图片 方法一 使用图片坐标循环的方式,代码如下. clear,clc,close all tic; map=imbinarize(imread('map.bmp'));%map ...

  9. 闫燕飞:Kafka的高性能揭秘及优化

    欢迎大家前往腾讯云+社区,获取更多腾讯海量技术实践干货哦~ 本文首发在云+社区,未经许可,不得转载. 大家下午好,我是来自腾讯云基础架构部ckafka团队的高级工程师闫燕飞.今天在这里首先为大家先分享 ...

  10. oracle查询表索引

    转载 http://blog.sina.com.cn/s/blog_5376c7190101hvvb.html 如下: select * from user_indexes where table_n ...