<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          手寫web服務器:基于NIO重構服務器,實現(xiàn)post請求處理

          共 17427字,需瀏覽 35分鐘

           ·

          2021-06-07 10:59

          前言

          前幾天一直被post請求處理的問題卡著,因此web服務器這邊也沒啥進展,再加上昨天又突然被告知要去加班,所以這個問題就一直被一次次往后拖,還好今天有時間,就抽空把這個問題徹底解決了,然后服務這邊也徹底從原來的socket,被我重構成NioServerSocketChannel,也就是我們前面說的非阻塞式socket,今天主要介紹整個重構過程,nio的知識點暫時也不打算講,因為我也沒有搞得特別清楚。好了,話不多說,直接重構。

          重構

          手寫我們重新寫了sokcet的核心程序,實現(xiàn)方式徹底改變了,首先是一個服務器接收客戶端請求的線程:

          接收服務器請求線程

           static class AcceptSocketThread extends Thread {
                  volatile boolean runningFlag = true;

                  @Override
                  public void run() {
                      try {
                          ServerSocketChannel serverChannel = ServerSocketChannel.open();
                          serverChannel.bind(new InetSocketAddress(30000));
                          serverChannel.configureBlocking(false);

                          while (runningFlag) {
                              SocketChannel channel = serverChannel.accept();

                              if (null == channel) {
                                  logger.info("服務端監(jiān)聽中.....");
                              } else {
                                  channel.configureBlocking(false);
                                  logger.info("一個客戶端上線,占用端口 :{}", channel.socket().getPort());
                                  keys.put(channel.socket().getPort(), channel);
                                  new ResponseThread().start();
                              }
                          }
                      } catch (IOException e) {
                          // TODO Auto-generated catch block
                          e.printStackTrace();
                      }
                  }
              }

          在線程內(nèi)部,我們通過ServerSocketChannel.open創(chuàng)建了一個ServerSocketChannel通信頻道,并設置頻道端口是30000;

          configureBlocking是設置當前通信是否阻塞,這里我們設置的是false,也就是非阻塞通信;

          然后通過一個死循環(huán)監(jiān)聽服務器serverChannel是否被連接,這里serverChannel.accept()返回值為null表示未建立連接或者連接被關閉;

          如果建立連接,我們將通信頻道放進keys通信頻道隊列中:

          public static volatile Map<Integer, SocketChannel> keys =
                  Collections.synchronizedMap(new HashMap<>());

          并啟動一個響應請求線程去處理這個頻道中的請求,下面我們看處理線程

          處理請求線程

          在寫這些文字時候,我發(fā)現(xiàn)這里其實沒必要創(chuàng)建隊列存放會話頻道,可以直接把這塊的隊列傳進線程,并處理(因為我這塊代碼是參考別人的,然后進行了大改,后面還需要進一步優(yōu)化)

          /**
               * 處理客戶端請求
               */

              static class ResponseThread extends Thread {
                  ByteBuffer buffer = ByteBuffer.allocate(1024);

                  @Override
                  public void run() {
                      int num = 0;
                      Iterator<Integer> ite = keys.keySet().iterator();
                      while (ite.hasNext()) {
                          int key = ite.next();
                          StringBuffer stb = new StringBuffer();
                          try {
                              SocketChannel socketChannel = keys.get(key);
                              if (Objects.isNull(socketChannel)) {
                                  break;
                              }
                              while ((num = socketChannel.read(buffer)) > 0) {
                                  buffer.flip();
                                  stb.append(charset.decode(buffer).toString());
                                  buffer.clear();
                              }
                              if (stb.length() > 0) {
                                  MsgWrapper msg = new MsgWrapper();
                                  msg.key = key;
                                  msg.msg = stb.toString();
                                  logger.info("端口:{}的通道,讀取到的數(shù)據(jù):{}",msg.key, msg.msg);
                                  msgQueue.add(msg);
                                  threadPoolExecutor.execute(new SyskeRequestNioHandler(socketChannel, msg.msg));
                                  ite.remove();
                              }
                          } catch (Exception e) {
                              ite.remove();
                              logger.error("error: 端口占用為:{},的連接的客戶端下線了", keys.get(key).socket().getPort(), e);
                          }
                      }
                      logger.info("讀取線程監(jiān)聽中......");
                  }

              }

          因為原代碼,作者的接收線程、處理線程都是在main方法啟動的,所以他這樣定義是ok的,但我這里其實就沒必要了。

          看了上面的代碼,大家會發(fā)現(xiàn),nio中不再有InputStream或者OutputStream這樣的類,這是因為nio的底層實現(xiàn)采用了新的架構,有一個selector進行頻道管理,當某個頻道有數(shù)據(jù)進來的時候,selector會切換到這個頻道進行數(shù)據(jù)處理,如果沒有數(shù)據(jù)他會去處理其他頻道的數(shù)據(jù),不像我們之前的I/O,一次通信就一個管道,沒有數(shù)據(jù)就一直等待,所以也就不會導致阻塞。

          我覺得有個例子能很好地說明這兩種模型,傳統(tǒng)的I/o就好比一個單位的電話,電話雖然很多,但是線路只有一條,同時只能有一個電話進行通話,電話不斷,其他人根本就打不進去,也沒法接電話,只能等著這個接收電話的人打完電話;

          Nio就相當于這個單位為了解決同時只能有一個人打電話這種情況,專門雇了一個接線員負責線路切換,當有電話進來以后,接線員會把對應的電話借給對應的人,這樣即提高了線路的效率,也避免了阻塞的情況。

          做完上面的改動后,我們的post請求就不再阻塞了,然后我們還優(yōu)化了request的初始化。

          優(yōu)化請求初始化

          現(xiàn)在不論get請求,還是post請求,最終都會拿到一個純文本的請求參數(shù),然后我我把它分別處理成header(請求方法、請求地址)、requestAttributeMap(請求頭參數(shù))、requestBody(請求體):

           private void initRequest() throws IllegalParameterException{
                  logger.info("SyskeRequest start init");
                  String[] inputs = input.split("\r\n");
                  System.out.println(Arrays.toString(inputs));
                  Map<String, Object> attributeMap = Maps.newHashMap();
                  boolean hasBanlk = false;
                  StringBuilder requestBodyBuilder = new StringBuilder();
                  for (int i = 0; i < inputs.length; i++) {
                      if(i == 0) {
                          String[] headers = inputs[0].split(" "3);
                          String requestMapping = headers[1];
                          if (requestMapping.contains("?")) {
                              int endIndex = requestMapping.lastIndexOf('?');
                              String requestParameterStr = requestMapping.substring(endIndex + 1);
                              requestMapping = requestMapping.substring(0, endIndex);
                              String[] split = requestParameterStr.split("&");
                              for (String s : split) {
                                  String[] split1 = s.split("=");
                                  attributeMap.put(StringUtil.trim(split1[0]), StringUtil.trim(split1[1]));
                              }

                          }
                          this.header = new RequestHear(RequestMethod.match(headers[0]), requestMapping);
                      } else {
                          if (StringUtil.isEmpty(inputs[i])) {
                              hasBanlk = true;
                          }
                          if (inputs[i].contains(":") && Objects.equals(hasBanlk, Boolean.FALSE)) {
                              String[] split = inputs[i].split(":"2);
                              attributeMap.put(split[0], split[1]);
                          } else {
                              // post 請求
                              requestBodyBuilder.append(inputs[i]);
                          }
                      }
                  }
                  requestAttributeMap = attributeMap;
                  requestBody = JSON.parseObject(requestBodyBuilder.toString());
                  logger.info("requestBodyBuilder: {}", requestBodyBuilder.toString());
                  logger.info("SyskeRequest init finished. header: {}, requestAttributeMap: {}", header, requestAttributeMap);
              }

          這里就很簡單了,就是通過\r\n分割即可。

          getpost唯一的區(qū)別就是,get請求的參數(shù)都在requestAttributeMap,而post的請求參數(shù)在requestBody

          /**
               * 處理post請求
               * @param method
               * @return
               * @throws IllegalAccessException
               * @throws InstantiationException
               * @throws InvocationTargetException
               */

              private Object doPost(Method method) throws IllegalAccessException, InstantiationException, InvocationTargetException {
                  JSONObject requestBody = (JSONObject)request.getRequestBody();
                  return doRequest(method, requestBody);
              }

              /**
               * 處理get請求
               * @param method
               * @param requestAttributeMap
               * @return
               * @throws InvocationTargetException
               * @throws IllegalAccessException
               * @throws InstantiationException
               */

              private Object doGet(Method method, Map<String, Object> requestAttributeMap) throws InvocationTargetException, IllegalAccessException, InstantiationException {
                 return doRequest(method, requestAttributeMap);
              }

          測試

          然后我們測試下看下,這里就只測試post了,這里我用的postMan

          看下后臺:

          請求體已經(jīng)有數(shù)據(jù)了,后面的就很簡單了

          總結

          我現(xiàn)在越來越覺得,作為一個web后端工程師,網(wǎng)絡編程是一個特別重要的技能,因為你不了解數(shù)據(jù)在網(wǎng)絡中的傳輸過程,不了解各種協(xié)議,不了解各種請求頭,那你再遇到具體問題的時候,是根本沒有任何思路的。

          可能在你眼里你可能會覺得一切你解決不了的問題,都是玄學問題,但事實并非如此。

          所以,對我現(xiàn)在而言,學習的方向大概分為這幾種:

          • 多線程:這個應該是一個比較核心,掌握的好,你的工作真的會事半功倍的
          • 網(wǎng)絡編程:這個原因我前面說了
          • 算法,包括數(shù)據(jù)結構等:幫助你構建更好的模型,讓你的程序運行更快,性能更好
          • 虛擬化相關知識,比如dockerk8s等,以及jenkins自動化構建,這一塊現(xiàn)在是比較主流的技術
          • 主流開源框架學習,這里我會花比較少的時間,以搞清楚具體的原理和實現(xiàn)方式為目的

          今天把這個問題解決了,后面又可以繼續(xù)實現(xiàn)springboot的其他注解了,繼續(xù)搞事情。好了,今天就到這里吧!

          下面是項目的開源倉庫,有興趣的小伙伴可以去看看,如果有想法的小伙伴,我真心推薦你自己動個手,自己寫一下,真的感覺不錯:

          https://github.com/Syske/syske-boot
          - END -


          瀏覽 53
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  欧美日韩一卡 | 久久色无码视频 | 五月天六月色婷婷在线 | 国产成人精品视频在线 | 高清在线免费观看亚洲视频 |