主頁 > 後端開發 > Tomcat 調優之從 Linux 內核原始碼層面看 Tcp backlog

Tomcat 調優之從 Linux 內核原始碼層面看 Tcp backlog

2022-10-19 12:02:08 後端開發

前兩天看到一群里在討論 Tomcat 引數調優,看到不止一個人說通過 accept-count 來配置執行緒池大小,我笑了笑,看來其實很多人并不太了解我們用的最多的 WebServer Tomcat,這篇文章就來聊下 Tomcat 調優,重點介紹下執行緒池調優及 TCP 半連接、全連接佇列調優

Tomcat 執行緒池

先來說下執行緒池調優,就拿 SpringBoot 內置的 Tomcat 來說,確實是支持執行緒池引數配置的,但不是 accept-count 引數,可以通過 threads.max 和 threads.minSpare 來配置執行緒池最大執行緒數和核心執行緒數,

如果沒有設定,則會使用默認值

threads.max: 200
threads.minSpare: 10

Tomcat 底層用到的 ThreadPoolExecutor 也不是 JUC 原生的執行緒池,而是自定義的,做了一些調整來支持 IO 密集型場景使用,具體介紹可以看之前寫的兩篇文章,

動態執行緒池(DynamicTp),動態調整 Tomcat、Jetty、Undertow 執行緒池引數篇

以面試官視角萬字解讀執行緒池 10 大經典面試題!

通過這兩篇文章能了解到 Tomcat 自定義執行緒池的執行流程及原理,然后可以接入動態執行緒池框架 DynamicTp,將 Tomcat 執行緒池交由 DynamicTp 管理,使之能享受到動態調參、監控告警的功能,

在配置中心配置 tomcat 執行緒池核心引數

spring:
  dynamic:
    tp:
      tomcatTp:
        corePoolSize: 100
        maximumPoolSize: 400
        keepAliveTime: 60

Tomcat 執行緒池調優主要思想就是動態化執行緒池引數,上線前通過壓測初步確定一套較優的引數值,上線后通過監控、告警實時感知執行緒池負載情況,動態調整引數適應流量的變化,

執行緒池調優就說這些吧,下面主要介紹下 Tcp backlog 及半連接、全連接佇列相關內容,

劃重點

  1. threads.max 和 threads.minSpare 是用來配置 Tomcat 的作業執行緒池大小的,是執行緒池維度的引數

  2. accept-count 和 max-connections 是 TCP 維度的配置引數

TCP 狀態機

Client 端和 Server 端基于 TCP 協議進行通信時,首先需要經過三次握手建連的,通信結束時需要通過四次揮手斷連的,注意所謂的連接其實是個邏輯上的概念,并不存在真實連接的,那 TCP 是怎么面向連接傳輸的呢?

TCP 定義了個復雜的有限狀態機模型,通信雙方通過維護一個連接狀態,來達到看起來像有一條連接的效果,如下是 TCP 狀態機狀態流轉圖,這個圖非常重要,建議大家一定要掌握,圖片來自 TCP 狀態機

  1. 圖上半部分描述了三次握手建立連接程序中狀態的變化

  2. 圖下半部分描述了四次揮手斷開連接程序中狀態的變化

1.TCP協議的狀態機

圖 2 是通過三次握手建立連接的程序,老八股文了,建議結合圖 1 狀態機變化圖看,圖片來源三次握手

2.TCP建立連接程序

圖 3 是通過四次揮手斷開連接的程序,建議結合圖 1 狀態機變化圖看,圖片來源四次揮手

3.TCP斷開連接程序

服務端程式呼叫 listen() 函式后,TCP 狀態機從 CLOSED 轉變為 LISTEN,并且 linux 內核會創建維護兩個佇列,一個是半連接佇列(Syn queue),另一個是全連接佇列(Accept queue),

建連主要流程如下:

  1. 客戶端向服務端發送 SYN 包請求建立連接,發送后客戶端進入 SYN_SENT 狀態

  2. 服務端收到客戶端的 SYN 請求,將該連接存放到半連接佇列(Syn queue)中,并向客戶端回復 SYN + ACK,隨后服務端進入 SYN_RECV 狀態

  3. 客戶端收到服務端的 SYN + ACK 后,回復服務端 ACK 并進入 ESTABLISHED 狀態

  4. 服務端收到客戶端的 ACK 后,從半連接佇列中取出連接放到全連接佇列(Accept queue)中,服務端進入 ESTABLISHED 狀態

  5. 服務端程式呼叫 accept() 方法,從全連接佇列中取出連接進行處理請求

連接佇列大小

上述提到了半連接佇列、全連接佇列,這兩佇列都有大小限制的,超過的連接會被丟掉或者回傳 RST 包,

半連接佇列大小主要受:listen backlog、somaxconn、tcp_max_syn_backlog 這三引數影響

全連接佇列大小主要受:listen backlog 和 somaxconn 這兩引數影響

tcp_max_syn_backlog 和 somaxconn 都是 linux 內核引數,在 /proc/sys/net/ipv4/ 和 /proc/sys/net/core/ 下,可以通過 /etc/sysctl.conf 檔案來修改,默認值為 128,

listen backlog 引數其實就是我們呼叫 listen 函式時傳入的第二個引數,回到主題,Tomcat 的 accept-count 其實最后就會傳給 listen 函式做 backlog 用,

int listen(int sockfd, int backlog);

可以在組態檔中配置 tomcat accept-count 大小,默認為 100

以下代碼注釋中也注明了 acceptCount 就是 backlog

以 Nio2Endpoint 為例看下代碼,bind 方法首先會根據配置的核心執行緒數、最大執行緒數創建 worker 執行緒池,然后呼叫 jdk nio2 中的 AsynchronousServerSocketChannelImpl 的 bind 方法,該方法內會呼叫 Net.listen() 進行 socket 監聽,通過這幾段代碼,我們可以清晰的看到 Tomcat accept-count = Tcp backlog,默認值為 100,

上面說到了半全兩個連接佇列,至于這兩個連接佇列大小怎么確定,其實不同 linux 內核版本演算法也都不太一樣,我們就以 v3.10 來看,

以下是 linux 內核 socket.c 中的原始碼,也就是我們呼叫 listen() 函式會執行的代碼

/*
 * Perform a listen. Basically, we allow the protocol to do anything
 * necessary for a listen, and if that works, we mark the socket as
 * ready for listening.
 */
SYSCALL_DEFINE2(listen, int, fd, int, backlog)
{
    struct socket *sock;
    int err, fput_needed;
    int somaxconn;

    sock = sockfd_lookup_light(fd, &err, &fput_needed);
    if (sock) {
            somaxconn = sock_net(sock->sk)->core.sysctl_somaxconn;
            if ((unsigned int)backlog > somaxconn)
                    backlog = somaxconn;

            err = security_socket_listen(sock, backlog);
            if (!err)
                    err = sock->ops->listen(sock, backlog);

            fput_light(sock->file, fput_needed);
    }
    return err;
}

可以看到,此處會拿內核引數 somaxconn 和 傳入的 backlog 做比較,取二者中的較小者作為全連接佇列大小,

全連接佇列大小 = min(backlog, somaxconn),

接下來 backlog 會依次傳遞給如下函式,格式約定(源代碼檔案名#函式名)

af_inet.c#inet_listen() -> inet_connection_sock.c#inet_csk_listen_start() -> request_sock.c#reqsk_queue_alloc()

reqsk_queue_alloc() 函式代碼如下,主要就是用來計算半連接佇列大小的,

計算邏輯可以簡化為下述公式,簡單描述 roundup_pow_of_two 演算法就是向上取最接近的最大 2 的指數次冪,注意此處 backlog 已經是 min(backlog, somaxconn)

半連接佇列大小 = roundup_pow_of_two(max(8, min(backlog, tcp_max_syn_backlog))+1)

代碼里 max_qlen_log 在一個 for 回圈里計算,比如算出的半連接佇列大小 nr_table_entries = 16 = 2^4,那么 max_qlen_log = 4,該值在判斷半連接佇列是否溢位時會用到,

舉個例子,如果 listen backlog = 10、somaxconn = 128、tcp_max_syn_backlog = 128,那么半連接佇列大小 = 16,全連接佇列大小 = 10,

所以要知道,在做連接佇列大小調優的時候,一定要綜合上述三個引數,只修改某一個起不到想要的效果,

連接佇列大小查看

全連接佇列大小

可以通過 linux 提供的 ss 命令來查看全連接佇列的大小

引數說明,引數很多,其他引數可以自己 help 查看說明

l:表示顯示 listening 狀態的 socket

n:不決議服務名稱

t:只顯示 tcp sockets

這個命令結果怎么解讀呢?

主要看前三個欄位,Recv-Q 和 Send-Q 在 State 為 LISTEN 和非 LISTEN 狀態時代表不同的含義,

State: LISTEN

Recv-Q: 全連接佇列的當前長度,也就是已經完成三次握手等待服務端呼叫 accept() 方法獲取的連接數量

Send-Q: 全連接佇列的最大長度,也就是我們上述分析的 backlog 和somaxconn 的最小值

State: 非 LISTEN

Recv-Q: 已接受但未被應用行程讀取的位元組數

Send-Q: 已發送但未收到確認的位元組數

以上區別從如下內核代碼也可以看出,ss 命令就是從 tcp_diag 模塊獲取的資料

半連接佇列大小

半連接佇列沒有像 ss 這種命令直接查看,但服務端處于 SYN_RECV 狀態的連接都在半連接佇列里,所以可以通過如下命令間接統計

netstat -natp | grep SYN_RECV | wc -l

半連接佇列最大長度可以使用我們上述分析得到的公式計算得到

半全連接佇列溢位

全連接佇列溢位

當請求量很大,全連接佇列比較小時,就有可能發生全連接佇列溢位的情況,

此代碼是 linux 內核用來判斷全連接佇列是否已滿的函式,可以看到判斷用的是大于號,這也就是我們用 ss 命令可能會看到 Recv-Q > Send-Q 的原因

  1. sk_ack_backlog 是當前全連接佇列的大小

  2. sk_max_ack_backlog 是全連接佇列的最大長度,也就是 min(listen_backlog, somaxconn)

當全連接佇列滿了發生溢位時,會根據 /proc/sys/net/ipv4/tcp_abort_on_overflow 內核引數來決定怎么處理后續的 ack 請求,tcp_abort_on_overflow 默認值為 0,

  1. 當 tcp_abort_on_overflow = 0 時,如果全連接佇列已滿,服務端會直接扔掉客戶端發送的 ACK,此時服務端處于 SYN_RECV 狀態,客戶端處于 ESTABLISHED 狀態,服務端的超時重傳定時器會重傳 SYN + ACK 包給客戶端(重傳次數由/proc/sys/net/ipv4/tcp_synack_retries 指定,默認值為 5,重試間隔為 1s、2s、4s、8s、16s,共 31s,第 5 次發出后還要等 32s 才知道第 5 次也超時了,所以總共需要 63s),超過 tcp_synack_retries 后,服務端不會在重傳,這時如果客戶端發送資料過來,服務端會回傳 RST 包,客戶端會報 connection reset by peer 例外

  2. 當 tcp_abort_on_overflow = 1 時,如果全連接佇列已滿,服務端收到客戶端的 ACK 后,會發送一個 RST 包給客戶端,表示結束掉這個握手程序和這個連接,客戶端會報 connection reset by peer 例外

一般情況下 tcp_abort_on_overflow 保持默認值 0 就行,能提高建立連接的成功率

半連接佇列溢位

我們知道,服務端收到客戶端發送的 SYN 包后會將該連接放入半連接佇列中,然后回復 SYN+ACK,如果客戶端一直不回復 ACK 做第三次握手,這樣就會使得服務端有大量處于 SYN_RECV 狀態的 TCP 連接存在半連接佇列里,超過設定的佇列長度后就會發生溢位,

下述代碼是 linux 內核判斷是否發生半連接佇列溢位的函式

// 代碼在 include/net/inet_connection_sock.h 中
static inline int inet_csk_reqsk_queue_is_full(const struct sock *sk)
{
    return reqsk_queue_is_full(&inet_csk(sk)->icsk_accept_queue);
}

// 代碼在 include/net/request_sock.h 中
static inline int reqsk_queue_is_full(const struct request_sock_queue *queue)
{
   /*
    * qlen 是當前半連接佇列大小
    * max_qlen_log 上述解釋過,如果半連接佇列大小 = 16 = 2^4,那么該值就是4
    * 非常巧妙的用了移位運行來判斷半連接佇列是否溢位,底層滿滿的都是細節
    */
    return queue->listen_opt->qlen >> queue->listen_opt->max_qlen_log;
}

我們常說的 SYN Flood 洪水攻擊 是一種典型的 DDOS 攻擊,就是利用了這個點,給服務端發送一個 SYN 包后客戶端就下線了,服務端會超時重傳 SYN+ACK 包,上述也說了總共需要 63s 才停止重傳,也就是說服務端需要經過 63s 后才斷開該連接,這樣就會導致半連接佇列快速被耗盡,不能處理正常的請求,

那是怎么防止攻擊的呢?

linux 提供個一個內核引數 /proc/sys/net/ipv4/tcp_syncookies 來應對該攻擊,當半連接佇列滿了且開啟 tcp_syncookies = 1 配置時,服務端在收到 SYN 并回傳 SYN+ACK 后,不將該連接放入半連接佇列,而是根據這個 SYN 包 TCP 頭資訊計算出一個 cookie 值,將這個 cookie 作為第二次握手 SYN+ACK 包的初始序列號 seq 發過去,如果是攻擊者,就不會有回應,如果是正常連接,客戶端回復 ACK 包后,服務端根據頭資訊計算 cookie,與回傳的確認序列號進行比對,如果相同,則是一個正常建立連接,

下述代碼是計算 cookie 的函式,可以看到跟這些欄位有關(源 ip、源埠、目標 ip、目標埠、客戶端 syn 包序列號、時間戳、mssind)

下面看下第一次握手,收到 SYN 包后服務端的處理代碼,代碼太多,簡化提出跟半連接佇列溢位相關代碼

int tcp_v4_conn_request(struct sock *sk, struct sk_buff *skb)
{
   /*
    * 如果半連接佇列已滿,且 tcp_syncookies 未開啟,則直接丟棄該連接
    */
    if (inet_csk_reqsk_queue_is_full(sk) && !isn) {
        want_cookie = tcp_syn_flood_action(sk, skb, "TCP");
        if (!want_cookie)
                goto drop;
    }

   /*
    * 如果全連接佇列已滿,并且沒有重傳 SYN+ACk 包的連接數量大于1,則直接丟棄該連接
    * inet_csk_reqsk_queue_young 獲取沒有重傳 SYN+ACk 包的連接數量
    */
    if (sk_acceptq_is_full(sk) && inet_csk_reqsk_queue_young(sk) > 1) {
        NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_LISTENOVERFLOWS);
        goto drop;
    }

    // 分配 request sock 內核物件
    req = inet_reqsk_alloc(&tcp_request_sock_ops);
    if (!req)
        goto drop;

    if (want_cookie) {
        // 如果開啟了 tcp_syncookies 且半連接佇列已滿,則計算 cookie
        isn = cookie_v4_init_sequence(sk, skb, &req->mss);
        req->cookie_ts = tmp_opt.tstamp_ok;
    } else if (!isn) {
         /* 如果沒有開啟 tcp_syncookies 并且 max_syn_backlog - 半連接佇列當前大小 < max_syn_backlog >> 2,則丟棄該連接 */
        else if (!sysctl_tcp_syncookies &&
                 (sysctl_max_syn_backlog - inet_csk_reqsk_queue_len(sk) <
                  (sysctl_max_syn_backlog >> 2)) &&
                 !tcp_peer_is_proven(req, dst, false)) {
            LIMIT_NETDEBUG(KERN_DEBUG pr_fmt("drop open request from %pI4/%u\n"),
                           &saddr, ntohs(tcp_hdr(skb)->source));
            goto drop_and_release;
        }
        isn = tcp_v4_init_sequence(skb);
    }
    tcp_rsk(req)->snt_isn = isn;
    // 構造 syn+ack 回應包
    skb_synack = tcp_make_synack(sk, dst, req,
        fastopen_cookie_present(&valid_foc) ? &valid_foc : NULL);
    if (likely(!do_fastopen)) {
        int err;
        // 發送 syn+ack 回應包
        err = ip_build_and_send_pkt(skb_synack, sk, ireq->loc_addr,
             ireq->rmt_addr, ireq->opt);
        err = net_xmit_eval(err);
        if (err || want_cookie)
                goto drop_and_free;

        tcp_rsk(req)->snt_synack = tcp_time_stamp;
        tcp_rsk(req)->listener = NULL;
        // 添加到半連接佇列,并且開啟超時重傳定時器
        inet_csk_reqsk_queue_hash_add(sk, req, TCP_TIMEOUT_INIT);
    } else if (tcp_v4_conn_req_fastopen(sk, skb, skb_synack, req))
        goto drop_and_free;
}

查看溢位命令

當連接佇列溢位時,可以通過 netstart -s 命令查詢

  # 表示全連接佇列溢位的次數,累計值
  119005 times the listen queue of a socket overflowed

  # 表示半連接佇列溢位的次數,累計值
  119085 SYNs to LISTEN sockets dropped

如果發現這兩個值一直在增加,就說明發生了佇列溢位,需要看情況調大佇列大小

常用組件 backlog 大小

  1. Redis 默認 backlog = 511

  2. Nginx 默認 backlog = 511

  3. Mysql 默認 backlog = 50

  4. Undertow 默認 backlog = 1000

  5. Tomcat 默認 backlog = 100

總結

這篇文章以 Tomcat 性能調優為切入點,首先簡單講了下 Tomcat 執行緒池調優,然后借 Tomcat 配置引數 accept-count 引出了 Tcp backlog,從 linux 內核原始碼層面詳細講解了下 TCP backlog 引數以及半連接、全連接佇列的相關知識,包括連接佇列大小設定,以及佇列溢位怎么排查,這些東西也是我們服務端開發需要掌握的,在性能調優,問題排查時會有一定的幫助,

個人開源專案

DynamicTp 是一個基于配置中心實作的輕量級動態執行緒池管理工具,主要功能可以總結為動態調參、通知報警、運行監控、三方包執行緒池管理等幾大類,

目前累計 2k star,歡迎大家試用,感謝你的 star,歡迎 pr,業務之余一起給開源貢獻一份力量

官網:https://dynamictp.cn

gitee 地址:https://gitee.com/dromara/dynamic-tp

github 地址:https://github.com/dromara/dynamic-tp

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

標籤:Java

上一篇:扔掉工具類!MyBatis 一個簡單配置搞定加密、解密,不能太方便了~!

下一篇:對cv2矩形的openVINO模型輸出進行故障排除

標籤雲
其他(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)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more