整合營銷服務(wù)商

          電腦端+手機端+微信端=數(shù)據(jù)同步管理

          免費咨詢熱線:

          java-socket長連接demo體驗

          java-socket長連接demo體驗
          作者:DavidDing
          來源:https://zhuanlan.zhihu.com/p/56135195
          

          、前言

          最近公司在預(yù)研設(shè)備app端與服務(wù)端的交互方案,主要方案有:

          • 服務(wù)端和app端通過阿里iot套件實現(xiàn)消息的收發(fā);
          • 服務(wù)端通過極光推送主動給app端推消息,app通過rest接口與服務(wù)端進行交互;
          • 服務(wù)端與app通過mqtt消息隊列來實現(xiàn)彼此的消息交互;
          • 服務(wù)端與app通過原生socket長連接交互。

          雖然上面的一些成熟方案肯定更利于上生產(chǎn)環(huán)境,但它們通訊基礎(chǔ)也都是socket長連接,所以本人主要是預(yù)研了一下socket長連接的交互,寫了個簡單demo,采用了BIO的多線程方案,集成了springboot,實現(xiàn)了自定義簡單協(xié)議,心跳機制,socket客戶端身份強制驗證,socket客戶端斷線獲知等功能,并暴露了一些接口,可通過接口簡單實現(xiàn)客戶端與服務(wù)端的socket交互。

          Github源碼:

          https://github.com/DavidDingXu/springboot-socket-demo

          二、IO通訊模型

          1. IO通訊模型簡介

          IO通訊模型主要包括阻塞式同步IO(BIO),非阻塞式同步IO,多路復(fù)用IO以及異步IO。

          該部分內(nèi)容總結(jié)自專欄文章:

          https://blog.csdn.net/yinwenjie/column/info/sys-communication/3

          1.1 阻塞式同步IO

          BIO就是:blocking IO。最容易理解、最容易實現(xiàn)的IO工作方式,應(yīng)用程序向操作系統(tǒng)請求網(wǎng)絡(luò)IO操作,這時應(yīng)用程序會一直等待;另一方面,操作系統(tǒng)收到請求后,也會等待,直到網(wǎng)絡(luò)上有數(shù)據(jù)傳到監(jiān)聽端口;操作系統(tǒng)在收集數(shù)據(jù)后,會把數(shù)據(jù)發(fā)送給應(yīng)用程序;最后應(yīng)用程序受到數(shù)據(jù),并解除等待狀態(tài)。



          BIO通訊示意圖

          1.2 非阻塞式同步IO

          這種模式下,應(yīng)用程序的線程不再一直等待操作系統(tǒng)的IO狀態(tài),而是在等待一段時間后,就解除阻塞。如果沒有得到想要的結(jié)果,則再次進行相同的操作。這樣的工作方式,暴增了應(yīng)用程序的線程可以不會一直阻塞,而是可以進行一些其他工作。



          非阻塞式IO示意圖

          1.3 多路復(fù)用IO(阻塞+非阻塞)



          多路復(fù)用IO示意圖

          目前流程的多路復(fù)用IO實現(xiàn)主要包括四種:select、poll、epoll、kqueue。下表是他們的一些重要特性的比較:



          1.4 異步IO

          異步IO則是采用“訂閱-通知”模式:即應(yīng)用程序向操作系統(tǒng)注冊IO監(jiān)聽,然后繼續(xù)做自己的事情。當(dāng)操作系統(tǒng)發(fā)生IO事件,并且準(zhǔn)備好數(shù)據(jù)后,在主動通知應(yīng)用程序,觸發(fā)相應(yīng)的函數(shù)。



          異步IO示意圖

          和同步IO一樣,異步IO也是由操作系統(tǒng)進行支持的。微軟的windows系統(tǒng)提供了一種異步IO技術(shù):IOCP(I/O Completion Port,I/O完成端口);

          Linux下由于沒有這種異步IO技術(shù),所以使用的是epoll(上文介紹過的一種多路復(fù)用IO技術(shù)的實現(xiàn))對異步IO進行模擬。

          2. Java對IO模型的支持

          • Java對阻塞式同步IO的支持主要是java.net包中的Socket套接字實現(xiàn);
          • Java中非阻塞同步IO模式通過設(shè)置serverSocket.setSoTimeout(100);即可實現(xiàn);
          • Java 1.4中引入了NIO框架(java.nio包)可以構(gòu)建多路復(fù)用、同步非阻塞IO程序;
          • Java 7中對NIO進行了進一步改進,即NIO2,引入了異步非阻塞IO方式。

          由于是要實現(xiàn)socket長連接的demo,主要關(guān)注其一些實現(xiàn)注意點及方案,所以本demo采用了BIO的多線程方案,該方案代碼比較簡單、直觀,引入了多線程技術(shù)后,IO的處理吞吐量也大大提高了。下面是BIO多線程方案server端的簡單實現(xiàn):

          public static void main(String[] args) throws Exception{
           ServerSocket serverSocket=new ServerSocket(83);
           try {
           while(true) {
           Socket socket=null;
           socket=serverSocket.accept();
           //這邊獲得socket連接后開啟一個線程監(jiān)聽處理數(shù)據(jù)
           SocketServerThread socketServerThread=new SocketServerThread(socket);
           new Thread(socketServerThread).start();
           }
           } catch(Exception e) {
           log.error("Socket accept failed. Exception:{}", e.getMessage());
           } finally {
           if(serverSocket !=null) {
           serverSocket.close();
           }
           }
           }
          }
          @slf4j
          class SocketServerThread implements Runnable {
           private Socket socket;
           public SocketServerThread (Socket socket) {
           this.socket=socket;
           }
           @Override
           public void run() {
           InputStream in=null;
           OutputStream out=null;
           try {
           in=socket.getInputStream();
           out=socket.getOutputStream();
           Integer sourcePort=socket.getPort();
           int maxLen=2048;
           byte[] contextBytes=new byte[maxLen];
           int realLen;
           StringBuffer message=new StringBuffer();
           BIORead:while(true) {
           try {
           while((realLen=in.read(contextBytes, 0, maxLen)) !=-1) {
           message.append(new String(contextBytes , 0 , realLen));
           /*
           * 我們假設(shè)讀取到“over”關(guān)鍵字,
           * 表示客戶端的所有信息在經(jīng)過若干次傳送后,完成
           * */
           if(message.indexOf("over") !=-1) {
           break BIORead;
           }
           }
           }
           //下面打印信息
           log.info("服務(wù)器(收到來自于端口:" + sourcePort + "的信息:" + message);
           //下面開始發(fā)送信息
           out.write("回發(fā)響應(yīng)信息!".getBytes());
           //關(guān)閉
           out.close();
           in.close();
           this.socket.close();
           } catch(Exception e) {
           log.error("Socket read failed. Exception:{}", e.getMessage());
           }
           }
          }
          

          三、注意點及實現(xiàn)方案

          1. TCP粘包/拆包

          1.1 問題說明

          假設(shè)客戶端分別發(fā)送了兩個數(shù)據(jù)包D1和D2給服務(wù)端,由于服務(wù)端一次讀取到的字節(jié)數(shù)是不確定的,故可能存在以下4種情況。 1. 服務(wù)端分兩次讀取到了兩個獨立的數(shù)據(jù)包,分別是D1和D2,沒有粘包和拆包; 2. 服務(wù)端一次接收到了兩個數(shù)據(jù)包,D1和D2粘合在一起,被稱為TCP粘包; 3. 服務(wù)端分兩次讀取到了兩個數(shù)據(jù)包,第一次讀取到了完整的D1包和D2包的部分內(nèi)容,第二次讀取到了D2包的剩余內(nèi)容,這被稱為TCP拆包; 4. 服務(wù)端分兩次讀取到了兩個數(shù)據(jù)包,第一次讀取到了D1包的部分內(nèi)容D1_1,第二次讀取到了D1包的剩余內(nèi)容D1_2和D2包的整包。如果此時服務(wù)端TCP接收滑窗非常小,而數(shù)據(jù)包D1和D2比較大,很有可能會發(fā)生第五種可能,即服務(wù)端分多次才能將D1和D2包接收完全,期間發(fā)生多次拆包。

          1.2 解決思路

          由于底層的TCP無法理解上層的業(yè)務(wù)數(shù)據(jù),所以在底層是無法保證數(shù)據(jù)包不被拆分和重組的,這個問題只能通過上層的應(yīng)用協(xié)議棧設(shè)計來解決,根據(jù)業(yè)界的主流協(xié)議的解決方案,可以歸納如下: 1. 消息定長,例如每個報文的大小為固定長度200字節(jié),如果不夠,空位補空格; 2. 在包尾增加回車換行符進行分割,例如FTP協(xié)議; 3. 將消息分為消息頭和消息體,消息頭中包含表示消息總長度(或者消息體長度)的字段,通常設(shè)計思路為消息頭的第一個字段使用int32來表示消息的總長度; 4. 更復(fù)雜的應(yīng)用層協(xié)議。

          1.3 demo方案

          作為socket長連接的demo,使用了上述的解決思路2,即在包尾增加回車換行符進行數(shù)據(jù)的分割,同時整體數(shù)據(jù)使用約定的Json體進行作為消息的傳輸格式。

          使用換行符進行數(shù)據(jù)分割,可如下進行數(shù)據(jù)的單行讀取:

          BufferedReader reader=new BufferedReader(new InputStreamReader(socket.getInputStream()));
          String message;
          while ((message=reader.readLine()) !=null) {
          //....
          }
          

          可如下進行數(shù)據(jù)的單行寫入:

          PrintWriter writer=new PrintWriter(new OutputStreamWriter(socket.getOutputStream()), true);
          writer.println(message);
          

          Json消息格式如下:

          (1) 服務(wù)端接收消息實體類

          @Data
          public class ServerReceiveDto implements Serializable {
           private static final long serialVersionUID=6600253865619639317L;
           /**
           * 功能碼 0 心跳 1 登陸 2 登出 3 發(fā)送消息
           */
           private Integer functionCode;
           /**
           * 用戶id
           */
           private String userId;
           /**
           * 這邊假設(shè)是string的消息體
           */
           private String message;
          }
          

          (2) 服務(wù)端發(fā)送消息實體類

          @Data
          public class ServerSendDto implements Serializable {
           private static final long serialVersionUID=-7453297551797390215L;
           /**
           * 狀態(tài)碼 20000 成功,否則有errorMessage
           */
           private Integer statusCode;
           private String message;
           /**
           * 功能碼
           */
           private Integer functionCode;
           /**
           * 錯誤消息
           */
           private String errorMessage;
          }
          

          (3) 客戶端發(fā)送消息實體類

          @Data
          public class ClientSendDto implements Serializable {
           private static final long serialVersionUID=97085384412852967L;
           /**
           * 功能碼 0 心跳 1 登陸 2 登出 3 發(fā)送消息
           */
           private Integer functionCode;
           /**
           * 用戶id
           */
           private String userId;
           /**
           * 這邊假設(shè)是string的消息體
           */
           private String message;
          }
          

          2. 客戶端或服務(wù)端掉線檢測功能

          2.1 實現(xiàn)思路

          通過自定義心跳包來實現(xiàn)掉線檢測功能,具體思路如下:

          客戶端連接上服務(wù)端后,在服務(wù)端會維護一個在線客戶端列表。客戶端每隔一段時間,向服務(wù)端發(fā)送一個心跳包,服務(wù)端受收到包以后,會更新客戶端最近一次在線時間。一旦服務(wù)端超過規(guī)定時間沒有接收到客戶端發(fā)來的包,則視為掉線。

          2.2 代碼實現(xiàn)

          維護一個客戶端map,其中key代表用戶的唯一id(用戶唯一id的身份驗證下面會說明),value代表用戶對應(yīng)的一個實體

          /**
           * 存儲當(dāng)前由用戶信息活躍的的socket線程
           */
          private ConcurrentMap<String, Connection> existSocketMap=new ConcurrentHashMap<>();
          

          其中Connection對象包含的信息如下:

          @Slf4j
          @Data
          public class Connection {
           /**
           * 當(dāng)前的socket連接實例
           */
           private Socket socket;
           /**
           * 當(dāng)前連接線程
           */
           private ConnectionThread connectionThread;
           /**
           * 當(dāng)前連接是否登陸
           */
           private boolean isLogin;
           /**
           * 存儲當(dāng)前的user信息
           */
           private String userId;
           /**
           * 創(chuàng)建時間
           */
           private Date createTime;
           /**
           * 最后一次更新時間,用于判斷心跳
           */
           private Date lastOnTime;
          }
          

          主要關(guān)注其中的lastOnTime字段,每次服務(wù)端接收到標(biāo)識是心跳數(shù)據(jù),會更新當(dāng)前的lastOnTime字段,代碼如下:

          if (functionCode.equals(FunctionCodeEnum.HEART.getValue())) {
           //心跳類型
           connection.setLastOnTime(new Date());
           //發(fā)送同樣的心跳數(shù)據(jù)給客戶端
           ServerSendDto dto=new ServerSendDto();
           dto.setFunctionCode(FunctionCodeEnum.HEART.getValue());
           connection.println(JSONObject.toJSONString(dto));
          }
          

          額外會有一個監(jiān)測進程,以一定頻率來監(jiān)測上述維護的map中的每一個Connection對象,如果當(dāng)前時間與lastOnTime的時間間隔超過自定義的長度,則自動將其對應(yīng)的socket連接關(guān)閉,代碼如下:

          Date now=new Date();
          Date lastOnTime=connectionThread.getConnection().getLastOnTime();
          long heartDuration=now.getTime() - lastOnTime.getTime();
          if (heartDuration > SocketConstant.HEART_RATE) {
           //心跳超時,關(guān)閉當(dāng)前線程
           log.error("心跳超時");
           connectionThread.stopRunning();
          }
          

          在上面代碼中,服務(wù)端收到標(biāo)識是心跳數(shù)據(jù)的時候,除了更新該socket對應(yīng)的lastOnTime,還會同樣同樣心跳類型的數(shù)據(jù)給客戶端,客戶端收到標(biāo)識是心跳數(shù)據(jù)的時候也會更新自己的lastOnTime字段,同時也有一個心跳監(jiān)測線程在監(jiān)測當(dāng)前的socket連接心跳是否超時

          3. 客戶端身份獲知、強制身份驗證

          3.1 實現(xiàn)思路

          通過代碼socket=serverSocket.accept()獲得的一個socket連接我們僅僅只能知道其客戶端的ip以及端口號,并不能獲知這個socket連接對應(yīng)的到底是哪一個客戶端,因此必須得先獲得客戶端的身份并且驗證通過其身份才能讓其正常連接。

          具體的實現(xiàn)思路是:

          自定義一個登陸處理接口,當(dāng)server端受到標(biāo)識是用戶登陸的時候(此時會攜帶用戶信息或者token,此處簡化為用戶id),調(diào)用用戶的登陸驗證,驗證通過的話則將該socket連接與用戶信息綁定,設(shè)置其為已登錄,并且封裝對應(yīng)的對象放入前面提的客戶端map中,由此可獲得具體用戶對應(yīng)的哪一個socket連接。

          為了實現(xiàn)socket連接的強制驗證,在監(jiān)測線程中,也會判斷當(dāng)前用戶多長時間內(nèi)沒有實現(xiàn)登錄態(tài),若超時則認為該socket連接為非法連接,主動關(guān)閉該socket連接。

          3.2 代碼實現(xiàn)

          自定義登陸處理接口,這邊簡單以userId來判斷是否允許登陸:

          public interface LoginHandler {
           /**
           * client登陸的處理函數(shù)
           *
           * @param userId 用戶id
           *
           * @return 是否驗證通過
           */
           boolean canLogin(String userId);
          }
          

          收到客戶端發(fā)來的數(shù)據(jù)時候的處理:

          if (functionCode.equals(FunctionCodeEnum.LOGIN.getValue())) {
           //登陸,身份驗證
           String userId=receiveDto.getUserId();
           if (socketServer.getLoginHandler().canLogin(userId)) {
           //設(shè)置用戶對象已登錄狀態(tài)
           connection.setLogin(true);
           connection.setUserId(userId);
           if (socketServer.getExistSocketMap().containsKey(userId)) {
           //存在已登錄的用戶,發(fā)送登出指令并主動關(guān)閉該socket
           Connection existConnection=socketServer.getExistSocketMap().get(userId);
           ServerSendDto dto=new ServerSendDto();
           dto.setStatusCode(999);
           dto.setFunctionCode(FunctionCodeEnum.MESSAGE.getValue());
           dto.setErrorMessage("force logout");
           existConnection.println(JSONObject.toJSONString(dto));
           existConnection.getConnectionThread().stopRunning();
           log.error("用戶被客戶端重入踢出,userId:{}", userId);
           }
           //添加到已登錄map中
           socketServer.getExistSocketMap().put(userId, connection);
          }
          

          監(jiān)測線程判斷用戶是否完成身份驗證:

          if (!connectionThread.getConnection().isLogin()) {
           //還沒有用戶登陸成功
           Date createTime=connectionThread.getConnection().getCreateTime();
           long loginDuration=now.getTime() - createTime.getTime();
           if (loginDuration > SocketConstant.LOGIN_DELAY) {
           //身份驗證超時
           log.error("身份驗證超時");
           connectionThread.stopRunning();
           }
          }
          

          4. socket異常處理與垃圾線程回收

          4.1 實現(xiàn)思路

          socket在讀取數(shù)據(jù)或者發(fā)送數(shù)據(jù)的時候會出現(xiàn)各種異常,比如客戶端的socket已斷開連接(正常斷開或物理連接斷開等),但是服務(wù)端還在發(fā)送數(shù)據(jù)或者還在接受數(shù)據(jù)的過程中,此時socket會拋出相關(guān)異常,對于該異常的處理需要將自身的socket連接關(guān)閉,避免資源的浪費,同時由于是多線程方案,還需將該socket對應(yīng)的線程正常清理。

          4.2 代碼實現(xiàn)

          下面以server端發(fā)送數(shù)據(jù)為例,該代碼中加入了重試機制:

          public void println(String message) {
           int count=0;
           PrintWriter writer;
           do {
           try {
           writer=new PrintWriter(new OutputStreamWriter(socket.getOutputStream()), true);
           writer.println(message);
           break;
           } catch (IOException e) {
           count++;
           if (count >=RETRY_COUNT) {
           //重試多次失敗,說明client端socket異常
           this.connectionThread.stopRunning();
           }
           }
           try {
           Thread.sleep(2 * 1000);
           } catch (InterruptedException e1) {
           log.error("Connection.println.IOException interrupt,userId:{}", userId);
           }
           } while (count < 3);
          }
          

          上述調(diào)用的this.connectionThread.stopRunning()代碼如下:

          public void stopRunning() {
           //設(shè)置線程對象狀態(tài),便于線程清理
           isRunning=false;
           try {
           //異常情況需要將該socket資源釋放
           socket.close();
           } catch (IOException e) {
           log.error("ConnectionThread.stopRunning failed.exception:{}", e);
           }
          }
          

          上述代碼中設(shè)置了線程對象的狀態(tài),下述代碼在監(jiān)測線程中執(zhí)行,將沒有運行的線程給清理掉

          /**
           * 存儲只要有socket處理的線程
           */
          private List<ConnectionThread> existConnectionThreadList=Collections.synchronizedList(new ArrayList<>());
          /**
           * 中間list,用于遍歷的時候刪除
           */
          private List<ConnectionThread> noConnectionThreadList=Collections.synchronizedList(new ArrayList<>());
          //...
          //刪除list中沒有用的thread引用
          existConnectionThreadList.forEach(connectionThread -> {
           if (!connectionThread.isRunning()) {
           noConnectionThreadList.add(connectionThread);
           }
          });
          noConnectionThreadList.forEach(connectionThread -> {
           existConnectionThreadList.remove(connectionThread);
           if (connectionThread.getConnection().isLogin()) {
           //說明用戶已經(jīng)身份驗證成功了,需要刪除map
           this.existSocketMap.remove(connectionThread.getConnection().getUserId());
           }
          });
          noConnectionThreadList.clear();
          

          四、項目結(jié)構(gòu)

          由于使用了springboot框架來實現(xiàn)該demo,所以項目結(jié)構(gòu)如下:

          整體項目結(jié)構(gòu)圖

          socket工具包目錄如下:

          socket工具包目錄

          pom文件主要添加了springboot的相關(guān)依賴,以及json工具和lombok工具等,依賴如下:

          <parent>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter-parent</artifactId>
           <version>2.0.3.RELEASE</version>
           <relativePath/>
          </parent>
          <dependencies>
           <dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter-web</artifactId>
           </dependency>
           <dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter-test</artifactId>
           </dependency>
           <dependency>
           <groupId>org.projectlombok</groupId>
           <artifactId>lombok</artifactId>
           </dependency>
           <dependency>
           <groupId>com.alibaba</groupId>
           <artifactId>fastjson</artifactId>
           <version>1.2.36</version>
           </dependency>
          </dependencies>
          

          自己寫的socket工具包的使用方式如下:

          @Configuration
          @Slf4j
          public class SocketServerConfig {
          @Bean
          public SocketServer socketServer() {
           SocketServer socketServer=new SocketServer(60000);
           socketServer.setLoginHandler(userId -> {
           log.info("處理socket用戶身份驗證,userId:{}", userId);
           //用戶名中包含了dingxu則允許登陸
           return userId.contains("dingxu");
           });
           socketServer.setMessageHandler((connection, receiveDto) -> log
           .info("處理socket消息,userId:{},receiveDto:{}", connection.getUserId(),
           JSONObject.toJSONString(receiveDto)));
           socketServer.start();
           return socketServer;
          }
          }
          

          該demo中主要提供了以下幾個接口進行測試:

          • 服務(wù)端:獲得當(dāng)前用戶列表,發(fā)送一個消息客戶端:開始一個socket客戶端,發(fā)送一個消息,關(guān)閉一個socket客戶端,查看已開啟的客戶端

          具體的postman文件也放已在項目中,具體可點此鏈接獲得

          demo中還提供了一個簡單壓測函數(shù),如下:

          @Slf4j
          public class SocketClientTest {
           public static void main(String[] args) {
           ExecutorService clientService=Executors.newCachedThreadPool();
           String userId="dingxu";
           for (int i=0; i < 1000; i++) {
           int index=i;
           clientService.execute(() -> {
           try {
           SocketClient client;
           client=new SocketClient(InetAddress.getByName("127.0.0.1"), 60000);
           //登陸
           ClientSendDto dto=new ClientSendDto();
           dto.setFunctionCode(FunctionCodeEnum.LOGIN.getValue());
           dto.setUserId(userId + index);
           client.println(JSONObject.toJSONString(dto));
           ScheduledExecutorService clientHeartExecutor=Executors.newSingleThreadScheduledExecutor(
           r -> new Thread(r, "socket_client+heart_" + r.hashCode()));
           clientHeartExecutor.scheduleWithFixedDelay(() -> {
           try {
           ClientSendDto heartDto=new ClientSendDto();
           heartDto.setFunctionCode(FunctionCodeEnum.HEART.getValue());
           client.println(JSONObject.toJSONString(heartDto));
           } catch (Exception e) {
           log.error("客戶端異常,userId:{},exception:{}", userId, e.getMessage());
           client.close();
           }
           }, 0, 5, TimeUnit.SECONDS);
           while (true){
           }
           } catch (Exception e) {
           log.error(e.getMessage());
           }
           });
           }
           }
          }
          

          源碼地址如下,僅供學(xué)習(xí)參考

          github.com/DavidDingXu/springboot-socket-demo

          五、參考

          本樣式對齊文本text-align屬性用于指定文本塊的對齊方式,可選值包括: 1)start:內(nèi)容對齊開始邊界,默認; 2)end:內(nèi)容對齊結(jié)束邊界; 3)left:內(nèi)容左對齊; 4)right:內(nèi)容右對齊; 5)center:內(nèi)容居中對齊; 6)justify:內(nèi)容兩端對齊。當(dāng)text-align屬性使用了justify值時,可以使用text-justify屬性指定文本添加空白的方式,這個屬性...
          了解了包的概念,就可以系統(tǒng)的介紹Java中的訪問控制級別。在Java中,針對類、成員方法和屬性提供了四種訪問級別,分別是private、default、protected和public。 權(quán)限訪問修飾符(權(quán)限從大到小依次往右排) public(公共) protected(受保護) default(缺省) private(私有) 同一個類 √ √...
          Rust 提供了代碼封裝的機制。可以通過crate (等同于Java中的package)創(chuàng)建相對獨立的module模塊,模塊中封裝了可以重復(fù)使用的功能函數(shù)。當(dāng)創(chuàng)建了自己的 lib 庫或者要使用第三方的庫的時候(這些庫就是一些事先寫好的crate)需要將這些庫中的module 模塊引用到當(dāng)前的環(huán)境中。Rust提供了以下幾種引用方式:一、使用 extern crate在使用這些Module的文件中,通過...
          填空題: 他______犧牲生命_______出賣組織? 據(jù)數(shù)據(jù)統(tǒng)計,不同年代的同學(xué)回復(fù)的最多的是….. 60后,他寧可犧牲生命,也不出賣組織。 70后,他害怕犧牲生命,所以出賣組織。 80后,他與其犧牲生命,不如出賣組織。 90后,他即使?fàn)奚惨鲑u組織。 00后,他白白犧牲了生命,忘了出賣組織。 上邊的案例,引發(fā)了大家對”自我與企業(yè)關(guān)系的思考”. 能力與欲望...
          歷屆試題 國王的煩惱 時間限制:1.0s 內(nèi)存限制:256.0MB 問題描述 C國由n個小島組成,為了方便小島之間聯(lián)絡(luò),C國在小島間建立了m座大橋,每座大橋連接兩座小島。兩個小島間可能存在多座橋連接。然而,由于海水沖刷,有一些大橋面臨著不能使用的危險。 如果兩個小島間的所有大橋都不能使用,則這兩座小島就不能直接到達了。然而,只要這兩座...
          go test命令參數(shù)問題在使用go test對go代碼進行單元測試的時候,遇到關(guān)于命令參數(shù)的問題,google了一下,沒有找到很好的說明,其實就是一些細節(jié)而已。問題是這樣的,在進行單元測試的時候,我希望輸入一些命令行參數(shù)來控制程序的運行。 參考go官方文檔,只需要在go test后面加上-args和參數(shù)就可以了 例如 go test -args -classpath E:\testcase...
          阿里云OSS-使用經(jīng)驗總結(jié),存儲,賬號-權(quán)限,分頁,縮略圖,賬號切換最近項目中,需要使用云存儲,最后選擇了阿里云-對象存儲服務(wù)OSS。總的來說,比較簡單,但是仍然遇到了幾個問題,需要總結(jié)下。1.OSS總的使用介紹 https://help.aliyun.com/document_detail/oss/sdk/java-sdk/manage_object.html?spm=5176.docoss/...
          WEB應(yīng)用圖片的格式,以及各自的特點和優(yōu)化(一) by FungLeo前言12年前我入行三天.用table布局做了一個非常粗糙的網(wǎng)頁.我說了一句話,”網(wǎng)頁就是表格加文字加圖片,圖片分兩種,插入圖片和背景圖片”.這句話在今天看來,當(dāng)然是一個笑話.但是當(dāng)時我說出這句話的時候,當(dāng)時的那些前輩都非常認可我的總結(jié),并且認為我很有從事網(wǎng)絡(luò)發(fā)展的潛力啊.哎,要不是他們的鼓勵,說不定我早轉(zhuǎn)行了……扯遠了.說回正題,...
          1. 單表數(shù)據(jù)的導(dǎo)出針對單表數(shù)據(jù)的導(dǎo)出操作,MongoDB 提供了 mongoexport 命令。mongoexport 既可以將數(shù)據(jù)導(dǎo)出為 CSV 格式的文件,也可以導(dǎo)出 JSON 格式的文件。這兩者之間的區(qū)別是:JSON 是 mongoexport 默認的導(dǎo)出格式,不需要指定,而要導(dǎo)出 CSV 格式的話需要明確指定;導(dǎo)出 CSV 格式必須顯式指定各屬性名,而導(dǎo)出 JSON 格式不需要。由此可見...
          商業(yè)智能對于中小企業(yè)來說,由于其高昂的費用和運行維護技術(shù)水平要求高,往往難以承受,商業(yè)智能SAAS系統(tǒng)平臺+模塊的創(chuàng)新模式的出現(xiàn)能幫助中小企業(yè)走上商業(yè)智能之路。...
          stack.sh給出了一個非常好的例子,關(guān)于學(xué)習(xí)openstack創(chuàng)建 1.檢查devstack文件,檢查bash4.2以上,檢查用戶,不能是root2.準(zhǔn)備環(huán)境,導(dǎo)入函數(shù)3.檢查local.conf和localrc是否都存在,如果存在使用localrc4.檢查是否已經(jīng)運行devstack5.代理設(shè)置和禁用無效服務(wù)6.配置sudo7.配置distro庫8.配置目標(biāo)目錄,創(chuàng)建目標(biāo)目錄9.配置主機、日...
          博客地址:http://blog.csdn.net/FoxDave本文介紹如何利用SharePoint客戶端對象模型(.NET)逐級獲取Office 365網(wǎng)站中List的內(nèi)容,僅僅是示例,沒有講究太多東西。代碼如下:ClientContext ctx=new ClientContext(""); ctx.Credentials=new SharePointOn...
          題外話Atom,風(fēng)風(fēng)雨雨走過一年多了.,目前最新版本是V1.7.0 .社區(qū)還是相當(dāng)活躍;體驗也改善了很多;但是性能上還是欠缺;今天我再來介紹自己常用的一款插件git-control插件介紹 官方介紹頁面 作者: jacogr Github地址 我的介紹 就是命令行的GUI版本,,有些類似sourcetree,但是不如它強大,日用滿足使用在編輯器下加載git版本的工作目錄;工具默認啟用快捷鍵...
          安裝devstack后,如果沒有設(shè)置參數(shù),執(zhí)行openstack命令是不成功的。1.登錄到horizon頁面,使用admin登入,進入project->compute-> Access&Security -> API Access,記錄下Service Endpoint。或選擇download OpenStack RC File按鈕,下載demo-openrc.sh文件2.將demo-openrc...
          對于這樣的問題,看到第一眼就是暴力破解,所以也就遞歸找到所有情況,再篩選出合格的小明被劫持到X賭城,被迫與其他3人玩牌。 一副撲克牌(去掉大小王牌,共52張),均勻發(fā)給4個人,每個人13張。 這時,小明腦子里突然冒出一個問題: 如果不考慮花色,只考慮點數(shù),也不考慮自己得到的牌的先后順序,自己手里能拿到的初始牌型組合一共有多少種呢?思路: 首先無論怎么取,手牌為13張的時候結(jié)束.也就是遞歸結(jié)束標(biāo)...
          網(wǎng)紅和粉絲經(jīng)濟,是最近幾年流行起來的概念。 截至目前,有一些初步的認識,整理成文。 粉絲,最早是明星的跟隨者比較多。 我的理解是,對于一個人物、動物、運動等,有著共同的興趣,從而建立多個人和一個人之間的關(guān)系,比如粉絲和明星。 粉絲經(jīng)濟,大獲成功的標(biāo)志是,雷軍和小米科技。在創(chuàng)業(yè)早期,就把粉絲經(jīng)濟和社交傳播結(jié)合在一起,低成本地實現(xiàn)了全網(wǎng)營銷。從此以后,各大手機廠商等很多領(lǐng)域的企業(yè),都...
          關(guān)于android端apk退出方式的設(shè)計,現(xiàn)在大體只有下面幾種:1,有退出和取消按鈕;2,一定時間內(nèi)兩次返回為退出;3,一次返回就是退出。首先可以看到這兩個用按鈕的,退出都在左側(cè),設(shè)計者肯定沒有看過十年前雅虎研究院出的web端設(shè)計指導(dǎo),下一步的操作一定是在右側(cè),而返回上一步的操作是在左側(cè)。但是到了移動端應(yīng)該考慮用戶是左手還是右手使用,也就是說,如果是左手使用,這個位置設(shè)計沒有問題,反之就不用說了。...
          寫在最前:本文主要描述在網(wǎng)站的不同的并發(fā)訪問量級下,Mysql架構(gòu)的演變可擴展性架構(gòu)的可擴展性往往和并發(fā)是息息相關(guān),沒有并發(fā)的增長,也就沒有必要做高可擴展性的架構(gòu),這里對可擴展性進行簡單介紹一下,常用的擴展手段有以下兩種Scale-up : 縱向擴展,通過替換為更好的機器和資源來實現(xiàn)伸縮,提升服務(wù)能力Scale-out : 橫向擴展, 通過加節(jié)點(機器)來實現(xiàn)伸縮,提升服務(wù)能力對于互聯(lián)網(wǎng)的高并...
          angular.js中,指令是最基礎(chǔ)的也是最重要的工具之一。angular.js指令指的是以ng為前綴的HTML屬性。在之前的ng-app、ng-model等,都屬于指令。 angular.js中的基本指令包括如下內(nèi)容: · 1.ng-app/ng-model ng-app指令用于聲明angular,js的作用范圍,ng-model用于聲明模型。這些在之前都已經(jīng)進行過詳細介紹。 2.ng-...
          java編碼 當(dāng)你的字節(jié)序列是某種編碼時,這個時候想把字節(jié)序列變成 字符串,也需要用這種編碼方式,否則會出現(xiàn)亂碼 文本文件就是字節(jié)序列 可以是任意編碼的序列,如果在中文機器上直接創(chuàng)建文本文件,那么該文本文件 只認識ANSI編碼 案例: public class Bianma { public static void main(Strin...
          調(diào)試JDK源碼-一步一步看HashMap怎么Hash和擴容調(diào)試JDK源碼-ConcurrentHashMap實現(xiàn)原理調(diào)試JDK源碼-HashSet實現(xiàn)原理調(diào)試JDK源碼-調(diào)試JDK源碼-Hashtable實現(xiàn)原理以及線程安全的原因 ConcurrentHashMap線程安全的總結(jié)是我從源碼分析出來的:ConcurrentHashMap所謂線程安全是哈希沖突的時候新增的節(jié)點是線程安全的,而 Conc...
          對于后臺系統(tǒng)的搜索進行UI自動化,主要是比對頁面查詢結(jié)果是否與預(yù)期一致(即數(shù)據(jù)庫查詢結(jié)果) search.py# -*- coding:utf8 -*- import HTMLTestRunner import time import unittest import public from selenium import webdriver class Search(unittest.TestCa...
          ajax 的全稱是Asynchronous(異步的意思) JavaScript and XML,是一種創(chuàng)建交互式網(wǎng)頁應(yīng)用的網(wǎng)頁開發(fā)技術(shù) ajax技術(shù)的流行得益于google的大力推廣,正是由于google產(chǎn)品對ajax技術(shù)的廣泛應(yīng)用,使得ajax流行起來了。 Ajax其核心有JavaScript、XMLHTTPRequest、DOM對象組成,通過XmlHttpRequest對象來向服務(wù)器發(fā)異步請求,從服務(wù)器獲得數(shù)據(jù),然后用JavaScript來操作DOM而更新頁面。這其中最關(guān)鍵的一步就是從服務(wù)器獲得請...
          一、SpringMVChttp://blog.csdn.net/evankaka/article/details/45501811Spring Web MVC是一種基于Java的實現(xiàn)了Web MVC設(shè)計模式的請求驅(qū)動類型的輕量級Web框架,即使用了MVC架構(gòu)模式的思想,將web層進行職責(zé)解耦,基于請求驅(qū)動指的就是使用請求-響應(yīng)模型,框架的目的就是幫助我們簡化開發(fā),Spring Web MVC也是要簡...
          概念: 優(yōu)化策略:字段選擇性 選擇性較低索引 可能帶來的性能問題索引選擇性=索引列唯一值/表記錄數(shù);選擇性越高索引檢索價值越高,消耗系統(tǒng)資源越少;選擇性越低索引檢索價值越低,消耗系統(tǒng)資源越多;查詢條件含有多個字段時,不要在選擇性很低字段上創(chuàng)建索引可通過創(chuàng)建組合索引來增強低字段選擇性和避免選擇性很低字段創(chuàng)建索引帶來副作用;盡量減少possible_keys,正確索引會提高sql查詢速度,過多索引...
          一. 什么是Spark? Spark是UC Berkeley AMP lab所開源的類Hadoop MapReduce的通用的并行計算框架,Spark基于map reduce算法實現(xiàn)的分布式計算,擁有Hadoop MapReduce所具有的優(yōu)點;但不同于MapReduce的是Job中間輸出和結(jié)果可以保存在內(nèi)存中,從而不再需要讀寫HDFS,因此Spark能更好地適用于數(shù)據(jù)挖掘與機器學(xué)習(xí)等需...
          相比之前的增改查,刪除就顯得簡單的多了。 這里的request的type為delete,刪除成功的status為204,404則是要刪除的記錄不存在 var id='BAD90A95-7FEA-E511-9414-ADA183AB6249'; $.ajax({ async: false, type: "DELETE ", co...
          關(guān)于JPush極光推送是國內(nèi)的服務(wù)廠商提供的一站式push服務(wù)(同時支持iOS、android),后面也加入了即時通訊的能力供app使用。致力于打造簡單、可靠、價格有競爭力的服務(wù)(簡單功能全免費,高級版才收費),讓應(yīng)用開發(fā)商可以聚焦業(yè)務(wù)開發(fā),push相關(guān)的技術(shù)實現(xiàn)全部通過極光推送來解決,僅需調(diào)用極光推送的api即可。正因為如此,開發(fā)者小伙伴們對其的評價相當(dāng)不錯。筆者的app新增了從服務(wù)器往移動客戶端...
          Mapreduce初析 Mapreduce是一個計算框架,既然是做計算的框架,那么表現(xiàn)形式就是有個輸入(input),mapreduce操作這個輸入(input),通過本身定義好的計算模型,得到一個輸出(output),這個輸出就是我們所需要的結(jié)果。 重點就是這個計算模型的運行規(guī)則。在運行一個mapreduce計算任務(wù)時候,任務(wù)過程被分為兩個階段:map階段...
          Jquery對象常用的方法:$(”p”).addClass(css中定義的樣式類型); 給某個元素添加樣式 $(”img”).attr({src:”test.jpg”,alt:”test Image”}); 給某個元素添加屬性/值,參數(shù)是map $(”img”).attr(”src”,”test.jpg”); 給某個元素添加屬性/值 $(”img”).attr(”title”, function(...

          隆匯6月29日丨極光(JG.US)盤前漲逾9%,報1.19美元。近日,極光的核心產(chǎn)品極光推送(JPush)順利通過亞馬遜云科技的多項測試及審核,正式上線亞馬遜云科技Marketplace。當(dāng)前,亞馬遜云科技Marketplace上的用戶,通過其中國區(qū)網(wǎng)站,即可直接購買和體驗極光推送(JPush)。

          本文源自格隆匯


          主站蜘蛛池模板: 日韩在线观看一区二区三区| 国模无码人体一区二区| 成人在线观看一区| 久久精品成人一区二区三区| 国产精品久久久久久一区二区三区| 国产精品盗摄一区二区在线| 人妻无码一区二区三区免费| 国产一区二区三区樱花动漫| 后入内射国产一区二区| 久久se精品一区二区| 精品一区二区三区视频| 亚洲一区二区在线视频| 91国在线啪精品一区| 高清一区二区三区| 国产亚洲福利精品一区二区| 无码精品国产一区二区三区免费 | 一区二区国产在线播放| 老熟妇仑乱一区二区视頻| 好爽毛片一区二区三区四 | 成人区人妻精品一区二区不卡视频 | 日韩a无吗一区二区三区| 日本一区二区三区精品国产| 99精品国产一区二区三区不卡 | 中文字幕无码一区二区三区本日| 国产在线精品观看一区| 无码精品一区二区三区在线| 久久一区二区精品综合| 91一区二区视频| 精品无码成人片一区二区98| 无码乱码av天堂一区二区| 福利一区二区在线| 亚洲一区视频在线播放| 精品国产一区二区三区不卡 | 精品乱码一区二区三区四区| 日韩精品一区二区三区国语自制| 78成人精品电影在线播放日韩精品电影一区亚洲 | 国产一区二区三区不卡观| 中文字幕在线视频一区| 欧洲精品码一区二区三区免费看| 国产精品一区二区毛卡片| 一区二区三区日本视频|