Appium+python自動化(四十一)-Appium自動化測試框架綜合實踐 – 即將落下帷幕(超詳解)

1.簡介

  今天我們緊接着上一篇繼續分享Appium自動化測試框架綜合實踐 – 代碼實現。到今天為止,大功即將告成;框架所需要的代碼實現都基本完成。

2.data數據封裝

2.1使用背景

在實際項目過程中,我們的數據可能是存儲在一個數據文件中,如txt,excel、csv文件類型。我們可以封裝一些方法來讀取文件中的數據來實現數據驅動。

2.2案例

將測試賬號存儲在account.csv文件,內容如下:

account.csv

hg2018

hg2018

hg2019

zxw2019

666

222

參考代碼

2.3enumerate()簡介

enumerate()是python的內置函數

  • enumerate在字典上是枚舉、列舉的意思
  • 對於一個可迭代的(iterable)/可遍歷的對象(如列表、字符串),enumerate將其組成一個索引序列,利用它可以同時獲得索引和值
  • enumerate多用於在for循環中得到計數。

2.4enumerate()使用

如果對一個列表,既要遍歷索引又要遍曆元素時,首先可以這樣寫:

參考代碼
list = ["", "", "一個", "測試","數據"]

for i in range(len(list)):

    print(i,list[i])

上述方法有些累贅,利用enumerate()會更加直接和優美:

參考代碼
list1 = ["", "", "一個", "測試","數據"]

for index, item in enumerate(list1):

        print(index,item)

3.數據讀取方法封裝

  數據讀取方法也屬於公共方法,這裏我們首先實現一下,然後將其封裝到裡邊即可。

3.1數據讀取方法實現的參考代碼

import csv


     def get_csv_data(csv_file,line):

        with open(csv_file, 'r', encoding='utf-8-sig') as file:

            reader=csv.reader(file)

            for index, row in enumerate(reader,1):

                if index == line:

                    return row

 

    csv_file='../data/account.csv'

    data=get_csv_data(csv_file,3)

    print(data)

3.2封裝

將其封裝在公共方法中,在其他地方用到的時候,直接導入調用即可。

4.utf-8與utf-8-sig兩種編碼格式的區別

UTF-8以字節為編碼單元,它的字節順序在所有系統中都是一樣的,沒有字節序的問題,也因此它實際上並不需要BOM(“ByteOrder Mark”)。但是UTF-8 with BOM即utf-8-sig需要提供BOM。

5.config文件配置

各種配置文件都放在這個目錄下。

5.1日誌文件配置 

主要是一些日誌信息的配置。

log.config

 參考代碼
[loggers]
keys=root,infoLogger

[logger_root]
level=DEBUG
handlers=consoleHandler,fileHandler

[logger_infoLogger]
handlers=consoleHandler,fileHandler
qualname=infoLogger
propagate=0

[handlers]
keys=consoleHandler,fileHandler

[handler_consoleHandler]
class=StreamHandler
level=INFO
formatter=form02
args=(sys.stdout,)

[handler_fileHandler]
class=FileHandler
level=INFO
formatter=form01
args=('../logs/runlog.log', 'a')

[formatters]
keys=form01,form02

[formatter_form01]
format=%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s

[formatter_form02]
format=%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s

6.測試用例封裝

這裏宏哥舉例給小夥伴們演示:封裝註冊和登錄兩個測試用例。

6.1測試用例執行開始結束操作封裝

測試用例執行開始和結束的封裝,其他模塊用到直接導入,調用即可。

myunit.py

參考代碼
# coding=utf-8
# 1.先設置編碼,utf-8可支持中英文,如上,一般放在第一行

# 2.註釋:包括記錄創建時間,創建人,項目名稱。
'''
Created on 2019-11-20
@author: 北京-宏哥   QQ交流群:707699217
Project:Appium自動化測試框架綜合實踐 - 代碼實現
'''
# 3.導入模塊
import unittest
from kyb_testProject.common.desired_caps import appium_desired
import logging
from time import sleep

class StartEnd(unittest.TestCase):
    def setUp(self):
        logging.info('=====setUp====')
        self.driver=appium_desired()

    def tearDown(self):
        logging.info('====tearDown====')
        sleep(5)
        self.driver.close_app()

6.2註冊用例

開始註冊用例代碼邏輯的實現。

test_register.py

參考代碼
# coding=utf-8
# 1.先設置編碼,utf-8可支持中英文,如上,一般放在第一行

# 2.註釋:包括記錄創建時間,創建人,項目名稱。
'''
Created on 2019-11-20
@author: 北京-宏哥   QQ交流群:707699217
Project:Appium自動化測試框架綜合實踐 - 代碼實現
'''
# 3.導入模塊
from kyb_testProject.common.myunit import StartEnd
from kyb_testProject.businessView.registerView import RegisterView
import logging,random,unittest

class RegisterTest(StartEnd):
    def test_user_register(self):
        logging.info('======test_user_register======')
        r=RegisterView(self.driver)

        username = 'bjhg2019' + 'fly' + str(random.randint(1000, 9000))
        password = 'bjhg2020' + str(random.randint(1000, 9000))
        email = 'bjhg' + str(random.randint(1000, 9000)) + '@163.com'

        self.assertTrue(r.register_action(username,password,email))

if __name__ == '__main__':
    unittest.main()

6.3登錄用例

開始登錄用例代碼邏輯的實現。

test_login.py

參考代碼
# coding=utf-8
# 1.先設置編碼,utf-8可支持中英文,如上,一般放在第一行

# 2.註釋:包括記錄創建時間,創建人,項目名稱。
'''
Created on 2019-11-13
@author: 北京-宏哥   QQ交流群:707699217
Project:Appium自動化測試框架綜合實踐 - 代碼實現
'''
# 3.導入模塊
from kyb_testProject.common.myunit import StartEnd
from kyb_testProject.businessView.loginView import LoginView
import unittest
import logging

class TestLogin(StartEnd):
    csv_file='../data/account.csv'

    @unittest.skip('test_login_zxw2018')
    def test_login_zxw2018(self):
        logging.info('======test_login_zxw2018=====')
        l=LoginView(self.driver)
        data=l.get_csv_data(self.csv_file,2)

        l.login_action(data[0],data[1])
        self.assertTrue(l.check_loginStatus())

    # @unittest.skip('skip test_login_zxw2017')
    def test_login_zxw2017(self):
        logging.info('======test_login_zxw2017=====')
        l=LoginView(self.driver)
        data = l.get_csv_data(self.csv_file, 1)

        l.login_action(data[0], data[1])
        self.assertTrue(l.check_loginStatus())

    @unittest.skip('test_login_error')
    def test_login_error(self):
        logging.info('======test_login_error=====')
        l = LoginView(self.driver)
        data = l.get_csv_data(self.csv_file, 3)

        l.login_action(data[0], data[1])
        self.assertTrue(l.check_loginStatus(),msg='login fail!')

if __name__ == '__main__':
    unittest.main()

7.小結

到此,Appium自動化測試框架就差下一篇就全部完成了,聰明的你都懂了嗎???嘿嘿!慢慢地來吧。

下節預告

下一篇,講解執行測試用例,生成測試報告,以及自動化平台,請關注宏哥,敬請期待!!!

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

台灣海運大陸貨務運送流程

兩岸物流進出口一站式服務

Netty學習篇⑤–編、解碼源碼分析

前言

學習Netty也有一段時間了,Netty作為一個高性能的異步框架,很多RPC框架也運用到了Netty中的知識,在rpc框架中豐富的數據協議及編解碼可以讓使用者更加青睞;
Netty支持豐富的編解碼框架,其本身內部提供的編解碼也可以應對各種業務場景;
今天主要就是學習下Netty中提供的編、解碼類,之前只是簡單的使用了下Netty提供的解碼類,今天更加深入的研究下Netty中編、解碼的源碼及部分使用。

編、解碼的概念

  • 編碼(Encoder)

    編碼就是將我們發送的數據編碼成字節數組方便在網絡中進行傳輸,類似Java中的序列化,將對象序列化成字節傳輸
  • 解碼(Decoder)

    解碼和編碼相反,將傳輸過來的字節數組轉化為各種對象來進行展示等,類似Java中的反序列化
    如:
    // 將字節數組轉化為字符串
    new String(byte bytes[], Charset charset)

編、解碼超類

ByteToMessageDecoder: 解碼超類,將字節轉換成消息

解碼解碼一般用於將獲取到的消息解碼成系統可識別且自己需要的數據結構;因此ByteToMessageDecoder需要繼承ChannelInboundHandlerAdapter入站適配器來獲取到入站的數據,在handler使用之前通過channelRead獲取入站數據進行一波解碼;
ByteToMessageDecoder類圖

源碼分析

通過channelRead獲取入站數據,將數據緩存至cumulation數據緩衝區,最後在傳給decode進行解碼,在read完成之後清空緩存的數據

1. 獲取入站數據

/**
*  通過重寫channelRead方法來獲取入站數據
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
    // 檢測是否是byteBuf對象格式數據
    if (msg instanceof ByteBuf) {
        // 實例化字節解碼成功輸出集合 即List<Object> out
        CodecOutputList out = CodecOutputList.newInstance();
        try {
            // 獲取到的請求的數據
            ByteBuf data = (ByteBuf) msg;
            // 如果緩衝數據區為空則代表是首次觸發read方法
            first = cumulation == null;
            if (first) {
                // 如果是第一次read則當前msg數據為緩衝數據
                cumulation = data;
            } else {
                // 如果不是則觸發累加,將緩衝區的舊數據和新獲取到的數據通過        expandCumulation 方法累加在一起存入緩衝區cumulation
                // cumulator 累加類,將緩衝池中數據和新數據進行組合在一起
                // private Cumulator cumulator = MERGE_CUMULATOR;
                cumulation = cumulator.cumulate(ctx.alloc(), cumulation, data);
            }
            // 將緩衝區數據cumulation進行解碼
            callDecode(ctx, cumulation, out);
        } catch (DecoderException e) {
            throw e;
        } catch (Throwable t) {
            throw new DecoderException(t);
        } finally {
            // 在解碼完畢后釋放引用和清空全局字節緩衝區
            if (cumulation != null && !cumulation.isReadable()) {
                numReads = 0;
                cumulation.release();
                cumulation = null;
                // discardAfterReads為netty中設置的讀取多少次后開始丟棄字節 默認值16
                // 可通過setDiscardAfterReads(int n)來設置值不設置默認16次
            } else if (++ numReads >= discardAfterReads) {
                // We did enough reads already try to discard some bytes so we not risk to see a OOME.
                // 在我們讀取了足夠的數據可以嘗試丟棄一些字節已保證不出現內存溢出的異常
                // 
                // See https://github.com/netty/netty/issues/4275
                // 讀取次數重置為0
                numReads = 0;
                // 重置讀寫指針或丟棄部分已讀取的字節
                discardSomeReadBytes();
            }
            // out為解碼成功的傳遞給下一個handler
            int size = out.size();
            decodeWasNull = !out.insertSinceRecycled();
            // 結束當前read傳遞到下個ChannelHandler
            fireChannelRead(ctx, out, size);
            // 回收響應集合 將insertSinceRecycled設置為false;
            // insertSinceRecycled用於channelReadComplete判斷使用
            out.recycle();
        }
    } else {
        // 不是的話直接fire傳遞給下一個handler
        ctx.fireChannelRead(msg);
    }
}
2. 初始化字節緩衝區計算器: Cumulator主要用於全局字節緩衝區和新讀取的字節緩衝區組合在一起擴容
public static final Cumulator MERGE_CUMULATOR = new Cumulator() {
    
    /**
    * alloc ChannelHandlerContext分配的字節緩衝區
    * cumulation 當前ByteToMessageDecoder類全局的字節緩衝區
    * in 入站的字節緩衝區
    **/
    @Override
    public ByteBuf cumulate(ByteBufAllocator alloc, ByteBuf cumulation, ByteBuf in) {
        final ByteBuf buffer;
        // 如果全局ByteBuf寫入的字節+當前入站的字節數據大於全局緩衝區最大的容量或者全局緩衝區的引用數大於1個或全局緩衝區只讀
        if (cumulation.writerIndex() > cumulation.maxCapacity() - in.readableBytes()
            || cumulation.refCnt() > 1 || cumulation.isReadOnly()) {
            // Expand cumulation (by replace it) when either there is not more room in the buffer
            // or if the refCnt is greater then 1 which may happen when the user use slice().retain() or
            // duplicate().retain() or if its read-only.
            //
            // See:
            // - https://github.com/netty/netty/issues/2327
            // - https://github.com/netty/netty/issues/1764
            // 進行擴展全局字節緩衝區(容量大小 = 新數據追加到舊數據末尾組成新的全局字節緩衝區)
            buffer = expandCumulation(alloc, cumulation, in.readableBytes());
        } else {
            buffer = cumulation;
        }
        // 將新數據寫入緩衝區
        buffer.writeBytes(in);
        // 釋放當前的字節緩衝區的引用
        in.release();
        
        return buffer;
    }
};


/**
* alloc 字節緩衝區操作類
* cumulation 全局累加字節緩衝區
* readable 讀取到的字節數長度
*/
// 字節緩衝區擴容方法
static ByteBuf expandCumulation(ByteBufAllocator alloc, ByteBuf cumulation, int readable) {
    // 舊數據
    ByteBuf oldCumulation = cumulation;
    // 通過ByteBufAllocator將緩衝區擴大到oldCumulation + readable大小
    cumulation = alloc.buffer(oldCumulation.readableBytes() + readable);
    // 將舊數據重新寫入到新的字節緩衝區
    cumulation.writeBytes(oldCumulation);
    // 舊字節緩衝區引用-1
    oldCumulation.release();
    return cumulation;
}
3. ByteBuf釋放當前字節緩衝區的引用: 通過調用ReferenceCounted接口中的release方法來釋放
@Override
public boolean release() {
    return release0(1);
}

@Override
public boolean release(int decrement) {
    return release0(checkPositive(decrement, "decrement"));
}

/**
* decrement 減量
*/
private boolean release0(int decrement) {
    for (;;) {
        int refCnt = this.refCnt;
        // 當前引用小於減量
        if (refCnt < decrement) {
            throw new IllegalReferenceCountException(refCnt, -decrement);
        }
        // 這裏就利用里線程併發中的知識CAS,線程安全的設置refCnt的值
        if (refCntUpdater.compareAndSet(this, refCnt, refCnt - decrement)) {
            // 如果減量和引用量相等
            if (refCnt == decrement) {
                // 全部釋放
                deallocate();
                return true;
            }
            return false;
        }
    }
}

4. 將全局字節緩衝區進行解碼

/**
* ctx ChannelHandler的上下文,用於傳輸數據與下一個handler來交互
* in 入站數據
* out 解析之後的出站集合 (此出站不是返回給客戶端的而是傳遞給下個handler的)
*/
protected void callDecode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
    try {
        // 如果入站數據還有沒解析的
        while (in.isReadable()) {
            // 解析成功的出站集合長度
            int outSize = out.size();
            // 如果大於0則說明解析成功的數據還沒被消費完,直接fire掉給通道中的後續handler繼續                消費
            if (outSize > 0) {
                fireChannelRead(ctx, out, outSize);
                out.clear();

                // Check if this handler was removed before continuing with decoding.
                // 在這個handler刪除之前檢查是否還在繼續解碼
                // If it was removed, it is not safe to continue to operate on the buffer.
                // 如果移除了,它繼續操作緩衝區是不安全的
                //
                // See:
                // - https://github.com/netty/netty/issues/4635
                if (ctx.isRemoved()) {
                    break;
                }
                outSize = 0;
            }
            // 入站數據字節長度
            int oldInputLength = in.readableBytes();
            // 開始解碼數據
            decodeRemovalReentryProtection(ctx, in, out);

            // Check if this handler was removed before continuing the loop.
            // 
            // If it was removed, it is not safe to continue to operate on the buffer.
            //
            // See https://github.com/netty/netty/issues/1664
            if (ctx.isRemoved()) {
                break;
            }

            // 解析完畢跳出循環
            if (outSize == out.size()) {
                if (oldInputLength == in.readableBytes()) {
                    break;
                } else {
                    continue;
                }
            }

            if (oldInputLength == in.readableBytes()) {
                throw new DecoderException(
                    StringUtil.simpleClassName(getClass()) +
                    ".decode() did not read anything but decoded a message.");
            }

            if (isSingleDecode()) {
                break;
            }
        }
    } catch (DecoderException e) {
        throw e;
    } catch (Throwable cause) {
        throw new DecoderException(cause);
    }
}

final void decodeRemovalReentryProtection(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
        // 設置解碼狀態為正在解碼  STATE_INIT = 0; STATE_CALLING_CHILD_DECODE = 1;             STATE_HANDLER_REMOVED_PENDING = 2; 分別為初始化; 解碼; 解碼完畢移除
        decodeState = STATE_CALLING_CHILD_DECODE;
        try {
            // 具體的解碼邏輯(netty提供的解碼器或自定義解碼器中重寫的decode方法)
            decode(ctx, in, out);
        } finally {
            // 此時decodeState為正在解碼中 值為1,返回false
            boolean removePending = decodeState == STATE_HANDLER_REMOVED_PENDING;
            // 在設置為初始化等待解碼
            decodeState = STATE_INIT;
            // 解碼完成移除當前ChannelHandler標記為不處理
            // 可以看看handlerRemoved源碼。如果緩衝區還有數據直接傳遞給下一個handler
            if (removePending) {
                handlerRemoved(ctx);
            }
        }
    }
5. 執行channelReadComplete
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
    // 讀取次數重置
    numReads = 0;
    // 重置讀寫index
    discardSomeReadBytes();
    // 在channelRead meth中定義賦值 decodeWasNull = !out.insertSinceRecycled();
    // out指的是解碼集合List<Object> out; 咱們可以點進
    if (decodeWasNull) {
        decodeWasNull = false;
        if (!ctx.channel().config().isAutoRead()) {
            ctx.read();
        }
    }
    // fire掉readComplete傳遞到下一個handler的readComplete
    ctx.fireChannelReadComplete();
}

/**
*  然後我們可以搜索下insertSinceRecucled在什麼地方被賦值了
* Returns {@code true} if any elements where added or set. This will be reset once {@link #recycle()} was called.
*/
boolean insertSinceRecycled() {
    return insertSinceRecycled;
}


// 搜索下insert的調用我們可以看到是CodecOutputList類即為channelRead中的out集合,眾所周知在    decode完之後,解碼數據就會被調用add方法,此時insertSinceRecycled被設置為true
private void insert(int index, Object element) {
    array[index] = element;
    insertSinceRecycled = true;
}


/**
* 清空回收數組內部的所有元素和存儲空間
* Recycle the array which will clear it and null out all entries in the internal storage.
*/
// 搜索recycle的調用我么可以知道在channelRead的finally邏輯中 調用了out.recycle();此時        insertSinceRecycled被設置為false
void recycle() {
    for (int i = 0 ; i < size; i ++) {
        array[i] = null;
    }
    clear();
    insertSinceRecycled = false;
    handle.recycle(this);
}

至此ByteToMessageDecoder解碼類應該差不多比較清晰了!!!

MessageToByteEncoder: 編碼超類,將消息轉成字節進行編碼發出

何謂編碼,就是將發送數據轉化為客戶端和服務端約束好的數據結構和格式進行傳輸,我們可以在編碼過程中將消息體body的長度和一些頭部信息有序的設置到ByteBuf字節緩衝區中;方便解碼方靈活的運用來判斷(是否完整的包等)和處理業務;解碼是繼承入站數據,反之編碼應該繼承出站的數據;接下來我們看看編碼類是怎麼進行編碼的;
MessageToByteEncoder類圖如下

源碼分析

既然是繼承出站類,我們直接看看write方法是怎麼樣的

/**
* 通過write方法獲取到出站的數據即要發送出去的數據
* ctx channelHandler上下文
* msg 發送的數據 Object可以通過繼承類指定的泛型來指定
* promise channelPromise異步監聽,類似ChannelFuture,只不過promise可以設置監聽的結果,future只能通過獲取監聽的成功失敗結果;可以去了解下promise和future的區別
*/
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
    ByteBuf buf = null;
    try {
        // 檢測發送數據的類型 通過TypeParameterMatcher類型匹配器
        if (acceptOutboundMessage(msg)) {
            @SuppressWarnings("unchecked")
            I cast = (I) msg;
            // 分配字節緩衝區 preferDirect默認為true
            buf = allocateBuffer(ctx, cast, preferDirect);
            try {
                // 進行編碼
                encode(ctx, cast, buf);
            } finally {
                // 完成編碼后釋放對象的引用
                ReferenceCountUtil.release(cast);
            }
            // 如果緩衝區有數據則通過ctx發送出去,promise可以監聽數據傳輸並設置是否完成
            if (buf.isReadable()) {
                ctx.write(buf, promise);
            } else {
                // 如果沒有數據則釋放字節緩衝區的引用併發送一個empty的空包
                buf.release();
                ctx.write(Unpooled.EMPTY_BUFFER, promise);
            }
            buf = null;
        } else {
            // 非TypeParameterMatcher類型匹配器匹配的類型直接發送出去
            ctx.write(msg, promise);
        }
    } catch (EncoderException e) {
        throw e;
    } catch (Throwable e) {
        throw new EncoderException(e);
    } finally {
        if (buf != null) {
            buf.release();
        }
    }
}

// 初始化設置preferDirect為true
protected MessageToByteEncoder() {
    this(true);
}
protected MessageToByteEncoder(boolean preferDirect) {
    matcher = TypeParameterMatcher.find(this, MessageToByteEncoder.class, "I");
    this.preferDirect = preferDirect;
}

編碼: 重寫encode方法,根據實際業務來進行數據編碼

// 此處就是我們需要重寫的編碼方法了,我們和根據約束好的或者自己定義好想要的數據格式發送給對方

// 下面是我自己寫的demo的編碼方法;頭部設置好body的長度,服務端可以根據長度來判斷是否是完整的包,僅僅自學寫的簡單的demo非正常線上運營項目的邏輯
public class MyClientEncode extends MessageToByteEncoder<String> {

    @Override
    protected void encode(ChannelHandlerContext ctx, String msg, ByteBuf out) throws Exception {
        if (null != msg) {
            byte[] request = msg.getBytes(Charset.forName("UTF-8"));
            out.writeInt(request.length);
            out.writeBytes(request);
        }
    }
}

編碼類相對要簡單很多,因為只需要將發送的數據序列化,按照一定的格式進行發送數據!!!

項目實戰

項目主要簡單的實現下自定義編解碼器的運用及LengthFieldBasedFrameDecoder的使用

  • 項目結構如下
    │  hetangyuese-netty-06.iml
    │  pom.xml
    │
    ├─src
    │  ├─main
    │  │  ├─java
    │  │  │  └─com
    │  │  │      └─hetangyuese
    │  │  │          └─netty
    │  │  │              ├─client
    │  │  │              │      MyClient06.java
    │  │  │              │      MyClientChannelInitializer.java
    │  │  │              │      MyClientDecoder.java
    │  │  │              │      MyClientEncode.java
    │  │  │              │      MyClientHandler.java
    │  │  │              │      MyMessage.java
    │  │  │              │
    │  │  │              └─server
    │  │  │                      MyChannelInitializer.java
    │  │  │                      MyServer06.java
    │  │  │                      MyServerDecoder.java
    │  │  │                      MyServerDecoderLength.java
    │  │  │                      MyServerEncoder.java
    │  │  │                      MyServerHandler.java
    │  │  │
    │  │  └─resources
    │  └─test
    │      └─java
    
  • 服務端

    Serverhandler: 只是簡單的將解碼的內容輸出

    public class MyServerHandler extends ChannelInboundHandlerAdapter {
    
        @Override
        public void channelActive(ChannelHandlerContext ctx) throws Exception {
            System.out.println("客戶端連接成功 time: " + new Date().toLocaleString());
        }
    
        @Override
        public void channelInactive(ChannelHandlerContext ctx) throws Exception {
            System.out.println("客戶端斷開連接 time: " + new Date().toLocaleString());
        }
    
        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
            String body = (String) msg;
            System.out.println("content:" + body);
        }
    
        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
            // 出現異常關閉通道
            cause.printStackTrace();
            ctx.close();
        }
    }

    解碼器

    public class MyServerDecoder extends ByteToMessageDecoder {
    
        // 此處我頭部只塞了長度字段佔4個字節,別問為啥我知道,這是要客戶端和服務端約束好的
        private static int min_head_length = 4;
    
        @Override
        protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
            // 解碼的字節長度
            int size = in.readableBytes();
            if(size < min_head_length) {
                System.out.println("解析的數據長度小於頭部長度字段的長度");
                return ;
            }
            // 讀取的時候指針已經移位到長度字段的尾端
            int length = in.readInt();
            if (size < length) {
                System.out.println("解析的數據長度與長度不符合");
                return ;
            }
    
            // 上面已經讀取到了長度字段,後面的長度就是body
            ByteBuf decoderArr = in.readBytes(length);
            byte[] request = new byte[decoderArr.readableBytes()];
            // 將數據寫入空數組
            decoderArr.readBytes(request);
            String body = new String(request, Charset.forName("UTF-8"));
            out.add(body);
        }
    }

    將解碼器加入到channelHandler中:記得加到業務handler的前面否則無效

    public class MyChannelInitializer extends ChannelInitializer<SocketChannel> {
    
        @Override
        protected void initChannel(SocketChannel ch) throws Exception {
            ch.pipeline()
    //                .addLast(new MyServerDecoderLength(10240, 0, 4, 0, 0))
    //                .addLast(new LengthFieldBasedFrameDecoder(10240, 0, 4, 0, 0))
                    .addLast(new MyServerDecoder())
                    .addLast(new MyServerHandler())
            ;
        }
    }
  • 客戶端

    ClientHandler

    public class MyClientHandler extends ChannelInboundHandlerAdapter {
    
        @Override
        public void channelActive(ChannelHandlerContext ctx) throws Exception {
            System.out.println("與服務端連接成功");
            for (int i = 0; i<10; i++) {
                ctx.writeAndFlush("hhhhh" + i);
            }
        }
    
        @Override
        public void channelInactive(ChannelHandlerContext ctx) throws Exception {
            System.out.println("與服務端斷開連接");
        }
    
        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
            System.out.println("收到服務端消息:" +msg+ " time: " + new Date().toLocaleString());
        }
    
        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
            cause.printStackTrace();
            ctx.close();
        }
    }

    編碼器

    public class MyClientEncode extends MessageToByteEncoder<String> {
    
        @Override
        protected void encode(ChannelHandlerContext ctx, String msg, ByteBuf out) throws Exception {
            if (null != msg) {
                byte[] request = msg.getBytes(Charset.forName("UTF-8"));
                out.writeInt(request.length);
                out.writeBytes(request);
            }
        }
    }

    將編碼器加到ClientHandler的前面

    public class MyClientChannelInitializer extends ChannelInitializer<SocketChannel> {
    
        @Override
        protected void initChannel(SocketChannel ch) throws Exception {
            ch.pipeline()
                    .addLast(new MyClientDecoder())
                    .addLast(new MyClientEncode())
                    .addLast(new MyClientHandler())
            ;
    
        }
    }
  • 服務端運行結果
    MyServer06 is start ...................
    客戶端連接成功 time: 2019-11-19 16:35:47
    content:hhhhh0
    content:hhhhh1
    content:hhhhh2
    content:hhhhh3
    content:hhhhh4
    content:hhhhh5
    content:hhhhh6
    content:hhhhh7
    content:hhhhh8
    content:hhhhh9
  • 如果不用自定義的解碼器怎麼獲取到body內容呢

    將自定義編碼器換成LengthFieldBasedFrameDecoder(10240, 0, 4, 0, 0)

    public class MyChannelInitializer extends ChannelInitializer<SocketChannel> {
    
        @Override
        protected void initChannel(SocketChannel ch) throws Exception {
            ch.pipeline()
    //                .addLast(new MyServerDecoderLength(10240, 0, 4, 0, 0))
                    .addLast(new LengthFieldBasedFrameDecoder(10240, 0, 4, 0, 0))
    //                .addLast(new MyServerDecoder())
                    .addLast(new MyServerHandler())
            ;
        }
    }
    
    // 怕忘記的各個參數的含義在這在說明一次,自己不斷的修改每個值觀察結果就可以更加深刻的理解
    /**
    * maxFrameLength:消息體的最大長度,好像默認最大值為1024*1024
    * lengthFieldOffset 長度字段所在字節數組的下標 (我這是第一個write的所以下標是0)
    * lengthFieldLength 長度字段的字節長度(int類型佔4個字節)
    * lengthAdjustment 長度字段補償的數值 (lengthAdjustment =  數據包長度 - lengthFieldOffset - lengthFieldLength - 長度域的值),解析需要減去對應的數值
    * initialBytesToStrip 是否去掉長度字段(0不去除,對應長度域字節長度)
    */
    public LengthFieldBasedFrameDecoder(
                int maxFrameLength,
                int lengthFieldOffset, int lengthFieldLength,
                int lengthAdjustment, int initialBytesToStrip)
    結果: 前都帶上了長度
    MyServer06 is start ...................
    客戶端連接成功 time: 2019-11-19 17:53:42
    收到客戶端發來的消息:   hhhhh0, time: 2019-11-19 17:53:42
    收到客戶端發來的消息:   hhhhh1, time: 2019-11-19 17:53:42
    收到客戶端發來的消息:   hhhhh2, time: 2019-11-19 17:53:42
    收到客戶端發來的消息:   hhhhh3, time: 2019-11-19 17:53:42
    收到客戶端發來的消息:   hhhhh4, time: 2019-11-19 17:53:42
    收到客戶端發來的消息:   hhhhh5, time: 2019-11-19 17:53:42
    收到客戶端發來的消息:   hhhhh6, time: 2019-11-19 17:53:42
    收到客戶端發來的消息:   hhhhh7, time: 2019-11-19 17:53:42
    收到客戶端發來的消息:   hhhhh8, time: 2019-11-19 17:53:42
    收到客戶端發來的消息:   hhhhh9, time: 2019-11-19 17:53:42

    如果我們在客戶端的長度域中做手腳 LengthFieldBasedFrameDecoder(10240, 0, 4, 0, 0)

    舊: out.writeInt(request.length);
    新: out.writeInt(request.length + 1);
    // 看結果就不正常,0後面多了一個0;但是不知道為啥只解碼了一次??? 求解答
    MyServer06 is start ...................
    客戶端連接成功 time: 2019-11-19 17:56:55
    收到客戶端發來的消息:   hhhhh0 , time: 2019-11-19 17:56:55
    
    // 正確修改為 LengthFieldBasedFrameDecoder(10240, 0, 4, -1, 0)
    // 結果:
    MyServer06 is start ...................
    客戶端連接成功 time: 2019-11-19 18:02:18
    收到客戶端發來的消息:   hhhhh0, time: 2019-11-19 18:02:18
    收到客戶端發來的消息:   hhhhh1, time: 2019-11-19 18:02:18
    收到客戶端發來的消息:   hhhhh2, time: 2019-11-19 18:02:18
    收到客戶端發來的消息:   hhhhh3, time: 2019-11-19 18:02:18
    收到客戶端發來的消息:   hhhhh4, time: 2019-11-19 18:02:18
    收到客戶端發來的消息:   hhhhh5, time: 2019-11-19 18:02:18
    收到客戶端發來的消息:   hhhhh6, time: 2019-11-19 18:02:18
    收到客戶端發來的消息:   hhhhh7, time: 2019-11-19 18:02:18
    收到客戶端發來的消息:   hhhhh8, time: 2019-11-19 18:02:18
    收到客戶端發來的消息:   hhhhh9, time: 2019-11-19 18:02:18

    捨棄長度域 :LengthFieldBasedFrameDecoder(10240, 0, 4, 0, 4)

    // 結果
    MyServer06 is start ...................
    客戶端連接成功 time: 2019-11-19 18:03:44
    收到客戶端發來的消息:hhhhh0, time: 2019-11-19 18:03:44
    收到客戶端發來的消息:hhhhh1, time: 2019-11-19 18:03:44
    收到客戶端發來的消息:hhhhh2, time: 2019-11-19 18:03:44
    收到客戶端發來的消息:hhhhh3, time: 2019-11-19 18:03:44
    收到客戶端發來的消息:hhhhh4, time: 2019-11-19 18:03:44
    收到客戶端發來的消息:hhhhh5, time: 2019-11-19 18:03:44
    收到客戶端發來的消息:hhhhh6, time: 2019-11-19 18:03:44
    收到客戶端發來的消息:hhhhh7, time: 2019-11-19 18:03:44
    收到客戶端發來的消息:hhhhh8, time: 2019-11-19 18:03:44
    收到客戶端發來的消息:hhhhh9, time: 2019-11-19 18:03:44
    分析源碼示例中的 lengthAdjustment = 消息字節長度 – lengthFieldOffset-lengthFieldLength-長度域中的值
  • 源碼中的示例
     * <pre>
     * lengthFieldOffset   =  0
     * lengthFieldLength   =  2
     * <b>lengthAdjustment</b>    = <b>-2</b> (= the length of the Length field)
     * initialBytesToStrip =  0
     *
     * BEFORE DECODE (14 bytes)         AFTER DECODE (14 bytes)
     * +--------+----------------+      +--------+----------------+
     * | Length | Actual Content |----->| Length | Actual Content |
     * | 0x000E | "HELLO, WORLD" |      | 0x000E | "HELLO, WORLD" |
     * +--------+----------------+      +--------+----------------+
     * </pre>
    長度域中0x000E為16進制,轉換成10進制是14,說明消息體長度為14;根據公式:14-0-2-14 = -2
    * <pre>
     * lengthFieldOffset   = 0
     * lengthFieldLength   = 3
     * <b>lengthAdjustment</b>    = <b>2</b> (= the length of Header 1)
     * initialBytesToStrip = 0
     *
     * BEFORE DECODE (17 bytes)                      AFTER DECODE (17 bytes)
     * +----------+----------+----------------+      +----------+----------+----------------+
     * |  Length  | Header 1 | Actual Content |----->|  Length  | Header 1 | Actual Content |
     * | 0x00000C |  0xCAFE  | "HELLO, WORLD" |      | 0x00000C |  0xCAFE  | "HELLO, WORLD" |
     * +----------+----------+----------------+      +----------+----------+----------------+
     * </pre>
    從上的例子可以知道;lengthAdjustment(2) = 17- 12(00000C)-lengthFieldOffset(0) - lengthFieldLength(3);

    …….等等

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

※自行創業 缺乏曝光? 下一步”網站設計“幫您第一時間規劃公司的門面形象

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

※試算大陸海運運費!

Graphviz 畫圖的一些總結

Graphviz 是一個自動排版的作圖軟件,可以生成 png pdf 等格式。

一切以官方文檔為準,博客只是參考。這裏做一個自己學習的記錄。

dot 語言

Graphviz 構建組件為 圖,節點,邊,用屬性對其進行描述。

以下是定義DOT語言的抽象語法,約束的規則如下:

  • 元素的終止以 粗體 显示
  • 文字字符用單引號 引起來
  • 圓括號 () 的內容為必選項
  • 方括號 [] 為可選項目
  • 豎杠 | 為擇一選擇
聲明 結構
graph [ strict ] (graph | digraph) [ ID ] ‘{‘ stmt_list ‘}’
stmt_list [ stmt [ ‘;‘ ] stmt_list ]
stmt node_stmt | edge_stmt | attr_stmt | ID ‘=‘ ID | subgraph
attr_stmt (graph | node | edge) attr_list
attr_list ‘[‘** [ a_list ] **’]’ [ attr_list ]
a_list ID ‘=’ ID [ (‘;’ | ‘,’) ] [ a_list ]
edge_stmt (node_id | subgraph) edgeRHS [ attr_list ]
edgeRHS edgeop (node_id | subgraph) [ edgeRHS ]
node_stmt node_id [ attr_list ]
node_id ID [ port ]
port :‘ ID [ ‘:‘ compass_pt ] | ‘:‘ compass_pt
subgraph [ subgraph [ ID ] ] ‘{‘ stmt_list ‘}
compass_pt (n | ne | e | se | s | sw | w | nw | c | _)

ID 其實就是一個字符串,為該組件的名稱或者屬性的名稱,命名規則如下:

  1. 所有的字母 [a-zA-Z\200-\377] 下劃線,数字 [0-9],数字不能出現在起始位置
  2. 純数字 $[-]^?(.[0-9]^+ | [0-9]^+(.[0-9]*)6? $
  3. 所有用雙引號引用的字符串 "..."
  4. HTML 格式的字符串 <>

dot 語法的關鍵字

  • strict, 嚴格的圖限定,禁止創建多個相同的邊
  • graph, 無向圖. 在圖的創建時必須聲明為有向圖還是無向圖
  • digraph, 有向圖
  • node, 節點
  • edge, 邊
  • subgraph, 子圖

通過 dot 的抽象語法可以看到

  1. 整個 graph 必須使用 graph 或 digraph {} 進行限定說明圖的屬性
  2. 圖裡面的聲明列表可以為空,也可以為多個,每個聲明后的 ; 為可選項
  3. 聲明有幾種類型
    1. 節點 node
    2. edge
    3. 子圖 subgraph
    4. 屬性列表
    5. ID = ID, 這個類型暫時還沒有看到有什麼作用
  4. 屬性列表
    1. 必須使用中括號 [ ] 將列表項括起來
    2. 列表項為可選
  5. 屬性列表項
    1. 以 key = value 的形式存在,列表項可選擇 ‘,‘ 和 ‘;‘ 結尾
    2. 可存在多個列表項
  6. 邊的聲明
    1. 首端為 節點標識符或者子圖,
    2. 右部分由邊連接節點標識符或者子圖構成,右部分可以存在多個
    3. 尾部可選屬性列表
  7. 節點的聲明
    示例 節點的用法 node0 [label = "<postid1> string|<postid2> string|<postid3> string3", height=.5]` node0:head[color=lightblue] // 設置該部分的顏色
    1. 首部為節點標識符 節點部分(post) 方向 組成,其中后兩項為可選項。
    2. 後半部分為可選的屬性列表
方向 說明
n north 北
ne north east
e east 東
se south east 東南
s south 南
sw south west 西南
w west 西
nw north west 西北
c center 中部
_ 任意

一個方向的示例

digraph action {
    node [shape = record,height=.1];

    node0 [label = "<head> head|<body> body|<foot> foot", height=.5]
    node2 [shape = box label="mind"]

    node0:head:n -> node2:n [label = "n"]
    node0:head:ne -> node2:ne [label = "ne"]
    node0:head:e -> node2:e [label = "e"]
    node0:head:se -> node2:se [label = "se"]
    node0:head:s -> node2:s [label = "s"]
    node0:head:sw -> node2:sw [label = "sw"]
    node0:head:w -> node2:w [label = "w"]
    node0:head:nw -> node2:nw [label = "nw"]
    node0:head:c -> node2:c [label = "c"]
    node0:head:_ -> node2:_ [label = "_"]

    node0:body[style=filled color=lightblue]
}

效果如下 圖-1

繪製屬性

一個圖中有非常多的 node 和 edge,如果每次都需要聲明一個節點的屬性會非常麻煩,有一個簡單的方式為聲明一個公共的屬性如

digraph action {
    rankdir = LR // 設置方向
    node [shape=box color=blue]
    edge [color=red]

    node1 // 默認節點屬性
    node2 [color=lightblue] // 屬於該節點的顏色屬性
    node1 -> node2 // 默認邊屬性 
    node2 -> node1 [color=green] // 屬於該變的屬性
}

在聲明位置之後的節點都有一個 默認 的形狀和顏色屬性。

全部的屬性見,這裏列舉部分常用的屬性

  • charset 編碼,一般設置 UTF-8
  • fontname 字體名稱,這個在中文的情況需要設置,否則導出圖片的時候會亂碼,一般設置微軟雅黑(“Microsoft YaHei”), linux 下也是同樣設置系統帶的字體就好,其他字體設置見 屬性
  • fontcolor 字體顏色
  • fontsize 字體大小,用於文本內容
  • fillcolor 用於填充節點或者集群(cluster)的背景顏色。
  • size 圖形的最大寬度和高度
  • label 圖形上的文本標記
  • margin 設置圖形的邊距
  • pad 指定將繪製區域擴展到繪製圖形所需的最小區域的長度(以英寸為單位)
  • style 設置圖形組件的樣式信息。 對於聚類子圖或者節點,如果style = “filled”,則填充聚類框的背景
  • rankdir 設置圖形布局的排列方向 (全局只有一個生效). “TB”, “LR”, “BT”, “RL”, 分別對應於從上到下,從左到右,從下到上和從右到左繪製的有向圖。
  • ranksep 以英寸為單位提供所需的排列間隔
  • ratio 設置生成圖片的縱橫比

節點(node)

節點的默認屬性為 shape = ellipse, width = .75, height = 0.5 並且用節點標識符作為節點的显示文字。

如圖一中所示,聲明兩個節點 node0 和 node2,node0 或 node2 就表示這個節點的節點標識符,後面緊跟的是該節點的屬性列表;另一種用法為 節點標識符:節點部分:方向[屬性列表] node0:body[style=filled color=lightblue], 這個為單一節點聲明的方式。

節點中最基本的屬性為:

  • shape 形狀,全部形狀見,一些常用的圖形有
  • width height, 圖形的寬度和高度,如果設置了 fixedsize 為 true,則寬和高為最終的長度
  • fixedsize, 如果為false,節點的大小由其文本內容所需要的最小值決定
  • rank 子圖中節點上的排列等級約束. 最小等級是最頂部或最左側,最大等級是最底部或最右側。
    • same. 所有節點都位於同一等級
    • min. 所有節點都位於最小等級上
    • source. 所有節點都位於最小等級上,並且最小等級上的唯一節點屬於某個等級 source 或 min 的子圖.
    • max sink. 和上類似

邊 (edge)

有向圖中的的邊用 -> 表示,無向圖用 -- 表示。

可以同時連接多個節點或者子圖,但是只能有一個屬性列表,如下

digraph {
    rankdir = LR
    A -> B -> c[color=green]
}

一些關於邊的屬性如下:

digraph {
    rankdir = LR
    splines = ortho

    A -> B -> C -> D -> F [color = green]
    E -> F -> B -> D [color = blue]
    B -> E -> H[color = red]
}
  • len 首選邊的長度
  • weight 邊的權重, 權重越大越接近邊的長度
  • lhead 邏輯邊緣的頭部(箭頭那個位置),compound 設置為 true 時,邊被裁減到子圖的邊界處
  • ltail 類似 lhead
  • headlabel 邊上靠近箭頭部分的標籤
  • taillabel 邊上靠近尾部部分的標籤
    設置 A->B->C->D->F的權重最大,修改綠色的分支的權重為 100,使其變成主要邏輯分支。

  • splines 控制如何以及是否表示邊緣。其值如下
    • none 或者 “”, 無邊
    • true 或者 spline, 樣條線(無規則,可為直或者曲線)
    • false 或者 line, 直線段
    • polyline, 折線
    • curved, 曲弧線,兩條?
    • ortho, 正直的線(橫豎)
  • dir 設置繪製箭頭的邊緣類型

子圖

subgraph 必須配合 cluster 一起使用,用法為 subgraph cluster* {}

需要設置 compound 為 true,則在群集之間留出邊緣,子圖的邊界關係在 邊 的定義中有給出,這裏直接給個示例。

digraph G {
    compound = true  // 允許子圖間存在邊
    ranksep = 1
    node [shape = record]

    subgraph cluster_hardware {
        label = "hardware"
        color = lightblue
        CPU Memory
    }

    subgraph cluster_kernel {
        label = "kernel"
        color = green
        Init IPC
    }

    subgraph cluster_libc {
        label = "libc"
        color = yellow
        glibc
    }

    CPU -> Init [lhead = cluster_kernel ltail = cluster_hardware]
    IPC -> glibc [lhead = cluster_libc ltail = cluster_kernel]
}

示例

TCP IP 狀態流程圖

展示了兩個版本,怎麼把這些圖形節點稍微規範的显示出來

digraph {
    compound=true
    fontsize=10
    margin="0,0"
    ranksep = .75
    nodesep = .65

    node [shape=Mrecord fontname="Inconsolata, Consolas", fontsize=12, penwidth=0.5]
    edge [fontname="Inconsolata, Consolas", fontsize=10, arrowhead=normal]


    "TCP/IP State Transition" [shape = "plaintext", fontsize = 16]

    // now start server state transition
    "CLOSED" -> "LISTEN" [style = blod, label = "應用:被動打開\n發送:<無>"];
    "LISTEN" -> "SENT_REVD" [style = blod, label = "接收:SYN\n發送:SYN,ACK"]
    "SENT_REVD" -> "ESTABLISHED" [style = blod, label = "接收:ACK\n發送:<無>", weight = 20]
    "ESTABLISHED" -> "CLOSE_WAIT" [style = blod, label = "接收:FIN\n發送:ACK", weight = 20]

    subgraph cluster_passive_close {    
        style = dotted
        margin = 10

        passive_close [shape = plaintext, label = "被動關閉", fontsize = 14]

        "CLOSE_WAIT" -> "LAST_ACK" [style = blod, label = "應用:關閉\n發送:FIN", weight = 10]
    }
    "LAST_ACK" -> "CLOSED" [style = blod, label = "接收:ACK\n發送:<無>"]

    // now start client state transition
    "CLOSED" -> "SYN_SENT" [style = dashed, label = "應用:主動打開\n發送:SYN"]; 
    "SYN_SENT" -> "ESTABLISHED" [style = dashed, label = "接收:SYN,ACK\n發送:ACK", weight = 25]
    "SYN_SENT" -> "SENT_REVD" [style = dotted, label = "接收:SYN\n發送:SYN,ACK\n同時打開"]
    "ESTABLISHED" -> "FIN_WAIT_1" [style = dashed, label = "應用:關閉\n發送:FIN", weight = 20]
    
    subgraph cluster_active_close {
        style = dotted
        margin = 10
        
        active_open [shape = plaintext, label = "主動關閉", fontsize = 14]

        "FIN_WAIT_1" -> "FIN_WAIT_2" [style = dashed, label = "接收:ACK\n發送:<無>"]
        "FIN_WAIT_2" -> "TIME_WAIT" [style = dashed, label = "接收:FIN\n發送:ACK"]
        "FIN_WAIT_1" -> "CLOSING" [style = dotted, label = "接收:ACK\n發送:<無>"]
        "FIN_WAIT_1" -> "TIME_WAIT" [style = dotted, label = "接收:SYN,ACK\n發送:ACK"]
        "CLOSING" -> "TIME_WAIT" [style = dotted]
    }
    
    "TIME_WAIT" -> "CLOSED" [style = dashed, label = "2MSL超時"]
}

這是一個很挫的版本,排版亂飛了。

digraph rankdot {
    compound=true
    margin="0,0"
    ranksep = .75
    nodesep = 1
    pad = .5
    //splines = ortho

    node [shape=Mrecord, charset = "UTF-8" fontname="Microsoft YaHei", fontsize=14]
    edge [charset = "UTF-8" fontname="Microsoft YaHei", fontsize=11, arrowhead = normal]


    CLOSED -> LISTEN [style = dashed, label = "應用:被動打開\n發送:<無>", weight = 100];
    
    "TCP/IP State Transition" [shape = "plaintext", fontsize = 16]

    {
        rank = same
        SYN_RCVD SYN_SENT
        point_1 [shape = point, width = 0]
        
        SYN_SENT -> point_1 [style = dotted, label = "應用關閉或者超時"]
        // SYN_SENT -> SYN_RCVD 這個一行代碼和上一行衝突了,syn_sent 會在syn_rcvd右邊
        SYN_RCVD -> SYN_SENT [style = dotted, dir = back, headlabel = "接收:SYN\n發送:SYN,ACK\n同時打開"]
    }

    LISTEN -> SYN_RCVD [style = dashed, headlabel = "接收:SYN\n發送:SYN,ACK"]
    SYN_RCVD -> LISTEN [style = dotted, headlabel = "接收:RST"]
    CLOSED:es -> SYN_SENT [style = blod, label = "應用:主動打開\n發送:SYN"]

    {
        rank = same
        ESTABLISHED CLOSE_WAIT

        ESTABLISHED -> CLOSE_WAIT [style = dashed, label = "接收:SYN,ACK\n發送:ACK"]
    }

    SYN_RCVD -> ESTABLISHED [style = dashed, label = "接收:ACK\n發送:<無>", weight = 9]
    SYN_SENT -> ESTABLISHED  [style = blod, label = "接收:SYN,ACK\n發送:ACK", weight = 10]

    {
        rank = same

        FIN_WAIT_1
        CLOSING 
        LAST_ACK
        point_2 [shape = point, width = 0]

        FIN_WAIT_1 -> CLOSING [style = dotted, label = "接收:FIN\n發送:ACK"]
        LAST_ACK -> point_2 [style = dashed, label = "接收:ACK\n發送:<無>"]
    }

    CLOSE_WAIT -> LAST_ACK [style = dashed, label = "應用:關閉\n發送:FIN", weight = 10]

    {
        rank = same
        FIN_WAIT_2  TIME_WAIT

        point_3 [shape = point, width = 0]
        TIME_WAIT -> point_3 [style = blod, label = "2MSL超時"]
    }

    ESTABLISHED -> FIN_WAIT_1 [style = blod, label = "應用:關閉\n發送:FIN"]
    FIN_WAIT_1 -> FIN_WAIT_2 [style = blod, headlabel = "接收:ACK\n發送:<無>", weight = 15]
    FIN_WAIT_2 -> TIME_WAIT [style = blod, label = "接收:FIN\n發送:ACK", weight = 10]

    CLOSING -> TIME_WAIT [style = dotted, label = "接收:ACK\n發送:<無>", weight = 15]
    FIN_WAIT_1 -> TIME_WAIT [style = dotted, label = "接收:ACK\n發送:<無>"]

    point_3 -> point_2 [arrowhead = none, style = dotted, weight = 10]
    point_2 -> point_1 [arrowhead = none, style = dotted]
    point_1 -> CLOSED [style = dotted]
}

這個版本看起來有內味了,最最最的主要的原因就是我使用 rank = same 屬性,將一些圖形固定在 同一列,一些需要橫豎的直線的地方使用 weight 來調整權重,達到橫豎的直接的效果,很多地方都是微調的結果。有一個很差的地方是 使用了rank限制若干圖形后,就不能使用 subgraph 屬性了,這樣就不能在若干不同部分的節點周邊畫線(對比關閉的區域)了。

epoll 相關數據結構及關係

digraph rankdot {
    compound=true
    margin="0,0"
    ranksep = .75
    nodesep = 1
    pad = .5
    rankdir = LR

    node [shape=record, charset = "UTF-8" fontname="Microsoft YaHei", fontsize=14]
    edge [style = dashed, charset = "UTF-8" fontname="Microsoft YaHei", fontsize=11]

    epoll [shape = plaintext, label = "epoll 相關結構及部分關係"]

    eventpoll [
        color = cornflowerblue,
        label = "<eventpoll> struct \n eventpoll |
            <lock> spinlock_t lock; |
            <mutex> struct mutex mtx; |
            <wq> wait_queue_head_t wq; |
            <poll_wait> wait_queue_head_t poll_wait; |
            <rdllist> struct list_head rdllist; |
            <ovflist> struct epitem *ovflist; |
            <rbr> struct rb_root_cached rbr; |
            <ws> struct wakeup_source *ws; |
            <user> struct user_struct *user; |
            <file> struct file *file; |
            <visited> int visited; |
            <visited_list_link> struct list_head visited_list_link;"
    ]

    epitem [
        color = sienna,
        label = "<epitem> struct \n epitem  |
            <rb>struct rb_node rbn;\nstruct rcu_head rcu; |
            <rdllink> struct list_head rdllink; |
            <next> struct epitem *next; |
            <ffd> struct epoll_filefd ffd; |
            <nwait> int nwait; |
            <pwqlist> struct list_head pwqlist; |
            <ep> struct eventpoll *ep; |
            <fllink> struct list_head fllink; |
            <ws> struct wakeup_source __rcu *ws; |
            <event> struct epoll_event event;"
    ]

    epitem2 [
        color = sienna,
        label = "<epitem> struct \n epitem |
            <rb>struct rb_node rbn;\nstruct rcu_head rcu; |
            <rdllink> struct list_head rdllink; |
            <next> struct epitem *next; |
            <ep> struct eventpoll *ep; |
             ··· |
             ··· "
    ]

    eppoll_entry [
        color = darkviolet,
        label = "<entry> struct \n eppoll_entry |
            <llink> struct list_head llink; |
            <base> struct epitem *base; |
            <wait> wait_queue_entry_t wait; |
            <whead> wait_queue_head_t *whead;"
    ]

    epitem:ep -> eventpoll:se [color = sienna]
    epitem2:ep -> eventpoll:se [color = sienna]
    eventpoll:ovflist -> epitem:next -> epitem2:next [color = cornflowerblue]
    eventpoll:rdllist -> epitem:rdllink -> epitem2:rdllink [dir = both]
    eppoll_entry:llink -> epitem:pwqlist [color = darkviolet]
    eppoll_entry:base -> epitem:nw  [color = darkviolet]
}

遺留問題

  1. 在以上TCP/IP 狀態變遷圖中,嘗試增加主動關閉方的區域邊框
  2. 嘗試增加 TCP/IP 的時序圖

使用 VSCode 進行預覽生成

  1. 在官網下載graphviz安裝包
  2. 安裝 vscode 插件 Graphviz Preview
  3. 在 settings.json 中添加 "graphvizPreview.dotPath": "graphviz_path\graphviz-2.38\\release\\bin\\dot.exe", graphviz_path 為所在路徑,這些修改一下既可
  4. 新建一個 dot 文件,右上角就會有預覽生成的按鈕了

參考

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

台灣海運大陸貨務運送流程

兩岸物流進出口一站式服務

美國蒙大拿州密蘇拉(Missoula)聯邦地區法官克里斯坦森(Dana Christensen)與環保人士及美國原住民站在同一陣線

美國蒙大拿州密蘇拉(Missoula)聯邦地區法官克里斯坦森(Dana Christensen)與環保人士及美國原住民站在同一陣線,駁回美國魚類暨野生動物管理局(US Fish and Wildlife Service)將灰熊從瀕危物種名單除名的決定。

環保人士主張,根據瀕臨滅絕物種保護法,對這些灰熊與蒙大拿州和下48州(Lower 48)的其他灰熊族群採取差別待遇,是生物學上靠不住且非法行為,法官也同意這類說法。

環保人士說,儘管灰熊數量有所回升,倘若沒有受到聯邦持續保護,牠們的復育情況就會受到影響。此外,氣候變遷導致灰熊食物供給出現變化和人為死亡率高,也對灰熊生存構成威脅。

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

※自行創業 缺乏曝光? 下一步”網站設計“幫您第一時間規劃公司的門面形象

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

※試算大陸海運運費!

北極白鯨驚奇現身泰晤士河 再不回家專家憂安危

摘錄自2018年9月26日中央社報導

據英國各大媒體報導,一隻迷途的白鯨現身英國泰晤士河,第一次被目擊是25日在肯特郡(Kent)格雷夫森德(Gravesend)的泰晤士河段,當時白鯨正在駁船附近覓食,被暱稱為「班尼」(Benny),距離其原棲息地北極圈水域達數千公里。26日白鯨再次現身同個地點,引發是否迷航及可能遇險的擔憂。

鯨豚保育協會(WDC)海洋哺乳類動物科學家羅特(Rob Lott)表示,這隻白鯨正受到監控,以防牠擱淺。「但白鯨停留在泰晤士河口的時間愈長,就愈令人擔心。」

海洋生物保育慈善團體ORCA的保育學家巴比(Lucy Babey)表示:「這是白鯨出現在英國的最南端紀錄。」

英國海洋生物救援組織表示,英國最後一次發現白鯨蹤跡是3年前在北英格蘭的諾森伯蘭(Northumberland)海岸,以及北愛爾蘭,但極為罕見。

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

台灣海運大陸貨務運送流程

兩岸物流進出口一站式服務

特斯拉擴產,2018目標年產50萬輛車

因應龐大需求,電動車龍頭廠商特斯拉(Tesla)表示將積極擴產,目標在2018年達成年產量50萬輛車。

特斯拉2015年的產量約為5萬輛,但Model 3甫一推出就獲得40萬輛的超大筆訂單。為滿足這些預購車主的需求,特斯拉表示將積極擴產五倍,將原先2020年年產量50萬輛的目標提前到2018年實現,從中可望取得可觀的美國政府補助。而2016年,特斯拉預計將交車8~9萬輛。

特斯拉執行長Elon Musk於5月4日發表公司經營狀況時表示已將自己的辦公桌「移至生產線尾端,整個團隊都全力以赴」,彰顯急速擴張的決心。但他也坦言這個目標極具挑戰,若能成功,全球電動車市場將產生結構性的變化。

特斯拉財報:首季虧損

特斯拉在4日所發表的財報顯示,該公司今年第一季營收較去年同期大幅增加22%,為11.5億美元。但由於公司正在高速擴張,使營業費用比去年同期增加四成,至5億美元;加上Model X交車進度略有落後,使首季出現虧損,淨損為2.83億美元,高於去年第一季的淨損1.54億美元。扣除非常態性項目後,相當於每股虧損0.57美元。

未見起色的財報直接衝擊特斯拉股情。4日當天,特斯拉股價一度下滑4.2%,為222.56美元。不過,當Musk再宣布目標於2018年擴產至50萬輛後,股價再度回彈,最高漲至每股239.72美元。

特斯拉能否如期實現年產能50萬輛的目標,仍待觀察。近期有兩位高階副總即將離職,且特斯拉營運燒錢速度快於盈利回收,也是一隱憂。但特斯拉表示目前仍不需要依靠融資。

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

※自行創業 缺乏曝光? 下一步”網站設計“幫您第一時間規劃公司的門面形象

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

※試算大陸海運運費!

中國新能源汽車零部件發展規劃成果集中亮相北京10月車展

2016年10月13—16日,以“選擇·行動——未來從現在開始”為主題的2016(第四屆)節能與新能源汽車產業發展規 劃成果展覽會、中國國際汽車新能源及技術應用展覽會將在北京國家會議中心舉辦。展會由中國國家工業和資訊化部支援,科學技術部、中國國際貿易促進委員會批准,是貫徹落實國務院《節能與 新能源汽車產業發展規劃(2012—2020年)》,推動中國節能與新能源汽車產業發展的重要舉措之一。本屆展會將全方面覆蓋節能環保汽車、純電動 汽車、混合動力汽車、插電式混合動力和燃料電池汽車,以及電池、電機、電控等關鍵零部件和充電設施等產品,眾多國際知名汽車企業、零部件和充電設施供應 商將攜旗下新品參展。

汽車產業是國民經濟的重要支柱產業,也是實現新一輪技術革命和產業變革的重要載體。中國汽車產業快速發展,產銷增速連續七年居世界第一位,新能源汽車產業初具規模,15年中國已成為全球最大的新能源汽車生產以及銷售市場.

但同時,據中國國家863“節能與新能源汽車”重大項目監理諮詢專家組王秉剛調研,中國目前具有生產傳統汽車ABS系統能力的工廠主要有亞太、元豐與伯特利等幾家,他們是未來最有希望成為中國自主電動汽車制動系統的生產企業,與國外同類企業相比,他們規模都不大,在同類產品的市場佔有率不足5%,在年產量達2000多萬輛的中國汽車產業裡,如此重要的底盤部件,自主企業生產的比例低到差不多可以忽略不計的程度。中國汽車產業空心化可見一斑!這裡舉的僅是制動系統的例子,其它零部件也存在類似情況,就連備受關注的動力電池也未必樂觀,已經有國外大電池企業,在部分地方政府優惠政策的支援下在國內建廠。

中國新能源汽車零部件企業正加強關鍵核心技術研發,推進技術創新,竭盡全力避免傳統汽車產業存在的核心技術缺失、產業空心化現象,在新能源汽車行業重蹈覆轍。據車展合作單位盛大超越展覽公司嶽巍介紹,“新能源汽車零部件主要包括電機、驅動控制系統和電源系統,目前中國在新能源汽車零部件上的技術創新有序推進,整體研發水平不斷提升,中國企業的電機及驅動控制系統在新能源客車(如宇通、安凱、中通等)及乘用車(比亞迪、北汽、江淮等)上已得到廣泛應用,這些成果大部分將在北京10月新能源汽車展上展出,中國的電機及驅動控制系統參展企業包括:德沃仕、英威騰、中冶南方、超同步、合普動力、合康動力、福工動力、八達等。這些企業有的具有國外及科研院所的專業背景,有的是從電梯控制、機床控制發展而來。比如合普動力是低速電動車電機驅動控制的領軍企業,隨著高速新能源汽車市場的不斷成長,已開發了電動客車及乘用車電機驅動控制產品”。

盛大超越展覽公司電池及BMS展區經理耿忱也對中國企業抱有積極信心,他說,”北京10月新能源汽車成果展上的中國電池企業非常給力,沃特瑪、寧德時代、中航鋰電、力神電池、比克電池、萬向、山東威能、神工光電、內蒙古稀奧科、實聯長宜等知名電池企業展示了車用動力電池技術及市場應用的發展成果,部分電池展商還帶來了實際應用整車配合展示”。他還表示,“環宇賽爾、科隆集團、均勝普瑞等一批新的中國電池及BMS展商將有望加盟2016北京10月新能源汽車成果展,給主機廠提供更加前沿和多元化的產品解決方案“。

展會相關

2016中國國際汽車新能源及技術應用展覽會
節能與新能源汽車產業發展規劃成果展覽會
行業地位:中國最大高速新能源汽車展
展覽規模:30,000平方米(2015年)
觀眾數量:60,000人次(2015年)
展覽週期:每年一屆,2013年首屆
連絡人:岳巍 先生
手機:+86 135 5286 5285
展會網址:
 

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

台灣海運大陸貨務運送流程

兩岸物流進出口一站式服務

abp(net core)+easyui+efcore實現倉儲管理系統——ABP WebAPI與EasyUI結合增刪改查之一(二十七)

 



 

一.前言

       通過前面的文章的學習,我們已經有實現了傳統的ASP.NET Core MVC+EasyUI的增刪改查功能。本篇文章我們要實現了使用ABP提供的WebAPI方式+EasyUI來實現增刪改查的功能。本文中我們將不在使用DataGrid表格控件,而是使用樹形表格(TreeGrid)控件。

二、樹形表格(TreeGrid)介紹

       我先上圖,讓我們來看一下功能完成之後的組織管理信息列表頁面。如下圖。

       這個組織管理列表頁面使用TreeGrid來實現的。我們接下來介紹一下TreeGrid。

     首先、在定義TreeGrid時有兩個屬性必須要有一個是idField,這個要唯一;另一個是treeField的定義,這是樹節點的值,必須要有。 

     其次、easyui加載treegrid的json數據格式有三種,我就介紹我常用的這種。其他兩種方式,請查看easyui的相關文檔。

  {"total":7,"rows":[

           {"id":1,"name":"All Tasks","begin":"3/4/2010","end":"3/20/2010","progress":60,"iconCls":"icon-ok"},      
{"id":2,"name":"Designing","begin":"3/4/2010","end":"3/10/2010","progress":100,"_parentId":1,"state":"closed"},
{"id":21,"name":"Database","persons":2,"begin":"3/4/2010","end":"3/6/2010","progress":100,"_parentId":2},
{"id":22,"name":"UML","persons":1,"begin":"3/7/2010","end":"3/8/2010","progress":100,"_parentId":2}, {"id":23,"name":"Export Document","persons":1,"begin":"3/9/2010","end":"3/10/2010","progress":100,"_parentId":2},
{"id":3,"name":"Coding","persons":2,"begin":"3/11/2010","end":"3/18/2010","progress":80},
{"id":4,"name":"Testing","persons":1,"begin":"3/19/2010","end":"3/20/2010","progress":20} ],"footer":[ {"name":"Total Persons:","persons":7,"iconCls":"icon-sum"} ]}

     下面介紹一下上面數據中的幾個重要屬性:

     1)  _parentId :字段_parentId必不可少,且名稱唯一。記得前面有“_” ,他是用來記錄父級節點,沒有這個屬性,是沒法展示父級節點 其次就是這個父級節點必須存在,不然信息也是展示不出來,在後台遍歷組合的時候,如果父級節點不存在或為0時,此時 _parentId 應該不賦值,或設為“”。如果賦值 “0” 則在表格中不显示數據。

    2) state:是否展開

     3) checked:是否選中(用於複選框)

    4) iconCls:選項前面的圖標,如果自己不設定,父級節點默認為文件夾圖標,子級節點為文件圖標

 

    下面我們來開始實現組織管理頁面的相關功能。首先我們要創建一個組織信息實體。

三、創建Org實體

       1. 在Visual Studio 2017的“解決方案資源管理器”中,右鍵單擊“ABP.TPLMS.Core”項目的“Entitys”文件夾,在彈出菜單中選擇“添加” >

 > “類”。 將類命名為 Org,然後選擇“添加”。

      2.創建Org類繼承自Entity<int>,通過實現審計模塊中的IHasCreationTime來實現保存創建時間。根據TreeGrid所需要的數據格式的要求。代碼如下:

using Abp.Domain.Entities;
using Abp.Domain.Entities.Auditing;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Text;
 

namespace ABP.TPLMS.Entitys
{

   public partial class Org : Entity<int>, IHasCreationTime
{
      int m_parentId = 0;
        public Org()
        {

            this.Id = 0;
            this.Name = string.Empty;
            this.HotKey = string.Empty;
            this.ParentId = 0;
            this.ParentName = string.Empty;

            this.IconName = string.Empty;

            this.Status = 0;

            this.Type = 0;

            this.BizCode = string.Empty;
            this.CustomCode = string.Empty;
            this.CreationTime = DateTime.Now;
            this.UpdateTime = DateTime.Now;
            this.CreateId = 0;

            this.SortNo = 0;

        }

        [Required]
        [StringLength(255)]
        public string Name { get; set; }

        [StringLength(255)]
        public string HotKey { get; set; }

        public int ParentId { get { return m_parentId; } set { m_parentId = value; } }

        [Required]
        [StringLength(255)]
        public string ParentName { get; set; }

        public bool IsLeaf { get; set; }

        public bool IsAutoExpand { get; set; }

        [StringLength(255)]
        public string IconName { get; set; } 

        public int Status { get; set; }

        public int Type { get; set; }

        [StringLength(255)]
        public string BizCode { get; set; }

        [StringLength(100)]
        public string CustomCode { get; set; }

        public DateTime CreationTime { get; set; }
        public DateTime UpdateTime { get; set; }
        public int CreateId { get; set; }

        public int SortNo { get; set; }
public int? _parentId {
            get {
                if (m_parentId == 0)                

                {
                    return null;
                }

                return m_parentId;
            }           

        }
    }
}

      3.定義好實體之後,我們去“ABP.TPLMS.EntityFrameworkCore”項目中的“TPLMSDbContext”類中定義實體對應的DbSet,以應用Code First 數據遷移。添加以下代碼

 

using Microsoft.EntityFrameworkCore;
using Abp.Zero.EntityFrameworkCore;
using ABP.TPLMS.Authorization.Roles;
using ABP.TPLMS.Authorization.Users;
using ABP.TPLMS.MultiTenancy;
using ABP.TPLMS.Entitys;
 

namespace ABP.TPLMS.EntityFrameworkCore
{

    public class TPLMSDbContext : AbpZeroDbContext<Tenant, Role, User, TPLMSDbContext>
    {

        /* Define a DbSet for each entity of the application */
    
        public TPLMSDbContext(DbContextOptions<TPLMSDbContext> options)
            : base(options)

        {
        }

        public DbSet<Module> Modules { get; set; }
        public DbSet<Supplier> Suppliers { get; set; }
  public DbSet<Cargo> Cargos { get; set; }
          public DbSet<Org> Orgs { get; set; }

    }
}

 

 

 

      4.從菜單中選擇“工具->NuGet包管理器器—>程序包管理器控制台”菜單。

     5. 在PMC中,默認項目選擇EntityframeworkCore對應的項目后。輸入以下命令:Add-Migration AddEntityOrg,創建遷移。如下圖。

 

       6. 在上面的命令執行完畢之後,創建成功后,會在Migrations文件夾下創建時間_AddEntityOrg格式的類文件,這些代碼是基於DbContext指定的模型。如下圖。

 

     7.在程序包管理器控制台,輸入Update-Database,回車執行遷移。執行成功后,如下圖。

 

     8. 在SQL Server Management Studio中查看數據庫,Orgs表創建成功。

 

 

 

 

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

※自行創業 缺乏曝光? 下一步”網站設計“幫您第一時間規劃公司的門面形象

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

※試算大陸海運運費!

前端每周學習分享–第12期

1.VuePress

大家看過不少Vue.js及其子項目的文檔,一定發現了它們風格完全一致,界面清爽,讀起來很舒服,它們都使用了vuepress。

VuePress是尤大為了支持 Vue 及其子項目的文檔需求而寫的一個靜態網站生成工具,廣泛用於編寫技術文檔 ,可以部署在github上做個人博客。

原理:

在構建過程中,會創建應用程序的服務器渲染版本,通過訪問每個路由,來渲染相應的 HTML。

其中, 每個 markdown 文件都使用 編譯為 HTML,然後作為 Vue 組件的模板進行處理。這允許你直接在 markdown 文件中使用 Vue,在需要嵌入動態內容時,這種使用方式非常有用。

十分實用的特性:

  • md文件可內嵌vue代碼
  • 可自定義主題
  • 利用service worker做離線緩存
  • 多語言支持
  • 基於git的最近更新

官方文檔:

快速搭建:

2.WebWorker

web worker是運行在後台的jacvascript,利用類似線程的消息傳遞實現并行,獨立於其他腳本,不會影響頁面的性能。

web worker能夠長時間運行,有理想的啟動性能以及理想的內存消耗。

worker 創建后,它可以向它的創建者指定的事件監聽函數傳遞消息,這樣該worker生成的所有任務都會接收到這些消息。

webworker有專用線程dedicated worker(單窗口專用),sharedWorker(可多窗口共享),以及後來的service worker(目前瀏覽器支持程度還不高)。

2.1.dedicated worker

使用方法:

worker線程里監聽onmessage,

頁面線程里創建worker對象:const myworker = new Worker("worker.js")

發送消息:postMessage(msg)

接受消息:onmessage = function(e){const msg = e.data}

msg的數據格式自行定義。

終止worker:myworker.terminate()

例如下面的示例,worker會接收頁面上輸入的兩個数字,計算出乘積后返回結果。

worker.js

onmessage = function(e) {
  console.log('Worker: Message received from main script');
  let result = e.data[0] * e.data[1];
  if (isNaN(result)) {
    postMessage('Please write two numbers');
  } else {
    let workerResult = 'Result: ' + result;
    console.log('Worker: Posting message back to main script');
    postMessage(workerResult);
  }
}

index.html里

const first = document.querySelector('#number1');
const second = document.querySelector('#number2');
const result = document.querySelector('.result');
if (window.Worker) {
    const myWorker = new Worker("worker.js");

    first.onchange = function() {
      myWorker.postMessage([first.value, second.value]);
      console.log('Message posted to worker');
    }

    second.onchange = function() {
      myWorker.postMessage([first.value, second.value]);
      console.log('Message posted to worker');
    }

    myWorker.onmessage = function(e) {
        result.textContent = e.data;
        console.log('Message received from worker');
    }
} else {
    console.log('Your browser doesn\'t support web workers.')
}

2.2.shared worker

共享進程可以連接到多個不同的頁面,這些頁面必須屬於相同的域(相同的協議,主機以及端口)

在火狐中,共享進程不能在私有與公共文檔間進行共享。

SharedWorker.port返回一個MessagePort對象,用來進行通信和對共享進程進行控制。

創建共享進程對象:const myWorker = new SharedWorker("worker.js");

獲取端口:

發送消息:myWorker.port.postMessage(msg)

接收消息:myWorker.port.onmessage = function(e) {const msg = e.data}

worker線程獲取端口:onconnect = function(e) {const port = e.ports[0]}

啟動端口:port.start()

2.3.service worker

Service Worker 可以理解為一個介於客戶端和服務器之間的一個代理服務器 ,常用於做離線資源緩存

出於對安全問題的考慮,Service Worker 只能被使用在 https 或者本地的 localhost 環境下。

暫時沒有仔細學這塊,可以閱讀。

參考文章:

3.代碼相關

3.1.元素內文本垂直居中

已知元素高度的話,可以設置line-height:元素高度.

如果元素高度未知,就不能使用line-height了。

有人會想使用line-height:100%,會發現這是不行的,這個百分比是相對當前字體尺寸,而不是元素高度。

我使用了flex布局實現

    display: flex;
    align-items:center;
    justify-content:center;

還可以設置padding來使文本看起來垂直居中

padding: 50px 20px;

3.2.微信小程序自定義placeholder的隱藏時機

在一個searchBar組件中,有一個自定的placeholder如下:

<!-- <view
​        wx:if="{{!inputValue.length}}"
​        class="placeholder" >
​        {{placeholder}}
​     </view> -->

原生的placeholder不是在觸發bindinput時隱藏,而是在輸入鍵盤按鈕點擊時。使用inputValue.length來判斷显示自定義的placeholder會在某些輸入法中導致拼音預覽和自定義placeholder重疊(因為拼音显示的時候value值還沒變)

最後選擇棄用這個自定義placeholder,使用input組件的placeholder屬性,並使用placeholder-class來設置它的樣式。

3.3.關於微信小程序原生組件的坑

原生組件有camera、canvas、input (僅在focus時表現為原生組件) 、live-player、live-pusher、map、textarea、video、cover-view、cover-image。

所以當你用canvas畫圖表、使用地圖、播放視頻甚至做文本輸入時,都是可能遇到相關坑點的。

  1. 關於原生組件、組件之間的層級關係、

​ 原生組件的層級始終高於普通組件,不論普通組件的z-index設置了多少。

​ 后插入的原生組件可以覆蓋之前的原生組件。

​ 原生組件之間的相對層級關係可以通過z-index來調整。

​ 原生組件會遮擋vconsole彈出的調試面板。

​ cover-view和cover-image可以覆蓋在部分原生組件上。

  1. cover-view的使用

​ cover-view在做地圖、畫布、視頻上的彈出層時是會用到的,但它有很多使用限制。

​ cover-view只能內嵌cover-view、cover-image、button,其他元素在真機上就會被cover-view給覆蓋住,如果想內嵌radio、picker等就只能自己用這3個可內嵌的元素來實現。

​ cover-view不支持iconfont,也不支持單邊border、background-imageshadowoverflow: visible等。

  1. input的使用

​ input在不聚焦時是佔位元素,會被原生組件遮擋,聚焦時才使用原生組件渲染。這就會出現input設置了更高的z-index,不聚焦時仍會被其他原生組件遮住。

​ 要解決這個問題,可以使用textarea來代替input。

​ 我的一個解決方案是,加一個標誌位來記錄input是否聚焦,當不聚焦時,显示一個承載value值的cover-view(它需要綁定一個觸發聚焦的點擊事件),聚焦時,就显示input組件。

3.4.多個標籤頁之間的通信方案

  1. 使用websocket

  2. 使用localstorage或者cookie

  3. 使用sharedworker

我遇到的問題是需要在新窗口打開當前網站的新窗口時,能繼承上一個窗口的vuex的狀態樹里的某些數據。這不需要和服務器打交道,最好就在本地。

最後使用localstorage來做,在跳轉新窗口前更新localstorage,在新窗口根組件掛載時取出數據。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

台灣海運大陸貨務運送流程

兩岸物流進出口一站式服務

工信部第285批新車公示218款新能源入選

根據《中華人民共和國行政許可法》和《國務院對確需保留的行政審批專案設定行政許可的決定》的規定,工信部日前將許可的汽車、摩托車、三輪汽車和低速貨車生產企業及產 品(第285批)予以了公告,共有218款新能源車型入選。進入該公告的新能源汽車可開展生產銷售,但是要獲得補貼,還需再獲得《新能源汽車推廣應用推薦車型目錄》准入。

純電動轎車/乘用車方面,北汽、長城、禦捷馬、卡威、吉利等12款車型入選。

插電式乘用車方面,比亞迪、寶馬、之諾、上汽等7款車型入選。

純電動客車方面,安凱、江淮、安源、北奔、北方、福田、比亞迪、白雲、五菱、陸地方舟、尼歐凱、友誼、青年、海格、開沃、依維柯、飛燕、大通、象牌、野馬、華新、金龍、金旅、宇通、黃河、中通、中植汽車、穗通等28個品牌75款車型入選。

插電式混動客車方面, 安凱、海格、易聖達、金龍、金旅、宇通6個品牌21款車型入選。

新能源專用車方面,福田、北京、大運、黃海、東風、華神、揚子江、東風、福建、環球、藍速、田野、中悅 、卡威、陸地方舟、青年曼、康迪、五菱、暢達、躍進、金龍、凱馬、時風、太行成功、東風、金杯、邢牛、海德、解放、神州、宇通、長帆汽車、迪馬、炫虎等33個品牌103款車型入選。

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

※自行創業 缺乏曝光? 下一步”網站設計“幫您第一時間規劃公司的門面形象

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

※試算大陸海運運費!