1. 程式人生 > >pac4j探索(二): buji-pac4j+Cas+Shiro+SpringMvc實現單點登入

pac4j探索(二): buji-pac4j+Cas+Shiro+SpringMvc實現單點登入

在pac4j探索的上一篇文章大致講述了一下buji-pac4j+CAS的認證流程。這裡記錄一下本人實現的最簡單的單點登入,僅作為筆記、學習交流之用,戳這裡獲取本文原始碼

一、專案框架
1、 buji-pac4j(v.3.0.0)
2、shiro (v.1.4.0)
3、springmvc (v.4.3.2)
4、CAS (v.4.2.6)
5、pac4j-cas(v.2.2.1)

在maven專案的pom.xml裡配置以上相關依賴,具體依賴配置可以檢視我的專案,這裡不再贅述。

二、目錄結構
客戶端專案(pac4jtest1)目錄結構如下:
這裡寫圖片描述


1、java檔案目錄中,Redirect2CasLoginFilter是測試用的,可以不管,MyCasClient類是繼承自CasClient的自定義客戶端,ShiroCasLogoutHandler類是單點登出時對shiro的一些操作,Controller類是請求控制器,util包裡的是單點登出相關的類;

2、配置檔案目錄中,log4j.properties是日誌管理檔案,url.properties配置了專案中用到的各種url,spring-comm.xml配置了shiro整合pac4j的配置,spring-mvc.xml是springmvc的相關配置;

3、另外還有個index.jsp,就是受保護的頁面,請求訪問前需要先認證。

二、springMvc配置
這裡springmvc作最簡單的配置:

    <!-- 自動掃描的包名 -->  
    <context:component-scan base-package="com.pac4j.rest"/>  

    <!-- 預設的註解對映的支援,自動註冊DefaultAnnotationHandlerMapping和AnnotationMethodHandlerAdapter -->  
    <mvc:annotation-driven />  

    <!-- 檢視解釋類 -->  
    <bean
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/jsp/"/> <property name="suffix" value=".jsp"/> <property name="viewClass" value="org.springframework.web.servlet.view.JstlView" /> </bean> <!-- 對靜態資原始檔的訪問--> <mvc:resources mapping="/images/**" location="/WEB-INF/images/"/> <mvc:resources mapping="/js/**" location="/WEB-INF/js/" /> <mvc:resources mapping="/css/**" location="/WEB-INF/css/"/>

三、pac4j配置
spring-comm.xml是shiro整合pac4j的配置,具體配置如下:

<!-- 地址配置 -->
     <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="locations">
            <list>
                <value>/WEB-INF/classes/url.properties</value>
            </list>
        </property>
    </bean>

    <!-- pac4j configurations -->
    <!-- 請求cas服務端配置 -->
    <bean id="casConfig" class="org.pac4j.cas.config.CasConfiguration">
        <!-- CAS server登入連結 -->
       <property name="loginUrl" value="${sso.cas.server.loginUrl}"></property>
        <!-- CAS server服務字首 -->
       <property name="prefixUrl" value="${sso.cas.server.prefixUrl}"></property>
        <!-- 登出處理器,單點登出時所需要的操作在這裡實現-->
        <property name="logoutHandler" ref="casLogoutHandler"></property>
    </bean>

    <!-- cas客戶端配置 -->
    <bean id="casClient" class="com.pac4j.client.MyCasClient">
        <constructor-arg ref="casConfig" />
        <property name="includeClientNameInCallbackUrl" value="false"></property>
        <!-- 客戶端回撥地址 -->
        <property name="callbackUrl" value="${sso.cas.client.callbackUrl}"></property>
    </bean>

    <!-- shiro登出處理器,銷燬session及登入狀態等-->
    <bean id="casLogoutHandler" class="com.pac4j.handler.ShiroCasLogoutHandler">
       <property name="destroySession" value="true"></property>
    </bean>

    <bean id="sessionStore" class="com.pac4j.util.MyShiroSessionStore"></bean>

    <!-- pac4j配置 -->
    <bean id="authcConfig" class="org.pac4j.core.config.Config">
        <constructor-arg ref="casClient"></constructor-arg>
        <property name="sessionStore" ref="sessionStore"></property>
    </bean>


    <!-- shiro configurations -->

    <bean id="sessionIdGenerator"
        class="org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator" />

    <bean id="sessionIdCookie" class="org.apache.shiro.web.servlet.SimpleCookie">
        <constructor-arg value="sid" />
        <property name="httpOnly" value="false" />
        <property name="maxAge" value="180000" />
        <property name="path" value="/" />
    </bean>

    <bean id="sessionDAO"   
        class="org.apache.shiro.session.mgt.eis.MemorySessionDAO">  
        <property name="sessionIdGenerator" ref="sessionIdGenerator"/>  
    </bean> 

    <bean id="sessionValidationScheduler"
        class="org.apache.shiro.session.mgt.quartz.QuartzSessionValidationScheduler">
        <property name="sessionValidationInterval" value="1800000" />
        <property name="sessionManager" ref="sessionManager" />
    </bean>

    <bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
        <property name="globalSessionTimeout" value="1800000" />
        <property name="deleteInvalidSessions" value="true" />
        <property name="sessionValidationSchedulerEnabled" value="true" />
        <property name="sessionValidationScheduler" ref="sessionValidationScheduler" />
        <property name="sessionDAO" ref="sessionDAO" />
        <property name="sessionIdCookieEnabled" value="true" />
        <property name="sessionIdCookie" ref="sessionIdCookie" />
    </bean> 

    <!-- 基於pac4j的Subject工廠 -->
    <bean id="pac4jSubjectFactory" class="io.buji.pac4j.subject.Pac4jSubjectFactory"></bean>

    <!-- 安全管理器 -->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <property name="realm">
            <bean class="io.buji.pac4j.realm.Pac4jRealm">
                <property name="cachingEnabled" value="false" />
                <property name="authenticationCachingEnabled" value="false" />
                <property name="authenticationCacheName" value="authenticationCache" />
                <property name="authorizationCachingEnabled" value="false" />
                <property name="authorizationCacheName" value="authorizationCache" />
            </bean>
        </property>

        <property name="subjectFactory" ref="pac4jSubjectFactory"></property>

    </bean>

    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <property name="securityManager" ref="securityManager"></property>
        <property name="filters">
            <util:map>
                <!-- 配置SecurityFilter,用於攔截受保護的url -->
                <entry key="casSecurityFilter">
                    <bean class="io.buji.pac4j.filter.SecurityFilter">
                        <property name="config" ref="authcConfig"></property>
                        <property name="clients" value="MyCasClient"></property>
                    </bean>
                </entry>
                <!-- 回撥過濾器,完成ticket認證 -->
                <entry key="callback">
                    <bean class="io.buji.pac4j.filter.CallbackFilter">
                        <property name="config" ref="authcConfig"></property>
                        <property name="defaultUrl" value="${sso.cas.client.successUrl}"></property>
                    </bean>
                </entry>
                <!-- 登出過濾器 -->
                <entry key="logout">
                    <bean id="logout" class="io.buji.pac4j.filter.LogoutFilter">
                        <property name="defaultUrl" value="${sso.cas.client.callbackUrl}"></property>
                        <property name="config" ref="authcConfig"></property>
                        <property name="centralLogout" value="true"></property>
                        <property name="localLogout" value="false"></property>
                    </bean>
                </entry>
                <!-- 攔截/login,並作一些操作,測試用,可以忽略 -->
                <entry key="login">
                     <bean class="com.pac4j.filter.Redirect2CasLoginFilter"></bean>
                </entry>

            </util:map>
        </property>

        <property name="filterChainDefinitions">
            <value>
                /index = casSecurityFilter
                /logout = logout
                /callback = callback
                /login** = login
                /login/** = login
            </value>
        </property>

    </bean>

    <!-- 生命週期處理 -->
    <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" />

    <bean id="annotationProxy"
        class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
        depends-on="lifecycleBeanPostProcessor">
        <property name="proxyTargetClass" value="true" />
    </bean>

關於shiro的相關知識,在這裡不再贅述,因為不屬於本文的討論範圍。需要留意的是,以上配置在原來一般的shiro配置基礎上,除了增加pac4j的配置,還修改了一下原來shiro的配置,具體變更如下:

  • 原來的CasFilter被替換成CallbackFilter
  • CasRealm被替換成Pac4jRealm
  • 當一個url需要被保護時(需要經過認證、鑑權),不僅需要使用shiro預設的過濾器,而且必須使用SecurityFilter。

四、url.properties
在spring-comm.xml會用到的一些路徑:

##cas服務字首
sso.cas.server.prefixUrl=http://localhost:8080/hgretail.authc/
##cas服務登入url
sso.cas.server.loginUrl=http://localhost:8080/hgretail.authc/login

##cas客戶端回撥地址
sso.cas.client.callbackUrl=http://localhost:8080/pac4jtest1/callback?client_name=MyCasClient
##cas服務端成功跳轉地址
sso.cas.client.successUrl=http://localhost:8080/pac4jtest1/index

五、MyCasClient
這個類是繼承自CasClient的,因為其超類IndirectClient中的getRedirectAction方法用起來有問題,報401異常,所以寫了這個類,覆蓋getRedirectAction方法,遮蔽掉異常程式碼。以後思考一下是否有更好的解決辦法。

public class MyCasClient extends CasClient {

    public MyCasClient(final CasConfiguration configuration) {
        super(configuration);
    }

    /*
     * (non-Javadoc)    
     * @see org.pac4j.core.client.IndirectClient#getRedirectAction(org.pac4j.core.context.WebContext)
     */
    @Override
    public RedirectAction getRedirectAction(WebContext context) throws HttpAction {
         init(context);
            // it's an AJAX request -> unauthorized (with redirection url in header)
            if (getAjaxRequestResolver().isAjax(context)) {
                logger.info("AJAX request detected -> returning 401");
                RedirectAction action = getRedirectActionBuilder().redirect(context);
                cleanRequestedUrl(context);
                throw HttpAction.unauthorized("AJAX request -> 401", context, null, action.getLocation());
            }
            // authentication has already been tried -> unauthorized
            //FIXME 以下這段程式碼在org.pac4j.cas.client.CasClient中會出現401錯誤,所以在這裡遮蔽掉。以後尋求更好的解決辦法。
//          final String attemptedAuth = (String) context.getSessionAttribute(getName() + ATTEMPTED_AUTHENTICATION_SUFFIX);
//          if (CommonHelper.isNotBlank(attemptedAuth)) {
//              cleanAttemptedAuthentication(context);
//              cleanRequestedUrl(context);
//              throw HttpAction.unauthorized("authentication already tried -> forbidden", context, null, null);
//          }

            return getRedirectActionBuilder().redirect(context);
    }

    private void cleanRequestedUrl(final WebContext context) {
        context.setSessionAttribute(Pac4jConstants.REQUESTED_URL, "");
    }

}

六、ShiroCasLogoutHandler
ShiroCasLogoutHandler繼承於DefaultCasLogoutHandler。用於單點登出的時候shiro的一系列登出操作:

public class ShiroCasLogoutHandler<C extends WebContext> extends DefaultCasLogoutHandler<C> {

    public ShiroCasLogoutHandler() {
    }

    public ShiroCasLogoutHandler(final Store<String, Object> store) {
        super(store);
    }

    protected void destroy(final C context, final SessionStore sessionStore, final String channel) {
        // remove profiles
        final ShiroProfileManager manager = new ShiroProfileManager(context);
        manager.logout();//shiro登出操作
        logger.debug("destroy the user profiles");
        // and optionally the web session
        if (isDestroySession()) {
            logger.debug("destroy the whole session");
            final boolean invalidated = sessionStore.destroySession(context);
            if (!invalidated) {
                logger.error("The session has not been invalidated for {} channel logout", channel);
            }
        }
    }
}

七、MyShiroSessionStore類和MyShiroProvidedSessionStore類
1、SessionStore是用於暫時快取session以供後續的操作,MyShiroSessionStore類重寫了ShiroSessionStore,因為pac4j本身的ShiroSessionStore不能滿足單點登出,會有問題,所以改寫了該類:

public class MyShiroSessionStore implements SessionStore<J2EContext> {


    private final static Logger logger = LoggerFactory.getLogger(MyShiroSessionStore.class);

    //獲取shiro session
    protected Session getSession(final boolean createSession) {
        return SecurityUtils.getSubject().getSession(createSession);
    }

    //獲取shiro的sessionid
    @Override
    public String getOrCreateSessionId(final J2EContext context) {
        final Session session = getSession(false);
        if (session != null) {
            return session.getId().toString();
        }
        return null;
    }

    /**
     * 獲取shiro session中的屬性
     */
    @Override
    public Object get(final J2EContext context, final String key) {
        final Session session = getSession(false);
        if (session != null) {
            return session.getAttribute(key);
        }
        return null;
    }

    /**
     * 設定session屬性
     */
    @Override
    public void set(final J2EContext context, final String key, final Object value) {
        final Session session = getSession(true);
        if (session != null) {
            try {
                session.setAttribute(key, value);
            } catch (final UnavailableSecurityManagerException e) {
                logger.warn("Should happen just once at startup in some specific case of Shiro Spring configuration", e);
            }
        }
    }

    /**
     * 銷燬session
     */
    @Override
    public boolean destroySession(final J2EContext context) {
        getSession(true).stop();
        return true;
    }

    /**
     * 獲取shiro session並快取用於單點登出
     */
    @Override
    public Object getTrackableSession(final J2EContext context) {
        return getSession(true);
    }

    /**
     * 從getTrackableSession中獲取的session來構建SessionStore
     */
    @Override
    public SessionStore<J2EContext> buildFromTrackableSession(final J2EContext context, final Object trackableSession) {
        if(trackableSession != null) {
            return new MyShiroProvidedSessionStore((Session) trackableSession);
        }
        return null;
    }

    /**
     * 重新整理session屬性,這裡暫返回false,實際應用中需實現
     */
    @Override
    public boolean renewSession(final J2EContext context) {
        return false;
    }
}

2、MyShiroProvidedSessionStore是由MyShiroSessionStore構建來臨時儲存TrackableSession的:

public class MyShiroProvidedSessionStore extends MyShiroSessionStore{

    /**儲存的TrackableSession,往後要操作時用這個session操作*/
    private Session session;
    public MyShiroProvidedSessionStore(Session session) {
        this.session = session;
    }   
    protected Session getSession(final boolean createSession) {
        return session;
    }

}

八、Controller
這是個控制器,負責請求處理

public class Controller {

    @RequestMapping(value="/index",method=RequestMethod.GET) 
    public String index(ModelMap map) {

        //獲取使用者身份
        Pac4jPrincipal p = SecurityUtils.getSubject().getPrincipals().oneByType(Pac4jPrincipal.class);

        if(p != null) {
            CommonProfile profile = p.getProfile();
            map.put("profile", profile);
        }

        return "index";
    }

}

九、index.jsp
這裡是個簡單的jsp頁面,上面將會打印出使用者的資訊

<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
    pageEncoding="ISO-8859-1"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Hello pac4j1!</title>
</head>
<body>


hello~~~ <b>I'm pac4jtest1</b>

profile:${profile}

</body>
</html>

十、web.xml
主要配置了springmvc和shiroFilter

<context-param>
       <param-name>contextConfigLocation</param-name>
       <param-value>classpath*:spring-comm.xml</param-value>

    </context-param>

    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <listener>
        <listener-class>org.springframework.web.util.IntrospectorCleanupListener</listener-class>
    </listener>


    <filter>
       <filter-name>shiroFilter</filter-name>
       <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
        <init-param>
            <param-name>targetFilterLifecycle</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>

    <filter-mapping>
       <filter-name>shiroFilter</filter-name>
       <url-pattern>/*</url-pattern>
    </filter-mapping>

    <servlet>
        <servlet-name>springDispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:spring-mvc.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>springDispatcher</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

十一、執行
CAS服務的專案在這裡就不貼出來了,因為改變不大,就是注意要在CAS服務裡註冊Cas Client,也就是上面的專案,否則CAS服務會報錯。

為了認證單點登入,要再弄一個Cas Client,所以把上面的專案pac4jtest1複製一份命名為pac4jtest2,記得把裡面對應的配置要改過來。

部署CAS Server、pac4jtest1、pac4jtest2三個專案並啟動tomcat。根據以下步驟進行測試:

1、在位址列輸入:
這裡寫圖片描述

2、會看到去到了登入頁,位址列也發生了改變:

這裡寫圖片描述

3、輸入使用者名稱密碼登入,去到了pac4jtest1的index:

這裡寫圖片描述

4、此時在位址列輸入 http://localhost:8080/pac4jtest2/index,按道理如果是還沒認證,會跳轉到第一步中的登入頁面;但是我們已經在第三步已經登入認證通過了,所以無須再進行登入,直接進入pac4jtest2的index
這裡寫圖片描述

這裡寫圖片描述

至此,一個簡單的整合pac4j+CAS+shiro+springmvc的單點登入完成

十二、後記
這是pac4j最迷你版的單點登入,實際應用中比這個複雜很多,其實主要的部分弄清楚了,擴充套件也就很容易了。本人才疏學淺,專案中存在一些不足,歡迎大家批評指出!
原始碼位置:pac4jtest1

相關推薦

pac4j探索: buji-pac4j+Cas+Shiro+SpringMvc實現登入

在pac4j探索的上一篇文章大致講述了一下buji-pac4j+CAS的認證流程。這裡記錄一下本人實現的最簡單的單點登入,僅作為筆記、學習交流之用,戳這裡獲取本文原始碼。 一、專案框架 1、 buji-pac4j(v.3.0.0) 2、

cas shiro spring實現登入

這裡貼出傳送門,來自幕課網大神。講了cas配置和cas的基本原理。 CAS deployerConfigContext.xml配置檔案 預設通過配置檔案管理授權登入賬戶 <bean id="primaryAuthentication

JAVA三種實現例模式方法:使用靜態內部類實現例設計模式

靜態程式碼塊和靜態內部類的載入順序:當呼叫外部類的建構函式是,外部類的靜態程式碼塊同時被載入,但是其內部類不會同時被載入;當且僅當內部類的靜態域或其構造方法或其靜態方法被呼叫時,內部內才被載入。 因此,通過內部內實現單例,就能實現延遲載入。 這個解決方案被稱為Lazy i

如何使用spring-security來實現使用者的登入許可權功能?配合使用資料庫的方式

如何使用spring-security來實現使用者的登入功能之配合使用資料庫的方式 這個圖大家先熟悉一下簡單的過一遍,等把步驟都寫完之後,後面會總結 (一)使用spring-security之前需要做的準備(基於springMVC和dubbo的專案)

SpringBootSecurity學習26前後端分離版之github登入

單點登入(SSO) 關於oauth2.0,最後我們再來學習一下單點登入。前面介紹過單點登入的定義,單點登入(Single Sign On),簡稱為 SSO,是目前比較流行的企業業務整合的解決方案之一。SSO的定義是在多個應用系統中,使用者只需要登入一次就可以訪問所有相互信任的應用系統。 關於單點登入,spri

Spring Security原始碼分析十:Spring Security OAuth2基於JWT實現登入

單點登入(英語:Single sign-on,縮寫為 SSO),又譯為單一簽入,一種對於許多相互關連,但是又是各自獨立的軟體系統,提供訪問控制的屬性。當擁有這項屬性時,當用戶登入時,就可以獲取所有

pac4j探索buji-pac4j

一、初步認識buji-pac4j 公司單點登入cas客戶端用的是shiro的shiro-cas模組,但從原始碼看來,shiro不建議再使用shiro-cas,也就是說shiro-cas模組的相關都被s

AI探索Tensorflow環境準備

Python + Tensorflow環境安裝 Tensorflow支援Windows/Mac/Linux等三種作業系統, 其中windows下python需要安裝3.5以上的版本 Mac/Linux自帶的python 2.7可以支援安裝Tensorflow   #1. Python安裝

App自動化測試探索MAC環境搭建iOS+Python+Appium測試環境

code -s image ios 使用 usr developer contents gis 環境搭建要求,MAC 機器一臺,要求 Xcode 8.0以上 1. 安裝 Homebrew /usr/bin/ruby -e "$(curl -fsSL https://raw

C++ 非同步程式設計探索 thread safe

前言 在非同步程式設計實踐中,曾經遇到的最大的問題就是thread safe 問題。 我所在的團隊是Database團隊,主要是為APP提供和redis互動的API。場景就是application thread呼叫我們的API。我們有個worker thread

vue v-for迴圈巢狀的探索

使用v-for迴圈的目的就是為了處理大量型別重複的資料,歸根結底是一種有規律的資料,但是有些規律卻不是那麼容易的,很多時候,我們會使用到迴圈,甚至多重迴圈的巢狀,不同的迴圈巢狀對應著不同的json資料的結構,本篇主要講述的是使用v-for迴圈解決部分同,部分不同的情況,主要是

iOS自動化探索WDA(WebDriverAgent) API的使用

前面我們已經安裝好了WebdriverAgent, 現在可以用Facebook官方提供的API來進行一些操作 WDA API官方頁面: https://github.com/facebook/WebDriverAgent/wiki/Queries 如果在終端執行的話可以使用curl來呼叫API,需要搭配

Android外掛化探索資源載入

前情提要 PathClassLoader和DexClassLoader的區別 DexClassLoader的原始碼如下: public class DexClassLoader extends BaseDexClassLoader {

React Native探索:佈局篇

可以看到iphone 6的寬度為 375pt,對應了上邊的375,由此可見react的單位為pt。 那如何獲取實際的畫素尺寸呢? 這對圖片的高清化很重要,如果我的圖片大小為100*100 px. 設定寬度為100 * 100. 那在iphone上的尺寸就是模糊的。 這個時候需要的影象大小應該是 100 *

scrapy研究探索——爬w3school.com.cn

下午被一個問題困擾了好一陣,最終使用另一種方式解決。 在開始之前假設你已經成功安裝一切所需,整懷著一腔熱血想要抓取某網站。一起來have a try。 1. 前期基礎準備。 Oh,不能在準備了,直接來。 (1) 建立專案。 輸入: scapy startproject

CAS實現登入:自定義的使用者驗證登入

上一篇演示單點登入服務端認證機制採用的是cas server預設的使用者名稱和密碼(admin/admin)。今天介紹正常專案中如何通過驗證DB中的使用者資料,來驗證使用者的密碼的合法性 自定義驗證登入有兩種方式: 採用cas-server預設的資料庫查詢

Socket程式設計回射客戶/伺服器的實現

TCP客戶/伺服器模型,即C/S模型 可以將這個模型類比為打電話。 TCP伺服器通過socket()建立一個套接字,相當於安裝一臺話機;然後為安裝好的話機繫結bind()一個電話號碼;讓話機處於監聽的狀態listen();等待客戶端的連線accept(),也就是等待對方電話撥打過來,如果一直

mycat搭建主從複製,實現讀寫分離

環境準備 準備三臺linux伺服器,mycat、mycatmaster、mycatslave,分別安裝好mycat服務、mysql服務、mysql服務 mycatmaster和mycatslave基於binlog實現主從複製,見主從複製搭建 mycat配置 mycat權威指南中配

Java併發程式設計多執行緒四種實現方式

Java實現多執行緒的方式 Java實現多執行緒的方式有4種: 繼承Thread方法、實現Runnable介面、實現Callable介面並通過FutureTask建立執行緒、使用ExecutorService。 其中,前兩種執行緒執行結果沒有返回值,後兩種是有返回值的。 1、繼承Th

IO多路複用 -- select、poll、epoll實現TCP反射程式

接著上文IO多路複用(一)-- Select、Poll、Epoll,接下來將演示一個TCP回射程式,原始碼來自於該博文https://www.cnblogs.com/Anker...,在這裡將其進行了整合,突出select、poll和epoll不同方法之間的比較,但