主頁 > 軟體設計 > 服務器并發IO性能提升之路 — 從網路編程基礎到epoll

服務器并發IO性能提升之路 — 從網路編程基礎到epoll

2020-12-31 13:15:53 軟體設計

從網路編程基本概念說起

我們常常使用HTTP協議來傳輸各種格式的資料,其實HTTP這個應用層協議的底層,是基于傳輸層TCP協議來實作的,TCP協議僅僅把這些資料當做一串無意義的資料流來看待,所以,我們可以說:客戶端與服務器通過在建立的連接上發送位元組流來進行通信,
這種C/S架構的通信機制,需要標識通信雙方的網路地址和埠號資訊,對于客戶端來說,需要知道我的資料接收方位置,我們用網路地址和埠來唯一標識一個服務端物體;對于服務端來說,需要知道資料從哪里來,我們同樣用網路地址和埠來唯一標識一個客戶端物體,那么,用來唯一標識通信兩端的資料結構就叫做套接字,一個連接可以由它兩端的套接字地址唯一確定:

(客戶端地址:客戶端埠號,服務端地址:服務端埠號)

有了通信雙方的地址資訊之后,就可以進行資料傳輸了,那么我們現在需要一個規范,來規定通信雙方的連接及資料傳輸程序,在Unix系統中,實作了一套套接字介面,用來描述和規范雙方通信的整個程序,

socket():創建一個套接字描述符

connect():客戶端通過呼叫connect函式來建立和服務器的連接

bind():告訴內核將socket()創建的套接字與某個服務端地址與埠連接起來,后續會對這個地址和埠進行監聽

listen():告訴內核,將這個套接字當成服務器這種被動物體來看待(服務器是等待客戶端連接的被動物體,而內核認為

socket()創建的套接字默認是主動物體,所以才需要listen()函式,告訴內核進行主動到被動物體的轉換)

accept():等待客戶端的連接請求并回傳一個新的已連接描述符

最簡單的單行程服務器

由于Unix的歷史遺留問題,原始的套接字介面對地址和埠等資料封裝并不簡潔,為了簡化這些我們不關注的細節而只關注整個流程,我們使用PHP來進行分析,PHP對Unix的socket相關介面進行了封裝,所有相關套接字的函式都被加上了socket_前綴,并且使用一個資源型別的套接字句柄代替Unix中的檔案描述符fd,在下文的描述中,均用“套接字”代替Unix中的檔案描述符fd進行闡述,一個PHP實作的簡單服務器偽代碼如下:

<?php

if (($listenSocket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP))=== false) {
    echo '套接字創建失敗';
}
if (socket_bind($listenSocket, '127.0.0.1', 8888) === false) {
    echo '系結地址與埠失敗';
}
if (socket_listen($listenSocket) === false) {
    echo '轉換主動套接字到被動套接字失敗';
}
while (1) {
    if (($connSocket = socket_accept($listenSocket)) === false) {
        echo '客戶端的連接請求還沒有到達';
    } else {
        socket_close($listenSocket); //釋放監聽套接字
        socket_read($connSocket);  //讀取客戶端資料,阻塞
        socket_write($connSocket); //給客戶端回傳資料,阻塞
        
    }
    socket_close($connSocket);
}

我們梳理一下這個簡單的服務器創建流程

socket_create():創建一個套接字,這個套接字就代表建立的連接上的一個端點,第一個引數AF_INET為使用的底層協議為IPv4;第二個引數SOCK_STREAM表示使用位元組流進行資料傳輸;第三個引數SQL_TCP代表本層協議為TCP協議,這里創建的套接字只是一個連接上的端點的一個抽象概念,

socket_bind():系結這個套接字到一個具體的服務器地址和埠上,真正實體化這個套接字,引數就是你之前創建的一個抽象的套接字,還有你具體的網路地址和埠,

socket_listen():我們觀察到只有一個函式引數就是之前創建的套接字,有些同學之前可能認為這一步函式呼叫完全沒有必要,但是它告訴內核,我是一個服務器,將套接字轉換為一個被動物體,其實是有很大的作用的,

socket_accept():接收客戶端發來的請求,因為服務器啟動之后,是不知道客戶端什么時候有連接到來的,所以,需要在一個while回圈中不斷呼叫這個函式,如果有連接請求到來,那么就會回傳一個新的套接字,我們可以通過這個新的套接字進行與客戶端的資料通信,如果沒有,就只能不斷地進行回圈,直到有請求到來為止,

注意,在這里我將套接字分為兩類,一個是監聽套接字,一個是連接套接字,注意這里對兩種套接字的區分,在下面的討論中會用到:

監聽套接字:服務器對某個埠進行監聽,這個套接字用來表示這個埠($listenSocket)

連接套接字:服務器與客戶端已經建立連接,所有的讀寫操作都要在連接套接字上進行($connSocket)

那么我們對這個服務器進行分析,它存在什么問題呢?

一個這樣的服務器行程只能同時處理一個客戶端連接與相關的讀寫操作,因為一旦有一個客戶端連接請求到來,我們對監聽套接字進行accept之后,就開啟了與該客戶端的資料傳輸程序,在資料讀寫的程序中,整個行程被該客戶端連接獨占,當前服務器行程只能處理該客戶端連接的讀寫操作,無法對其它客戶端的連接請求進行處理,

IO并發性能提升之路

由于上述服務器的性能太爛,無法同時處理多個客戶端連接以及讀寫操作,所以優秀的開發者們想出了以下幾種方案,用以提升服務器的效率,分別是:

多行程多執行緒基于單行程的IO多路復用(select/poll/epoll)

多行程

那么如何去優化單行程呢?很簡單,一個行程不行,那搞很多個行程不就可以同時處理多個客戶端連接了嗎?我們想了想,寫出了代碼:

<?php

if (($listenSocket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP))=== false) {
    echo '套接字創建失敗';
}
if (socket_bind($listenSocket, '127.0.0.1', 8888) === false) {
    echo '系結地址與埠失敗';
}
if (socket_listen($listenSocket) === false) {
    echo '轉換主動套接字到被動套接字失敗';
}
for ($i = 0; $i < 10; $i++) { //初始創建10個子行程
    if (pcntl_fork() == 0) {
        if (($connSocket = socket_accept($listenSocket)) === false) {
            echo '客戶端的連接請求還沒有到達';
        } else {
            socket_close($listenSocket); //釋放監聽套接字
            socket_read($connSocket);  //讀取客戶端資料
            socket_write($connSocket); //給客戶端回傳資料
        }
        socket_close($connSocket);
    }
}

我們主要關注這個for回圈,一共回圈了10次代表初始的子行程數量我們設定為10,接著我們呼叫了pcntl_fork()函式創建子行程,由于一個客戶端的connect就對應一個服務端的accept,所以在每個fork之后的10個子行程中,我們均進行accept的系統呼叫,等待客戶端的連接,這樣,就可以通過10個服務器行程,同時接受10個客戶端的連接、同時為10個客戶端提供讀寫資料服務,
注意這樣一個細節,由于所有子行程都是預先創建好的,那么請求到來的時候就不用創建子行程,也提高了每個連接請求的處理效率,同時也可以借助行程池的概念,這些子行程在處理完連接請求之后并不立即回收,可以繼續服務下一個客戶端連接請求,就不用重復的進行fork()的系統呼叫,也能夠提高服務器的性能,這些小技巧在PHP-FPM的實作中都有所體現,其實這種行程創建方式是其三種運行模式中的一種,被稱作static(靜態行程數量)模式:

ondemand:按需啟動,PHP-FPM啟動的時候不會啟動任何一個子行程(worker行程),只有客戶端連接請求到達時才啟動

dynamic:在PHP-FPM啟動時,會初始啟動一些子行程,在運行程序中視情況動態調整worker數量

static:PHP-FPM啟動時,啟動固定大小數量的子行程,在運行期間也不會擴容

回到正題,多行程這種方式的的確確解決了服務器在同一時間只能處理一個客戶端連接請求的問題,但是這種基于多行程的客戶端連接處理模式,仍存在以下劣勢:

fork()等系統呼叫會使得行程的背景關系進行切換,效率很低

行程創建的數量隨著連接請求的增加而增加,比如100000個請求,就要fork100000個行程,開銷太大

行程與行程之間的地址空間是私有、獨立的,使得行程之間的資料共享變得困難

既然談到了多行程的資料共享與切換開銷的問題,那么我們能夠很快想到解決該問題的方法,就是化多行程為更輕量級的多執行緒,

對于想從事Linux后端服務器開發的或者是想轉行互聯網行業的朋友,提升自己,通過耗時比較久,技術堆疊不完善,架構不成體系,自律性差,這里給大家分享一個系統的學習視頻,點擊鏈接訂閱后免費試聽:https://ke.qq.com/course/417774?flowToken=1013189

從基礎入門開始到后端高級架構,一個完整的體系課程,原理到實戰的講解,值得推薦,有興趣了解的朋友可以看看哦,

多執行緒

執行緒是運行在行程背景關系的邏輯流,一個行程可以包含多個執行緒,多個執行緒運行在單一的行程背景關系中,因此共享這個行程的地址空間的所有內容,解決了行程與行程之間通信難的問題,同時,由于一個執行緒的背景關系要比一個行程的背景關系小得多,所以執行緒的背景關系切換,要比行程的背景關系切換效率高得多,執行緒是輕量級的行程,解決了行程背景關系切換效率低的問題,
由于PHP中沒有多執行緒的概念,所以我們僅僅把上面的偽代碼中創建行程的部分,改成創建執行緒即可,代碼大體類似,在此不再贅述,

IO多路復用

前面談到的都是通過增加行程和執行緒的數量來同時處理多個套接字而IO多路復用只需要一個行程就能夠處理多個套接字,IO多路復用這個名詞看起來好像很復雜很高深的樣子,實際上,這項技術所能帶來的本質成果就是:一個服務端行程可以同時處理多個套接字描述符,

多路:多個客戶端連接(連接就是套接字描述符)

復用:使用單行程就能夠實作同時處理多個客戶端的連接

在之前的講述中,一個服務端行程,只能同時處理一個連接,如果想同時處理多個客戶端連接,需要多行程或者多執行緒的幫助,免不了背景關系切換的開銷,

IO多路復用技術就解決了背景關系切換的問題IO多路復用技術的發展可以分為select->poll->epoll三個階段,

IO多路復用的核心就是添加了一個套接字集合管理員,它可以同時監聽多個套接字,由于客戶端連接以及讀寫事件到來的隨機性,我們需要這個管理員在單行程內部對多個套接字的事件進行合理的調度,

select

最早的套接字集合管理員是select()系統呼叫,它可以同時管理多個套接字,select()函式會在某個或某些套接字的狀態從不可讀變為可讀、或不可寫變為可寫的時候通知服務器主行程,所以select()本身的呼叫是阻塞的,但是具體哪一個套接字或哪些套接字變為可讀或可寫我們是不知道的,所以我們需要遍歷所有select()回傳的套接字來判斷哪些套接字可以進行處理了,而這些套接字中又可以分為監聽套接字與連接套接字(上文提過),我們可以使用PHP為我們提供的socket_select()函式,在select()的函式原型中,為套接字們分了個類:讀、寫與例外套接字集合,分別監聽套接字的讀、寫與例外事件,:

function socket_select (array &$read, array &$write, array &$except, $tv_sec, $tv_usec = 0) {}

舉個例子,如果某個客戶單通過呼叫connect()連接到了服務器的監聽套接字($listenSocket)上,這個監聽套接字的狀態就會從不可讀變為可讀,由于監聽套接字只有一個,select()對于監聽套接字上的處理仍然是阻塞的,一個監聽套接字,存在于整個服務器的生命周期中,所以在select()的實作中并不能體現出其對監聽套接字的優化管理,
在當一個服務器使用accept()接受多個客戶端連接,并生成了多個連接套接字之后,select()的管理才能就會體現出來,這個時候,select()的監聽串列中有一個監聽套接字、和與一堆客戶端建立連接后新創建的連接套接字,在這個時候,可能這一堆已建立連接的客戶端,都會通過這個連接套接字發送資料,等待服務端接收,假設同時有5個連接套接字都有資料發送,那么這5個連接套接字的狀態都會變成可讀狀態,由于已經有套接字變成了可讀狀態,select()函式解除阻塞,立即回傳,具體哪一個套接字或哪些套接字變為可讀或可寫我們是不知道的,所以我們需要遍歷所有select()回傳的套接字,來判斷哪些套接字已經就緒,可以進行讀寫處理,遍歷完畢之后,就知道有5個連接套接字可以進行讀寫處理,這樣就實作了同時對多個套接字的管理,使用PHP實作select()的代碼如下:

<?php
if (($listenSocket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP))=== false) {
    echo '套接字創建失敗';
}
if (socket_bind($listenSocket, '127.0.0.1', 8888) === false) {
    echo '系結地址與埠失敗';
}
if (socket_listen($listenSocket) === false) {
    echo '轉換主動套接字到被動套接字失敗';
}

/* 要監聽的三個sockets陣列 */
$read_socks = array(); //讀
$write_socks = array(); //寫
$except_socks = NULL; //例外

$read_socks[] = $listenSocket; //將初始的監聽套接字加入到select的讀事件監聽陣列中

while (1) {
    /* 由于select()是參考傳遞,所以這兩個陣列會被改變,所以用兩個臨時變數 */
    $tmp_reads = $read_socks;
    $tmp_writes = $write_socks;
    $count = socket_select($tmp_reads, $tmp_writes, $except_socks, NULL);
    foreach ($tmp_reads as $read) { //不知道哪些套接字有變化,需要對全體套接字進行遍歷來看誰變了
        if ($read == $listenSocket) { //監聽套接字有變化,說明有新的客戶端連接請求到來
            $connSocket = socket_accept($listenSocket);  //回應客戶端連接, 此時一定不會阻塞
            if ($connSocket) {
                //把新建立的連接socket加入監聽
                $read_socks[] = $connSocket;
                $write_socks[] = $connSocket;
            }
        } else { //新創建的連接套接字有變化
            /*客戶端傳輸資料 */
            $data = socket_read($read, 1024);  //從客戶端讀取資料, 此時一定會讀到資料,不會產生阻塞
            if ($data === '') { //已經無法從連接套接字中讀到資料,需要移除對該socket的監聽
                foreach ($read_socks as $key => $val) {
                    if ($val == $read) unset($read_socks[$key]); //移除失效的套接字
                }
                foreach ($write_socks as $key => $val) {
                    if ($val == $read) unset($write_socks[$key]);
                }
                socket_close($read);
            } else { //能夠從連接套接字讀到資料,此時$read是連接套接字
                if (in_array($read, $tmp_writes)) {
                    socket_write($read, $data);//如果該客戶端可寫 把資料寫回到客戶端
                }
            }
        }
    }
}
socket_close($listenSocket);

但是,select()函式本身的呼叫阻塞的,因為select()需要一直等到有狀態變化的套接字之后(比如監聽套接字或者連接套接字的狀態由不可讀變為可讀),才能解除select()本身的阻塞,繼續對讀寫就緒的套接字進行處理,雖然這里是阻塞的,但是它能夠同時回傳多個就緒的套接字,而不是之前單行程中只能夠處理一個套接字,大大提升了效率
總結一下,select()的過人之處有以下幾點:

實作了對多個套接字的同時、集中管理

通過遍歷所有的套接字集合,能夠獲取所有已就緒的套接字,對這些就緒的套接字進行操作不會阻塞

但是,select()仍存在幾個問題:

select管理的套接字描述符們存在數量限制,在Unix中,一個行程最多同時監聽1024個套接字描述符

select回傳的時候,并不知道具體是哪個套接字描述符已經就緒,所以需要遍歷所有套接字來判斷哪個已經就緒,可以繼續進行讀寫

為了解決第一個套接字描述符數量限制的問題,聰明的開發者們想出了poll這個新套接字描述符管理員,用以替換select這個老管理員,select()就可以安心退休啦,

poll

poll解決了select帶來的套接字描述符的最大數量限制問題,由于PHP的socket擴展沒有poll對應的實作,所以這里放一個Unix的C語言原型實作:

int poll (struct pollfd *fds, unsigned int nfds, int timeout);

poll的fds引數集合了select的read、write和exception套接字陣列,合三為一,poll中的fds沒有了1024個的數量限制,當有些描述符狀態發生變化并就緒之后,poll同select一樣會回傳,但是遺憾的是,我們同樣不知道具體是哪個或哪些套接字已經就緒,我們仍需要遍歷套接字集合去判斷究竟是哪個套接字已經就緒,這一點并沒有解決剛才提到select的第二個問題,
我們可以總結一下,select和poll這兩種實作,都需要在回傳后,通過遍歷所有的套接字描述符來獲取已經就緒的套接字描述符,事實上,同時連接的大量客戶端在一時刻可能只有很少的處于就緒狀態,因此隨著監視的描述符數量的增長,其效率也會線性下降,
為了解決不知道回傳之后究竟是哪個或哪些描述符已經就緒的問題,同時避免遍歷所有的套接字描述符,聰明的開發者們又發明出了epoll機制,完美解決了select和poll所存在的問題,

epoll

epoll是最先進的套接字們的管理員,解決了上述select和poll中所存在的問題,它將一個阻塞的select、poll系統呼叫拆分成了三個步驟,一次select或poll可以看作是由一次 epoll_create、若干次 epoll_ctl、若干次 epoll_wait構成:

int epoll_create(int size);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);

epoll_create():創建一個epoll實體,后續操作會使用

epoll_ctl():對套接字描述符集合進行增刪改操作,并告訴內核需要監聽套接字描述符的什么事件

epoll_wait():等待監聽串列中的連接事件(監聽套接字描述符才會發生)或讀寫事件(連接套接字描述符才會發生),如果有某個或某些套接字事件已經準備就緒,就會回傳這些已就緒的套接字們

看起來,這三個函式明明就是從select、poll一個函式拆成三個函式了嘛,我們對某套接字描述符的添加、洗掉、修改操作由之前的代碼實作變成了呼叫epoll_ctl()來實作,epoll_ctl()的引數含義如下:

epfd:epoll_create()的回傳值

op:表示對下面套接字描述符fd所進行的操作,EPOLL_CTL_ADD:將描述符添加到監聽串列;EPOLL_CTL_DEL:不再監聽某描述符;EPOLL_CTL_MOD:修改某描述符

fd:上面op操作的套接字描述符物件(之前在PHP中是listenSocket與listenSocket與 connSocket兩種套接字描述符)例如將某個套接字添加到監聽串列中

event:告訴內核需要監聽該套接字描述符的什么事件(如讀寫、連接等)

最后我們呼叫epoll_wait()等待連接或讀寫等事件,在某個套接字描述符上準備就緒,當有事件準備就緒之后,會存到第二個引數epoll_event結構體中,通過訪問這個結構體就可以得到所有已經準備好事件的套接字描述符,這里就不用再像之前select和poll那樣,遍歷所有的套接字描述符之后才能知道究竟是哪個描述符已經準備就緒了,這樣減少了一次O(n)的遍歷,大大提高了效率,
在最后回傳的所有套接字描述符中,同樣存在之前說過的兩種描述符:監聽套接字描述符和連接套接字描述符,那么我們需要遍歷所有準備就緒的描述符,然后去判斷究竟是監聽還是連接套接字描述符,然后視情況做做出accept(監聽套接字)或者是read(連接套接字)的處理,一個使用C語言撰寫的epoll服務器的偽代碼如下(重點關注代碼注釋):

int main(int argc, char *argv[]) {

    listenSocket = socket(AF_INET, SOCK_STREAM, 0); //同上,創建一個監聽套接字描述符
    
    bind(listenSocket)  //同上,系結地址與埠
    
    listen(listenSocket) //同上,由默認的主動套接字轉換為服務器適用的被動套接字
    
    epfd = epoll_create(EPOLL_SIZE); //創建一個epoll實體
    
    ep_events = (epoll_event*)malloc(sizeof(epoll_event) * EPOLL_SIZE); //創建一個epoll_event結構存盤套接字集合
    event.events = EPOLLIN;
    event.data.fd = listenSocket;
    
    epoll_ctl(epfd, EPOLL_CTL_ADD, listenSocket, &event); //將監聽套接字加入到監聽串列中
    
    while (1) {
    
        event_cnt = epoll_wait(epfd, ep_events, EPOLL_SIZE, -1); //等待回傳已經就緒的套接字描述符們
        
        for (int i = 0; i < event_cnt; ++i) { //遍歷所有就緒的套接字描述符
            if (ep_events[i].data.fd == listenSocket) { //如果是監聽套接字描述符就緒了,說明有一個新客戶端連接到來
            
                connSocket = accept(listenSocket); //呼叫accept()建立連接
                
                event.events = EPOLLIN;
                event.data.fd = connSocket;
                
                epoll_ctl(epfd, EPOLL_CTL_ADD, connSocket, &event); //添加對新建立的連接套接字描述符的監聽,以監聽后續在連接描述符上的讀寫事件
                
            } else { //如果是連接套接字描述符事件就緒,則可以進行讀寫
            
                strlen = read(ep_events[i].data.fd, buf, BUF_SIZE); //從連接套接字描述符中讀取資料, 此時一定會讀到資料,不會產生阻塞
                if (strlen == 0) { //已經無法從連接套接字中讀到資料,需要移除對該socket的監聽
                
                    epoll_ctl(epfd, EPOLL_CTL_DEL, ep_events[i].data.fd, NULL); //洗掉對這個描述符的監聽
                    
                    close(ep_events[i].data.fd);
                } else {
                    write(ep_events[i].data.fd, buf, str_len); //如果該客戶端可寫 把資料寫回到客戶端
                }
            }
        }
    }
    close(listenSocket);
    close(epfd);
    return 0;
}

我們看這個通過epoll實作一個IO多路復用服務器的代碼結構,除了由一個函式拆分成三個函式,其余的執行流程基本同select、poll相似,只是epoll會只回傳已經就緒的套接字描述符集合,而不是所有描述符的集合,IO的效率不會隨著監視fd的數量的增長而下降,大大提升了效率,同時它細化并規范了對每個套接字描述符的管理(如增刪改的程序),此外,它監聽的套接字描述符是沒有限制的,這樣,之前select、poll的遺留問題就全部解決啦,

總結

我們從最基本網路編程說起,開始從一個最簡單的同步阻塞服務器到一個IO多路復用服務器,我們從頭到尾了解到了一個服務器性能提升的思考與實作程序,而提升服務器的并發性能的方式遠不止這幾種,還包括協程等新的概念需要我們去對比與分析,

轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/242953.html

標籤:其他

上一篇:自加載宏讓你的Excel支持正則處理函式

下一篇:C++繼承中的賦值兼容規則你了解多少???

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 面試突擊第一季,第二季,第三季

    第一季必考 https://www.bilibili.com/video/BV1FE411y79Y?from=search&seid=15921726601957489746 第二季分布式 https://www.bilibili.com/video/BV13f4y127ee/?spm_id_fro ......

    uj5u.com 2020-09-10 05:35:24 more
  • 第三單元作業總結

    1.前言 這應該是本學期最后一次寫作業總結了吧。總體來說,對作業的節奏也差不多掌握了,作業做起來的效率也更高了。雖然和之前的作業一樣,作業中都要用到新的知識,但是相比之前,更加懂得了如何利用工具以及資料。雖然之間卡過殼,但總體而言,這幾次作業還算完成的比較好。 2.作業程序總結 相比前兩個單元,此單 ......

    uj5u.com 2020-09-10 05:35:41 more
  • 北航OO(2020)第四單元博客作業暨課程總結博客

    北航OO(2020)第四單元博客作業暨課程總結博客 本單元作業的架構設計 在本單元中,由于UML圖具有比較清晰的樹形結構,因此我對其中需要進行查詢操作的元素進行了包裝,在樹的父節點中存盤所有孩子的參考。考慮到性能問題,我采用了快取機制,一次查詢后盡可能快取已經遍歷過的資訊,以減少遍歷次數。 本單元我 ......

    uj5u.com 2020-09-10 05:35:48 more
  • BUAA_OO_第四單元

    一、UML決議器設計 ? 先看下題目:第四單元實作一個基于JDK 8帶有效性檢查的UML(Unified Modeling Language)類圖,順序圖,狀態圖分析器 MyUmlInteraction,實際上我們要建立一個有向圖模型,UML中的物件(元素)可能與同級元素連接,也可與低級元素相連形成 ......

    uj5u.com 2020-09-10 05:35:54 more
  • 6.1邏輯運算子

    邏輯運算子 1. && 短路與 運算式1 && 運算式2 01.運算式1為true并且運算式2也為true 整體回傳為true 02.運算式1為false,將不會執行運算式2 整體回傳為false 03.只要有一個運算式為false 整體回傳為false 2. || 短路或 運算式1 || 運算式2 ......

    uj5u.com 2020-09-10 05:35:56 more
  • BUAAOO 第四單元 & 課程總結

    1. 第四單元:StarUml檔案決議 本單元采用了圖模型決議UML。 UML檔案可以抽象為圖、子圖、邊的邏輯結構。 在實作中,圖的節點包括類、介面、屬性,子圖包括狀態圖、順序圖等。 采用了三次遍歷UML元素的方法建圖,第一遍遍歷建點,第二、三次遍歷設定屬性、連邊,實作圖物件的初始化。這里借鑒了一些 ......

    uj5u.com 2020-09-10 05:36:06 more
  • 談談我對C# 多型的理解

    面向物件三要素:封裝、繼承、多型。 封裝和繼承,這兩個比較好理解,但要理解多型的話,可就稍微有點難度了。今天,我們就來講講多型的理解。 我們應該經常會看到面試題目:請談談對多型的理解。 其實呢,多型非常簡單,就一句話:呼叫同一種方法產生了不同的結果。 具體實作方式有三種。 一、多載 多載很簡單。 p ......

    uj5u.com 2020-09-10 05:36:09 more
  • Python 資料驅動工具:DDT

    背景 python 的unittest 沒有自帶資料驅動功能。 所以如果使用unittest,同時又想使用資料驅動,那么就可以使用DDT來完成。 DDT是 “Data-Driven Tests”的縮寫。 資料:http://ddt.readthedocs.io/en/latest/ 使用方法 dd. ......

    uj5u.com 2020-09-10 05:36:13 more
  • Python里面的xlrd模塊詳解

    那我就一下面積個問題對xlrd模塊進行學習一下: 1.什么是xlrd模塊? 2.為什么使用xlrd模塊? 3.怎樣使用xlrd模塊? 1.什么是xlrd模塊? ?python操作excel主要用到xlrd和xlwt這兩個庫,即xlrd是讀excel,xlwt是寫excel的庫。 今天就先來說一下xl ......

    uj5u.com 2020-09-10 05:36:28 more
  • 當我們創建HashMap時,底層到底做了什么?

    jdk1.7中的底層實作程序(底層基于陣列+鏈表) 在我們new HashMap()時,底層創建了默認長度為16的一維陣列Entry[ ] table。當我們呼叫map.put(key1,value1)方法向HashMap里添加資料的時候: 首先,呼叫key1所在類的hashCode()計算key1 ......

    uj5u.com 2020-09-10 05:36:38 more
最新发布
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:20:47 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:20:25 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:20:17 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:20:10 more
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:19:44 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:19:07 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:18:57 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:18:49 more
  • 05單件模式

    #經典的單件模式 public class Singleton { private static Singleton uniqueInstance; //一個靜態變數持有Singleton類的唯一實體。 // 其他有用的實體變數寫在這里 //構造器宣告為私有,只有Singleton可以實體化這個類! ......

    uj5u.com 2023-04-19 08:42:51 more
  • 【架構與設計】常見微服務分層架構的區別和落地實踐

    軟體工程的方方面面都遵循一個最基本的道理:沒有銀彈,架構分層模型更是如此,每一種都有各自優缺點,所以請根據不同的業務場景,并遵循簡單、可演進這兩個重要的架構原則選擇合適的架構分層模型即可。 ......

    uj5u.com 2023-04-19 08:42:41 more