環境資訊中心綜合外電;姜唯 編譯;林大利 審校
本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!
※網頁設計公司推薦不同的風格,搶佔消費者視覺第一線
※Google地圖已可更新顯示潭子電動車充電站設置地點!!
※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益
※別再煩惱如何寫文案,掌握八大原則!
※網頁設計最專業,超強功能平台可客製化
北部有線電視-提供穩定的寬頻光纖上網、高畫質HD數位頻道、第四台電視、數位電視,現在申辦免費體驗3個月"HD99高畫質套餐"
![]() |
證券日報報導,中國工信部副部長辛國斌於今年9月初透露,工信部已啟動停止生產銷售傳統燃油汽車時間表的研究。靠研發生產電池起家的中國民營車企比亞迪董事長王傳福預測,中國2030年起將禁售傳統燃油汽車。而已在中國傳統燃油車做到自主品牌第一的長安汽車總裁朱華榮,更喊出2025年長安汽車將開始全面停止銷售傳統意義的燃油車,實現全系列產品的電氣化的目標。 據了解,德國宣布到2030年、法國宣布到2040年、英國宣布到2040年將禁止銷售傳統柴油車和汽油車,印度也宣布2030年要淘汰全部汽油車和柴油車。如果按照長安汽車2025年停售燃油車的時間表在中國全國執行,則中國將是第一個禁售傳統燃油車的國家。 對於長安汽車所提出的2025年停售傳統燃油車,市場分析,這可能與中國電動汽車百人會理事長陳清泰於9月下旬舉辦的中國電動汽車百人會常州論壇上表態有關,當時陳清泰提到,「最遲到2025年,電動汽車的性價比將達到或超過傳統燃油車」。 陳清泰的表態比王傳福2030年禁售傳統燃油車的表態晚了三天,但卻比王傳福更有分量,因為中國電動汽車百人會是一個與中國政府有著親密關係的非官方機構,機構顧問委員會有一批在職高官,例如科技部部長萬鋼、工信部部長苗圩。陳清泰非官方的表態可能接近中國官方接下來公布的停止生產銷售傳統燃油汽車的時間。 而中國在發展電動汽車方面,隨著政府後期出臺的新能源汽車補貼政策,目前中國新能源汽車市場幾乎被享受優惠政策的自主品牌車企壟斷;加上2018年至2019年「雙積分(車企油耗積分+新能源汽車積分)」制度的展開,中國車企在內有市場、外有政策支持的情況下,可能相較歐美國家先一步步入全面電動汽車時代。 (本文內容由授權使用。首圖來源:public domain CC0)
本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!
※網頁設計公司推薦不同的風格,搶佔消費者視覺第一線
※Google地圖已可更新顯示潭子電動車充電站設置地點!!
※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益
※別再煩惱如何寫文案,掌握八大原則!
※網頁設計最專業,超強功能平台可客製化
![]() |
隨著電動車逐漸興起,充電的問題變得越來越受到關注,當使用者在城市中開著電動車時,究竟該去哪裡尋找充電設施?Electrek 報導指出,倫敦一些地區似乎正試著透過在路燈柱內安裝充電站的計畫,來解決這個問題。
在充電的問題上,北歐國家似乎具有「先天」優勢,為了面臨嚴峻的冬季氣溫,他們原先在街道上就廣泛設有協助汽車引擎啟動的加熱器(block heater),因此也能夠運用同樣系統讓電動車能在街道上進行充電。
其他沒有這麼寒冷的地方就不同了,在沒有類似基礎設施的情況下,一些公司正在思考相關方案來解決這個問題;像是特斯拉就推出了城市專用的充電站,雖然因為功率低使得充電速度較慢,但也不失為市區內的一個選擇。
另一方面,位在倫敦的肯辛頓與切爾西區(RBKC)的行政當局則選擇了不同的做法,他們已經和能源供應商OVO Energy 和近期獲得西門子投資的德國充電公司ubitricity 簽約,要在都市中現有的燈柱內安裝充電站。
之所以做出這項決定,當地的交通委員會主席Cllr Gerard Hargreaves 表示,是因為居民的充電需求正隨著電動車持續增長,但多數人都無法在附近街道的停車處找到充電設施,讓電動車的充電變得難以進行。
Hargreaves 認為,透過在復古路燈內設置充電設施,駕駛人在住家附近就可以直接充電,倫敦的空氣汙染問題也得以緩解。「除此之外,在路燈內設置意味著不需要額外的基礎建設,更具成本效益的同時也不會影響市容。」
肯辛頓與切爾西區目前計畫安裝的是ubitricity 提供的「SimpleSockets」充電系統,最大輸出功率為4.6 kW。
OVO 表示,SimpleSockets 將會設立在付費和非付費停車格附近的路燈內,24 小時提供使用,每度電只需15 便士(約台幣6 元),這讓電動車不僅更為方便,花費也將更貼近一般人生活。
雖然SimpleSockets 每度電收取的費用與該區的電費規定相當,但Electrek 報導也指出,用戶必須每月繳納7.99 英鎊(約台幣320 元)的訂閱費,同時向ubitricity 購買199 英鎊(約台幣7,960 元)的電纜,才能使用這項收費標準。
當然用戶也可以選擇不繳納訂閱費,但使用上還是必須花100 多英鎊(約台幣4,000 元)購買使用的電纜,同時每度電的收費也將提高到19 便士(約台幣7.6 元),只是即使如此,也遠比英國國內的其他充電選擇好上許多。
ubitricity 目前已經開始在肯辛頓與切爾西區內進行安裝,目標在1 月底前要安裝完成50 個在路燈座內的充電裝置。
(合作媒體:。首圖來源: 臉書)
本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!
※網頁設計公司推薦不同的風格,搶佔消費者視覺第一線
※Google地圖已可更新顯示潭子電動車充電站設置地點!!
※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益
※別再煩惱如何寫文案,掌握八大原則!
※網頁設計最專業,超強功能平台可客製化
※聚甘新
tcp服務端和客戶端建立連接後會長時間維持這個連接,用於互相傳遞數據,tcp是以流的方式傳輸數據的,就像一個水管里的水一樣,從一頭不斷的流向另一頭。
理想情況下,發送的數據包都是獨立的,
現實要複雜一些,發送方和接收方都有各自的緩衝區。
發送緩衝區:應用不斷的把數據發送到緩衝區,系統不斷的從緩衝區取數據發送到接收端。
接收緩衝區:系統把接收到的數據放入緩衝區,應用不斷的從緩衝區獲取數據。
當發送方快速的發送多個數據包時,每個數據包都小於緩衝區,tcp會將多次寫入的數據放入緩衝區,一次發送出去,服務器在接收到數據流無法區分哪部分數據包獨立的,這樣產生了粘包。
或者接收方因為各種原因沒有從緩衝區里讀取數據,緩衝區的數據會積壓,等再取出數據時,也是無法區分哪部分數據包獨立的,一樣會產生粘包。
發送方的數據包大於緩存區了,其中有一部分數據會在下一次發送,接收端一次接收到時的數據不是完整的數據,就會出現半包的情況。
我們可以還原一下粘包和半包,寫一個測試代碼
服務端
func main() {
l, err := net.Listen("tcp", ":8899")
if err != nil {
panic(err)
}
fmt.Println("listen to 8899")
for {
conn, err := l.Accept()
if err != nil {
panic(err)
} else {
go handleConn(conn)
}
}
}
func handleConn(conn net.Conn) {
defer conn.Close()
var buf [1024]byte
for {
n, err := conn.Read(buf[:])
if err != nil {
break
} else {
fmt.Printf("recv: %s \n", string(buf[0:n]))
}
}
}
客戶端
func main() {
data := []byte("~測試數據:一二三四五~")
conn, err := net.Dial("tcp", ":8899")
if err != nil {
panic(err)
}
for i := 0; i < 2000; i++ {
if _, err = conn.Write(data); err != nil {
fmt.Printf("write failed , err : %v\n", err)
break
}
}
}
查看一下輸出
recv: ~測試數據:一二三四五~
recv: ~測試數據:一二三四五~ ~測試數據:一二三四五~
recv: ~測試數據:一�
recv: ��三四五~ ~測試數據:一二三四五~
recv: ~測試數據:一二三四五~
recv: ~測試數據:一二三四五~ ~測試數據:一二三四五~ ~測試數據:一二三四五~ ~測試數據:一二三四五~
recv: ~測試數據:一二三四五~
正常情況下輸出是recv: ~測試數據:一二三四五~,發生粘包的時候會輸出多個數據包,當有半包的情況下輸出的是亂碼數據,再下一次會把剩下的半包數據也輸出。
要解決也簡單的就想辦法確定數據的邊界,常見的處理方式:
個人更推薦數據頭方式來確定數據邊界,在發送和接收數據時做好規定,每個數據包是不定長的,比如4字節的包頭+真實的數據可以根據自己的業務進行擴展,比如上更多的包頭或者包尾,加上數據校驗等。
我修改一下上面的代碼:
客戶端
data := []byte("~測試數據:一二三四五~")
conn, err := net.Dial("tcp", ":8899")
if err != nil {
panic(err)
}
for i := 0; i < 2000; i++ {
var buf [4]byte
bufs := buf[:]
binary.BigEndian.PutUint32(bufs, uint32(len(data)))
if _, err := conn.Write(bufs); err != nil {
fmt.Printf("write failed , err : %v\n", err)
break
}
if _, err = conn.Write(data); err != nil {
fmt.Printf("write failed , err : %v\n", err)
break
}
}
服務端
func main() {
l, err := net.Listen("tcp", ":8899")
if err != nil {
panic(err)
}
fmt.Println("listen to 8899")
for {
conn, err := l.Accept()
if err != nil {
panic(err)
} else {
go handleConn(conn)
}
}
}
func handleConn(conn net.Conn) {
defer conn.Close()
for {
var msgSize int32
err := binary.Read(conn, binary.BigEndian, &msgSize)
if err != nil {
break
}
buf := make([]byte, msgSize)
_, err = io.ReadFull(conn, buf)
if err != nil {
break
}
fmt.Printf("recv: %s \n", string(buf))
}
}
執行再看一下輸出,沒有粘包或者半包的情況
recv: ~測試數據:一二三四五~
recv: ~測試數據:一二三四五~
recv: ~測試數據:一二三四五~
recv: ~測試數據:一二三四五~
recv: ~測試數據:一二三四五~
recv: ~測試數據:一二三四五~
也可以像第一個例子一樣用一個指定大小的buf var buf [1024]byte,每次從conn里取出指定大小的數據,然後進行數據解析,如果發現有半包的情況,就再讀取一次,加上上次未解析的數據,再次重新解析。
本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!
※網頁設計公司推薦不同的風格,搶佔消費者視覺第一線
※Google地圖已可更新顯示潭子電動車充電站設置地點!!
※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益
※別再煩惱如何寫文案,掌握八大原則!
※網頁設計最專業,超強功能平台可客製化
※聚甘新
簡單集成Prometheus+Grafana,指標的上報收集可視化。
Prometheus是一個監控平台,監控從HTTP端口收集受監控目標的指標。在微服務的架構里Prometheus多維度的數據收集是非常強大的 我們首先下載安裝Prometheus和node_exporter,node_exporter用於監控CPU、內存、磁盤、I/O等信息
下載完成后解壓以管理員運行 prometheus.exe 訪問 http://localhost:9090/ 出現一下頁面說明啟動成功啦
有了Prometheus,我們還需要給Prometheus提供獲取監控數據的接口,我們新建一個WebApi項目,並導入prometheus-net.AspNetCore包,在Configure中加入UseMetricServer中間件
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseMetricServer();
}
啟動項目訪問http://localhost:5000/metrics就可以看基本的一些監控信息啦,包括線程數,句柄數,3個GC的回收計數等信息。
# HELP process_num_threads Total number of threads
# TYPE process_num_threads gauge
process_num_threads 29
# HELP process_working_set_bytes Process working set
# TYPE process_working_set_bytes gauge
process_working_set_bytes 44441600
# HELP process_private_memory_bytes Process private memory size
# TYPE process_private_memory_bytes gauge
process_private_memory_bytes 69660672
# HELP dotnet_total_memory_bytes Total known allocated memory
# TYPE dotnet_total_memory_bytes gauge
dotnet_total_memory_bytes 2464584
# HELP dotnet_collection_count_total GC collection count
# TYPE dotnet_collection_count_total counter
dotnet_collection_count_total{generation="1"} 0
dotnet_collection_count_total{generation="0"} 0
dotnet_collection_count_total{generation="2"} 0
# HELP process_start_time_seconds Start time of the process since unix epoch in seconds.
# TYPE process_start_time_seconds gauge
process_start_time_seconds 1592448124.2853072
# HELP process_open_handles Number of open handles
# TYPE process_open_handles gauge
process_open_handles 413
# HELP process_virtual_memory_bytes Virtual memory size in bytes.
# TYPE process_virtual_memory_bytes gauge
process_virtual_memory_bytes 2225187631104
# HELP process_cpu_seconds_total Total user and system CPU time spent in seconds.
# TYPE process_cpu_seconds_total counter
process_cpu_seconds_total 1.171875
Help 是收集指標的說明,Type收集指標的類型
但是作為HTTP應用怎麼能沒有HTTP的監控和計數呢,只需要加加入UseHttpMetrics中間件就可以對HTTP請求監控和計數,主要注意的是UseHttpMetrics最好放在UseEndpoints和UseRouting中間
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseMetricServer();
app.UseRouting();
app.UseHttpMetrics();
app.UseEndpoints(endpoints => { endpoints.MapControllers(); });
}
啟動項目繼續訪問http://localhost:5000/metrics
# HELP http_requests_in_progress The number of requests currently in progress in the ASP.NET Core pipeline. One series without controller/action label values counts all in-progress requests, with separate series existing for each controller-action pair.
# TYPE http_requests_in_progress gauge
可以看到已經有了,我們隨便請求一下服務看看效果,會幫我們記錄下總耗時,總請求數,和每次請求的耗時數
但是單單有上面那些數據好像還不太好定位一下很奇葩的問題,這時候我們可以獲取Runtime的一些數據,方法童謠很簡單。導入prometheus-net.DotNetRuntime 包,它可以幫助我們看到如下指標
我們只需要在Program的Main方法中啟動收集器就可以啦。
public static void Main(string[] args)
{
DotNetRuntimeStatsBuilder.Default().StartCollecting();
CreateHostBuilder(args).Build().Run();
}
啟動項目繼續訪問http://localhost:5000/metrics測試一下
# HELP dotnet_collection_count_total GC collection count
# TYPE dotnet_collection_count_total counter
dotnet_collection_count_total{generation="1"} 0
dotnet_collection_count_total{generation="0"} 0
dotnet_collection_count_total{generation="2"} 0
# HELP process_private_memory_bytes Process private memory size
# TYPE process_private_memory_bytes gauge
process_private_memory_bytes 75141120
# HELP dotnet_gc_pause_ratio The percentage of time the process spent paused for garbage collection
# TYPE dotnet_gc_pause_ratio gauge
dotnet_gc_pause_ratio 0
# HELP http_requests_received_total Provides the count of HTTP requests that have been processed by the ASP.NET Core pipeline.
# TYPE http_requests_received_total counter
# HELP dotnet_gc_collection_seconds The amount of time spent running garbage collections
# TYPE dotnet_gc_collection_seconds histogram
dotnet_gc_collection_seconds_sum 0
dotnet_gc_collection_seconds_count 0
dotnet_gc_collection_seconds_bucket{le="0.001"} 0
dotnet_gc_collection_seconds_bucket{le="0.01"} 0
dotnet_gc_collection_seconds_bucket{le="0.05"} 0
dotnet_gc_collection_seconds_bucket{le="0.1"} 0
dotnet_gc_collection_seconds_bucket{le="0.5"} 0
dotnet_gc_collection_seconds_bucket{le="1"} 0
dotnet_gc_collection_seconds_bucket{le="10"} 0
dotnet_gc_collection_seconds_bucket{le="+Inf"} 0
# HELP dotnet_total_memory_bytes Total known allocated memory
# TYPE dotnet_total_memory_bytes gauge
dotnet_total_memory_bytes 4925936
# HELP dotnet_threadpool_num_threads The number of active threads in the thread pool
# TYPE dotnet_threadpool_num_threads gauge
dotnet_threadpool_num_threads 0
# HELP dotnet_threadpool_scheduling_delay_seconds A breakdown of the latency experienced between an item being scheduled for execution on the thread pool and it starting execution.
# TYPE dotnet_threadpool_scheduling_delay_seconds histogram
dotnet_threadpool_scheduling_delay_seconds_sum 0.015556
dotnet_threadpool_scheduling_delay_seconds_count 10
dotnet_threadpool_scheduling_delay_seconds_bucket{le="0.001"} 0
dotnet_threadpool_scheduling_delay_seconds_bucket{le="0.01"} 10
dotnet_threadpool_scheduling_delay_seconds_bucket{le="0.05"} 10
dotnet_threadpool_scheduling_delay_seconds_bucket{le="0.1"} 10
dotnet_threadpool_scheduling_delay_seconds_bucket{le="0.5"} 10
dotnet_threadpool_scheduling_delay_seconds_bucket{le="1"} 10
dotnet_threadpool_scheduling_delay_seconds_bucket{le="10"} 10
dotnet_threadpool_scheduling_delay_seconds_bucket{le="+Inf"} 10
# HELP process_working_set_bytes Process working set
# TYPE process_working_set_bytes gauge
process_working_set_bytes 50892800
# HELP process_num_threads Total number of threads
# TYPE process_num_threads gauge
process_num_threads 32
# HELP dotnet_jit_method_seconds_total Total number of seconds spent in the JIT compiler
# TYPE dotnet_jit_method_seconds_total counter
dotnet_jit_method_seconds_total 0
dotnet_jit_method_seconds_total{dynamic="false"} 0.44558800000000004
dotnet_jit_method_seconds_total{dynamic="true"} 0.004122000000000001
# HELP dotnet_gc_pinned_objects The number of pinned objects
# TYPE dotnet_gc_pinned_objects gauge
dotnet_gc_pinned_objects 0
# HELP process_start_time_seconds Start time of the process since unix epoch in seconds.
# TYPE process_start_time_seconds gauge
process_start_time_seconds 1592449942.6063592
# HELP dotnet_gc_heap_size_bytes The current size of all heaps (only updated after a garbage collection)
# TYPE dotnet_gc_heap_size_bytes gauge
# HELP http_request_duration_seconds The duration of HTTP requests processed by an ASP.NET Core application.
# TYPE http_request_duration_seconds histogram
# HELP dotnet_contention_seconds_total The total amount of time spent contending locks
# TYPE dotnet_contention_seconds_total counter
dotnet_contention_seconds_total 0
# HELP dotnet_gc_pause_seconds The amount of time execution was paused for garbage collection
# TYPE dotnet_gc_pause_seconds histogram
dotnet_gc_pause_seconds_sum 0
dotnet_gc_pause_seconds_count 0
dotnet_gc_pause_seconds_bucket{le="0.001"} 0
dotnet_gc_pause_seconds_bucket{le="0.01"} 0
dotnet_gc_pause_seconds_bucket{le="0.05"} 0
dotnet_gc_pause_seconds_bucket{le="0.1"} 0
dotnet_gc_pause_seconds_bucket{le="0.5"} 0
dotnet_gc_pause_seconds_bucket{le="1"} 0
dotnet_gc_pause_seconds_bucket{le="10"} 0
dotnet_gc_pause_seconds_bucket{le="+Inf"} 0
# HELP process_virtual_memory_bytes Virtual memory size in bytes.
# TYPE process_virtual_memory_bytes gauge
process_virtual_memory_bytes 2225201872896
# HELP dotnet_gc_finalization_queue_length The number of objects waiting to be finalized
# TYPE dotnet_gc_finalization_queue_length gauge
dotnet_gc_finalization_queue_length 0
# HELP dotnet_threadpool_io_num_threads The number of active threads in the IO thread pool
# TYPE dotnet_threadpool_io_num_threads gauge
dotnet_threadpool_io_num_threads 3
# HELP process_open_handles Number of open handles
# TYPE process_open_handles gauge
process_open_handles 436
# HELP dotnet_gc_collection_reasons_total A tally of all the reasons that lead to garbage collections being run
# TYPE dotnet_gc_collection_reasons_total counter
# HELP process_cpu_seconds_total Total user and system CPU time spent in seconds.
# TYPE process_cpu_seconds_total counter
process_cpu_seconds_total 0.890625
# HELP http_requests_in_progress The number of requests currently in progress in the ASP.NET Core pipeline. One series without controller/action label values counts all in-progress requests, with separate series existing for each controller-action pair.
# TYPE http_requests_in_progress gauge
# HELP dotnet_threadpool_adjustments_total The total number of changes made to the size of the thread pool, labeled by the reason for change
# TYPE dotnet_threadpool_adjustments_total counter
# HELP dotnet_jit_cpu_ratio The amount of total CPU time consumed spent JIT'ing
# TYPE dotnet_jit_cpu_ratio gauge
dotnet_jit_cpu_ratio 0.5728901224489797
# HELP process_cpu_count The number of processor cores available to this process.
# TYPE process_cpu_count gauge
process_cpu_count 8
# HELP dotnet_build_info Build information about prometheus-net.DotNetRuntime and the environment
# TYPE dotnet_build_info gauge
dotnet_build_info{version="3.3.1.0",target_framework=".NETCoreApp,Version=v5.0",runtime_version=".NET Core 5.0.0-preview.2.20160.6",os_version="Microsoft Windows 10.0.18363",process_architecture="X64"} 1
# HELP dotnet_jit_method_total Total number of methods compiled by the JIT compiler
# TYPE dotnet_jit_method_total counter
dotnet_jit_method_total{dynamic="false"} 830
dotnet_jit_method_total{dynamic="true"} 30
# HELP dotnet_gc_cpu_ratio The percentage of process CPU time spent running garbage collections
# TYPE dotnet_gc_cpu_ratio gauge
dotnet_gc_cpu_ratio 0
# HELP dotnet_threadpool_scheduled_total The total number of items the thread pool has been instructed to execute
# TYPE dotnet_threadpool_scheduled_total counter
dotnet_threadpool_scheduled_total 16
# HELP dotnet_gc_allocated_bytes_total The total number of bytes allocated on the small and large object heaps (updated every 100KB of allocations)
# TYPE dotnet_gc_allocated_bytes_total counter
dotnet_gc_allocated_bytes_total{gc_heap="soh"} 3008088
dotnet_gc_allocated_bytes_total{gc_heap="loh"} 805392
# HELP dotnet_contention_total The number of locks contended
# TYPE dotnet_contention_total counter
dotnet_contention_total 0
可以看到非常多的信息啦,但是我們有時候不需要這麼多指標也可以自定義。
public static void Main(string[] args)
{
DotNetRuntimeStatsBuilder
.Customize()
.WithContentionStats()
.WithJitStats()
.WithThreadPoolSchedulingStats()
.WithThreadPoolStats()
.WithGcStats()
.StartCollecting();
CreateHostBuilder(args).Build().Run();
}
JIT,GC和線程的監控是會影響到一點點性能,我們可以通過sampleRate這個枚舉的值來控制採樣頻率
public static void Main(string[] args)
{
DotNetRuntimeStatsBuilder
.Customize()
//每5個事件個採集一個
.WithContentionStats(sampleRate: SampleEvery.FiveEvents)
//每10事件採集一個
.WithJitStats(sampleRate: SampleEvery.TenEvents)
//每100事件採集一個
.WithThreadPoolSchedulingStats(sampleRate: SampleEvery.HundredEvents)
.WithThreadPoolStats()
.WithGcStats()
.StartCollecting();
CreateHostBuilder(args).Build().Run();
}
有了這些指標我們需要Prometheus來收集我們Api的指標,只需要修改prometheus.yml文件然後重啟Prometheus就可以了。
scrape_configs:
- job_name: mydemo
scrape_interval: 15s
scrape_timeout: 10s
metrics_path: /metrics
scheme: http
static_configs:
- targets:
- localhost:5000
啟動Api項目和Prometheus,選中dotnet_collection_count_total點擊Excute可以看到Api的指標是正常上報的。
Prometheus有了數據我們就需要一個炫酷的UI去展示上報的數據啦。
Prometheus有了數據就差一個漂亮的UI來展示的我們的指標了。Grafana是一個Go編寫的開源應用,用於把指標數據可視化。是當下流行的時序數據展示工具。先下載,直接下載exe安裝,完成后能打開http://localhost:3000/頁面就安裝成功了
先添加數據源,選擇Prometheus為數據源,並配置。
添加儀錶盤
在Import via panel json中加入下面這個json,點擊load,
選擇數據源,點擊Import就能看到儀錶盤了
還可以去這裏添加很多現有的儀錶盤。複製ID添加儀錶盤。
prometheus-net
.NetCore下使用Prometheus實現系統監控和警報系列
本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!
※網頁設計公司推薦不同的風格,搶佔消費者視覺第一線
※Google地圖已可更新顯示潭子電動車充電站設置地點!!
※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益
※別再煩惱如何寫文案,掌握八大原則!
※網頁設計最專業,超強功能平台可客製化
※聚甘新
摘錄自2018年9月10日自由時報報導
日本將在今年的國際捕鯨委員會(IWC)會議上,提議取消32年前制定的商業捕鯨禁令,希望建立「可持續捕鯨委員會」為商業捕鯨國家制定捕撈配額。保育人士憤怒表示,日本企圖公然推翻一直以來維持的狩獵禁令,若通過日本的提議,會導制危險的商業捕鯨行為。
《法新社》報導,為期一週的國際捕鯨委員會第67屆會議本週一(10日)在巴西弗洛里亞諾波利斯(Florianopolis)舉行,日本將提議廢止捕鯨禁令,稱小鬚鯨和其他鯨魚等總量已經恢復,建議IWC認同將恢復數量的物種訂定新的捕撈配額,建立「可持續捕鯨委員會」,替允許國民商業捕鯨的國家制定相關配額。日本還指出,實施捕鯨禁令不只是為了保育,也是為了再度開放捕鯨。
英國國際人道協會(HSI UK)也說,如果日本提案通過,將再次開放捕鯨季節,這會是幾十年來,最危險和魯莽企圖的商業捕鯨行為。
本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!
※網頁設計公司推薦不同的風格,搶佔消費者視覺第一線
※Google地圖已可更新顯示潭子電動車充電站設置地點!!
※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益
※別再煩惱如何寫文案,掌握八大原則!
※網頁設計最專業,超強功能平台可客製化
※聚甘新
日經新聞3日報導,日本汽車零組件商久野金屬工業(Kuno Kinzoku Industry)計畫將使用於電動車(EV)、插電式油電混合車(PHV)的馬達相關零件「馬達殼(motor housing、見附圖)」產能提高至現行的約50倍水準,主因全球各地對環保規範日益嚴苛,帶動EV/PHV今後料急速普及。馬達殼為圓筒狀的鐵製外殼,用於覆蓋住作為EV動力的大型馬達、並使其固定。
報導指出,久野位於日本常滑市的本社工廠內目前僅擁有一座生產馬達殼的設備、且最近已處於產能全開狀態,因此久野計畫投資約4億日圓、擴增設備,將月產能從現行的7,000-8,000個最高擴增至40萬個的規模。久野社長久野忠博表示,「來自顧客端的尋單持續增加」。
久野設立於1947年,於2010年左右開始供應大型馬達殼給汽車大廠使用,目前在馬達殼市場上握有高市佔率。
根據日本市調機構富士經濟(Fuji Keizai)公布的調查報告顯示,PHV、EV在2025年以後的需求增幅將加快,預估2030年左右時,油電混合車(HV)、PHV、EV將呈現幾乎相互抗衡的局面,2035年在北美、歐洲、中國需求加持下,PHV、EV市場將進一步擴大,超越HV。
富士經濟指出,全球EV市場當前將持續呈現緩和增長,不過預估自2020年左右起,EV需求將呈現急速擴大,預估2035年全球EV市場規模將達567萬台、將較2015年飆增近16倍(成長1,567%);另外,2035年全球PHV市場規模將達665萬台、將較2015年(21萬台)飆增近31倍(成長3,066%)。
(本文內容由 授權轉載)
本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!
※網頁設計公司推薦不同的風格,搶佔消費者視覺第一線
※Google地圖已可更新顯示潭子電動車充電站設置地點!!
※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益
※別再煩惱如何寫文案,掌握八大原則!
※網頁設計最專業,超強功能平台可客製化
※聚甘新
回顧:認證方案之初步認識JWT
在現代Web應用程序中,即分為前端與後端兩大部分。當前前後端的趨勢日益劇增,前端設備(手機、平板、電腦、及其他設備)層出不窮。因此,為了方便滿足前端設備與後端進行通訊,就必須有一種統一的機制。所以導致API架構的流行。而RESTful API這個API設計思想理論也就成為目前互聯網應用程序比較歡迎的一套方式。
這種API架構思想的引入,因此,我們就需要考慮用一種標準的,通用的,無狀態的,與語言無關的身份認證方式來實現API接口的認證。
HTTP提供了一套標準的身份驗證框架:服務端可以用來針對客戶端的請求發送質詢(challenge),客戶端根據質詢提供應答身份驗證憑證。
質詢與應答的工作流程如下:服務端向客戶端返回401(Unauthorized,未授權)狀態碼,並在WWW-Authenticate頭中添加如何進行驗證的信息,其中至少包含有一種質詢方式。然後客戶端可以在請求中添加Authorization頭進行驗證,其Value為身份驗證的憑證信息。
在本文中,將要介紹的是以Jwt Bearer方式進行認證。
本文要介紹的Bearer驗證也屬於HTTP協議標準驗證,它隨着OAuth協議而開始流行,詳細定義見: RFC 6570。
+--------+ +---------------+
| |--(A)- Authorization Request ->| Resource |
| | | Owner |
| |<-(B)-- Authorization Grant ---| |
| | +---------------+
| |
| | +---------------+
| |--(C)-- Authorization Grant -->| Authorization |
| Client | | Server |
| |<-(D)----- Access Token -------| |
| | +---------------+
| |
| | +---------------+
| |--(E)----- Access Token ------>| Resource |
| | | Server |
| |<-(F)--- Protected Resource ---| |
+--------+ +---------------+
A security token with the property that any party in possession of the token (a “bearer”) can use the token in any way that any other party in possession of it can. Using a bearer token does not require a bearer to prove possession of cryptographic key material (proof-of-possession).
因此Bearer認證的核心是Token,Bearer驗證中的憑證稱為BEARER_TOKEN,或者是access_token,它的頒發和驗證完全由我們自己的應用程序來控制,而不依賴於系統和Web服務器,Bearer驗證的標準請求方式如下:
Authorization: Bearer [BEARER_TOKEN]
那麼使用Bearer驗證有什麼好處呢?
302到登錄頁面,這在非瀏覽器情況下很難處理,而Bearer驗證則返回的是標準的401 challenge。上面介紹的Bearer認證,其核心便是BEARER_TOKEN,那麼,如何確保Token的安全是重中之重。一種是通過HTTPS的方式,另一種是通過對Token進行加密編碼簽名,而最流行的Token編碼簽名方式便是:JSON WEB TOKEN。
Json web token (Jwt), 是為了在網絡應用環境間傳遞聲明而執行的一種基於JSON的開放標準(RFC 7519)。該token被設計為緊湊且安全的,特別適用於分佈式站點的單點登錄(SSO)場景。JWT的聲明一般被用來在身份提供者和服務提供者間傳遞被認證的用戶身份信息,以便於從資源服務器獲取資源,也可以增加一些額外的其它業務邏輯所必須的聲明信息,該token也可直接被用於認證,也可被加密。
JWT是由.分割的如下三部分組成:
Header.Payload.Signature
還記得之前說個的一篇認證方案之初步認識JWT嗎?沒有的,可以看看,對JWT的特點和基本原理介紹,可以進一步的了解。
學習了之前的文章后,我們可以發現使用JWT的好處在於通用性、緊湊性和可拓展性。
在這裏,我們用微軟給我們提供的JwtBearer認證方式,實現認證服務註冊 。
引入nuget包:Microsoft.AspNetCore.Authentication.JwtBearer
註冊服務,將服務添加到容器中,
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
var Issurer = "JWTBearer.Auth"; //發行人
var Audience = "api.auth"; //受眾人
var secretCredentials = "q2xiARx$4x3TKqBJ"; //密鑰
//配置認證服務
services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(o=>{
o.TokenValidationParameters = new TokenValidationParameters
{
//是否驗證發行人
ValidateIssuer = true,
ValidIssuer = Issurer,//發行人
//是否驗證受眾人
ValidateAudience = true,
ValidAudience = Audience,//受眾人
//是否驗證密鑰
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(secretCredentials)),
ValidateLifetime = true, //驗證生命周期
RequireExpirationTime = true, //過期時間
};
});
}
注意說明:
一. TokenValidationParameters的參數默認值:
1. ValidateAudience = true, ----- 如果設置為false,則不驗證Audience受眾人
2. ValidateIssuer = true , ----- 如果設置為false,則不驗證Issuer發布人,但建議不建議這樣設置
3. ValidateIssuerSigningKey = false,
4. ValidateLifetime = true, ----- 是否驗證Token有效期,使用當前時間與Token的Claims中的NotBefore和Expires對比
5. RequireExpirationTime = true, ----- 是否要求Token的Claims中必須包含Expires
6. ClockSkew = TimeSpan.FromSeconds(300), ----- 允許服務器時間偏移量300秒,即我們配置的過期時間加上這個允許偏移的時間值,才是真正過期的時間(過期時間 +偏移值)你也可以設置為0,ClockSkew = TimeSpan.Zero
調用方法,配置Http請求管道:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
//1.先開啟認證
app.UseAuthentication();
//2.再開啟授權
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
在JwtBearerOptions的配置中,通常IssuerSigningKey(簽名秘鑰), ValidIssuer(Token頒發機構), ValidAudience(頒發給誰) 三個參數是必須的,后兩者用於與TokenClaims中的Issuer和Audience進行對比,不一致則驗證失敗。
創建一個需要授權保護的資源控制器,這裏我們用建立API生成項目自帶的控制器,WeatherForecastController.cs, 在控制器上使用Authorize即可
[ApiController]
[Route("[controller]")]
[Authorize]
public class WeatherForecastController : ControllerBase
{
private static readonly string[] Summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
private readonly ILogger<WeatherForecastController> _logger;
public WeatherForecastController(ILogger<WeatherForecastController> logger)
{
_logger = logger;
}
[HttpGet]
public IEnumerable<WeatherForecast> Get()
{
var rng = new Random();
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = rng.Next(-20, 55),
Summary = Summaries[rng.Next(Summaries.Length)]
})
.ToArray();
}
}
因為微軟為我們內置了JwtBearer驗證,但是沒有提供Token的發放,所以這裏我們要實現生成Token的方法
引入Nugets包:System.IdentityModel.Tokens.Jwt
這裏我們根據IdentityModel.Tokens.Jwt文檔給我們提供的幫助類,提供了方法WriteToken創建Token,根據參數SecurityToken,可以實例化,JwtSecurityToken,指定可選參數的類。
/// <summary>
/// Initializes a new instance of the <see cref="JwtSecurityToken"/> class specifying optional parameters.
/// </summary>
/// <param name="issuer">If this value is not null, a { iss, 'issuer' } claim will be added, overwriting any 'iss' claim in 'claims' if present.</param>
/// <param name="audience">If this value is not null, a { aud, 'audience' } claim will be added, appending to any 'aud' claims in 'claims' if present.</param>
/// <param name="claims">If this value is not null then for each <see cref="Claim"/> a { 'Claim.Type', 'Claim.Value' } is added. If duplicate claims are found then a { 'Claim.Type', List<object> } will be created to contain the duplicate values.</param>
/// <param name="expires">If expires.HasValue a { exp, 'value' } claim is added, overwriting any 'exp' claim in 'claims' if present.</param>
/// <param name="notBefore">If notbefore.HasValue a { nbf, 'value' } claim is added, overwriting any 'nbf' claim in 'claims' if present.</param>
/// <param name="signingCredentials">The <see cref="SigningCredentials"/> that will be used to sign the <see cref="JwtSecurityToken"/>. See <see cref="JwtHeader(SigningCredentials)"/> for details pertaining to the Header Parameter(s).</param>
/// <exception cref="ArgumentException">If 'expires' <= 'notbefore'.</exception>
public JwtSecurityToken(string issuer = null, string audience = null, IEnumerable<Claim> claims = null, DateTime? notBefore = null, DateTime? expires = null, SigningCredentials signingCredentials = null)
{
if (expires.HasValue && notBefore.HasValue)
{
if (notBefore >= expires)
throw LogHelper.LogExceptionMessage(new ArgumentException(LogHelper.FormatInvariant(LogMessages.IDX12401, expires.Value, notBefore.Value)));
}
Payload = new JwtPayload(issuer, audience, claims, notBefore, expires);
Header = new JwtHeader(signingCredentials);
RawSignature = string.Empty;
}
這樣,我們可以根據參數指定內容:
1. string iss = "JWTBearer.Auth"; // 定義發行人
2. string aud = "api.auth"; //定義受眾人audience
3. IEnumerable<Claim> claims = new Claim[]
{
new Claim(JwtClaimTypes.Id,"1"),
new Claim(JwtClaimTypes.Name,"i3yuan"),
};//定義許多種的聲明Claim,信息存儲部分,Claims的實體一般包含用戶和一些元數據
4. var nbf = DateTime.UtcNow; //notBefore 生效時間
5. var Exp = DateTime.UtcNow.AddSeconds(1000); //expires 過期時間
6. string sign = "q2xiARx$4x3TKqBJ"; //SecurityKey 的長度必須 大於等於 16個字符
var secret = Encoding.UTF8.GetBytes(sign);
var key = new SymmetricSecurityKey(secret);
var signcreds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
好了,通過以上填充參數內容,進行傳參賦值得到,完整代碼如下:
新增AuthController.cs控制器:
[HttpGet]
public IActionResult GetToken()
{
try
{
//定義發行人issuer
string iss = "JWTBearer.Auth";
//定義受眾人audience
string aud = "api.auth";
//定義許多種的聲明Claim,信息存儲部分,Claims的實體一般包含用戶和一些元數據
IEnumerable<Claim> claims = new Claim[]
{
new Claim(JwtClaimTypes.Id,"1"),
new Claim(JwtClaimTypes.Name,"i3yuan"),
};
//notBefore 生效時間
// long nbf =new DateTimeOffset(DateTime.Now).ToUnixTimeSeconds();
var nbf = DateTime.UtcNow;
//expires //過期時間
// long Exp = new DateTimeOffset(DateTime.Now.AddSeconds(1000)).ToUnixTimeSeconds();
var Exp = DateTime.UtcNow.AddSeconds(1000);
//signingCredentials 簽名憑證
string sign = "q2xiARx$4x3TKqBJ"; //SecurityKey 的長度必須 大於等於 16個字符
var secret = Encoding.UTF8.GetBytes(sign);
var key = new SymmetricSecurityKey(secret);
var signcreds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var jwt = new JwtSecurityToken(issuer: iss, audience: aud, claims:claims,notBefore:nbf,expires:Exp, signingCredentials: signcreds);
var JwtHander = new JwtSecurityTokenHandler();
var token = JwtHander.WriteToken(jwt);
return Ok(new
{
access_token = token,
token_type = "Bearer",
});
}
catch (Exception ex)
{
throw;
}
}
注意: 1.SecurityKey 的長度必須 大於等於 16個字符,否則生成會報錯。(可通過在線隨機生成密鑰)
訪問獲取Token方法,獲取得到access_token:
再訪問,授權資源接口,可以發現,再沒有添加請求頭token值的情況下,返回了401沒有權限。
這次,在請求頭通過Authorization加上之前獲取的token值后,再次進行訪問,發現已經可以獲取訪問資源控制器,並返回對應的數據。
在HTTP標準驗證方案中,我們比較熟悉的是”Basic”和”Digest”,前者將用戶名密碼使用BASE64編碼後作為驗證憑證,後者是Basic的升級版,更加安全,因為Basic是明文傳輸密碼信息,而Digest是加密後傳輸。
Basic認證是一種較為簡單的HTTP認證方式,客戶端通過明文(Base64編碼格式)傳輸用戶名和密碼到服務端進行認證,通常需要配合HTTPS來保證信息傳輸的安全。
客戶端請求需要帶Authorization請求頭,值為“Basic xxx”,xxx為“用戶名:密碼”進行Base64編碼後生成的值。 若客戶端是瀏覽器,則瀏覽器會提供一個輸入用戶名和密碼的對話框,用戶輸入用戶名和密碼后,瀏覽器會保存用戶名和密碼,用於構造Authorization值。當關閉瀏覽器后,用戶名和密碼將不再保存。
憑證為“YWxhzGRpbjpvcGVuc2VzYWl1”,是通過將“用戶名:密碼”格式的字符串經過的Base64編碼得到的。而Base64不屬於加密範疇,可以被逆向解碼,等同於明文,因此Basic傳輸認證信息是不安全的。
Basic基礎認證圖示:
缺陷匯總
1.用戶名和密碼明文(Base64)傳輸,需要配合HTTPS來保證信息傳輸的安全。
2.即使密碼被強加密,第三方仍可通過加密后的用戶名和密碼進行重放攻擊。
3.沒有提供任何針對代理和中間節點的防護措施。
4.假冒服務器很容易騙過認證,誘導用戶輸入用戶名和密碼。
Digest認證是為了修復基本認證協議的嚴重缺陷而設計的,秉承“絕不通過明文在網絡發送密碼”的原則,通過“密碼摘要”進行認證,大大提高了安全性。
Digest認證步驟如下:
第一步:客戶端訪問Http資源服務器。由於需要Digest認證,服務器返回了兩個重要字段nonce(隨機數)和realm。
第二步:客戶端構造Authorization請求頭,值包含username、realm、nouce、uri和response的字段信息。其中,realm和nouce就是第一步返回的值。nouce只能被服務端使用一次。uri(digest-uri)即Request-URI的值,但考慮到經代理轉發后Request-URI的值可能被修改、因此實現會複製一份副本保存在uri內。response也可叫做Request-digest,存放經過MD5運算后的密碼字符串,形成響應碼。
第三步:服務器驗證包含Authorization值的請求,若驗證通過則可訪問資源。
Digest認證可以防止密碼泄露和請求重放,但沒辦法防假冒。所以安全級別較低。
Digest和Basic認證一樣,每次都會發送Authorization請求頭,也就相當於重新構造此值。所以兩者易用性都較差。
Digest認證圖示:
notBefore和expires來驗證),當access_token過期后,可以在用戶無感知的情況下,使用refresh_token重新獲取access_token,但這就不屬於Bearer認證的範疇了,但是我們可以通過另一種方式通過IdentityServer的方式來實現,在後續中會對IdentityServer進行詳細講解。notBefore和expires來驗證,這在分佈式中提供給了極大便利。本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!
※網頁設計公司推薦不同的風格,搶佔消費者視覺第一線
※Google地圖已可更新顯示潭子電動車充電站設置地點!!
※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益
※別再煩惱如何寫文案,掌握八大原則!
※網頁設計最專業,超強功能平台可客製化
※聚甘新
在linux的高性能網絡編程中,繞不開的就是epoll。和select、poll等系統調用相比,epoll在需要監視大量文件描述符並且其中只有少數活躍的時候,表現出無可比擬的優勢。epoll能讓內核記住所關注的描述符,並在對應的描述符事件就緒的時候,在epoll的就緒鏈表中添加這些就緒元素,並喚醒對應的epoll等待進程。
本文就是筆者在探究epoll源碼過程中,對kernel將就緒描述符添加到epoll並喚醒對應進程的一次源碼分析(基於linux-2.6.32內核版本)。由於篇幅所限,筆者聚焦於tcp協議下socket可讀事件的源碼分析。
下面的例子,是從筆者本人用c語言寫的dbproxy中的一段代碼。由於細節過多,所以做了一些刪減。
int init_reactor(int listen_fd,int worker_count){
......
// 創建多個epoll fd,以充分利用多核
for(i=0;i<worker_count;i++){
reactor->worker_fd = epoll_create(EPOLL_MAX_EVENTS);
}
/* epoll add listen_fd and accept */
// 將accept后的事件加入到對應的epoll fd中
int client_fd = accept(listen_fd,(struct sockaddr *)&client_addr,&client_len)));
// 將連接描述符註冊到對應的worker裏面
epoll_ctl(reactor->client_fd,EPOLL_CTL_ADD,epifd,&event);
}
// reactor的worker線程
static void* rw_thread_func(void* arg){
......
for(;;){
// epoll_wait等待事件觸發
int retval = epoll_wait(epfd,events,EPOLL_MAX_EVENTS,500);
if(retval > 0){
for(j=0; j < retval; j++){
// 處理讀事件
if(event & EPOLLIN){
handle_ready_read_connection(conn);
continue;
}
/* 處理其它事件 */
}
}
}
......
}
上述代碼事實上就是實現了一個reactor模式中的accept與read/write處理線程,如下圖所示:
Unix的萬物皆文件的思想在epoll裏面也有體現,epoll_create調用返回一個文件描述符,此描述符掛載在anon_inode_fs(匿名inode文件系統)的根目錄下面。讓我們看下具體的epoll_create系統調用源碼:
SYSCALL_DEFINE1(epoll_create, int, size)
{
if (size <= 0)
return -EINVAL;
return sys_epoll_create1(0);
}
由上述源碼可見,epoll_create的參數是基本沒有意義的,kernel簡單的判斷是否為0,然後就直接就調用了sys_epoll_create1。由於linux的系統調用是通過(SYSCALL_DEFINE1,SYSCALL_DEFINE2……SYSCALL_DEFINE6)定義的,那麼sys_epoll_create1對應的源碼即是SYSCALL_DEFINE(epoll_create1)。
(注:受限於寄存器數量的限制,(80×86下的)kernel限制系統調用最多有6個參數。據ulk3所述,這是由於32位80×86寄存器的限制)
接下來,我們就看下epoll_create1的源碼:
SYSCALL_DEFINE1(epoll_create1, int, flags)
{
// kzalloc(sizeof(*ep), GFP_KERNEL),用的是內核空間
error = ep_alloc(&ep);
// 獲取尚未被使用的文件描述符,即描述符數組的槽位
fd = get_unused_fd_flags(O_RDWR | (flags & O_CLOEXEC));
// 在匿名inode文件系統中分配一個inode,並得到其file結構體
// 且file->f_op = &eventpoll_fops
// 且file->private_data = ep;
file = anon_inode_getfile("[eventpoll]", &eventpoll_fops, ep,
O_RDWR | (flags & O_CLOEXEC));
// 將file填入到對應的文件描述符數組的槽裏面
fd_install(fd,file);
ep->file = file;
return fd;
}
最後epoll_create生成的文件描述符如下圖所示:
所有的epoll系統調用都是圍繞eventpoll結構體做操作,現簡要描述下其中的成員:
/*
* 此結構體存儲在file->private_data中
*/
struct eventpoll {
// 自旋鎖,在kernel內部用自旋鎖加鎖,就可以同時多線(進)程對此結構體進行操作
// 主要是保護ready_list
spinlock_t lock;
// 這個互斥鎖是為了保證在eventloop使用對應的文件描述符的時候,文件描述符不會被移除掉
struct mutex mtx;
// epoll_wait使用的等待隊列,和進程喚醒有關
wait_queue_head_t wq;
// file->poll使用的等待隊列,和進程喚醒有關
wait_queue_head_t poll_wait;
// 就緒的描述符隊列
struct list_head rdllist;
// 通過紅黑樹來組織當前epoll關注的文件描述符
struct rb_root rbr;
// 在向用戶空間傳輸就緒事件的時候,將同時發生事件的文件描述符鏈入到這個鏈表裡面
struct epitem *ovflist;
// 對應的user
struct user_struct *user;
// 對應的文件描述符
struct file *file;
// 下面兩個是用於環路檢測的優化
int visited;
struct list_head visited_list_link;
};
本文講述的是kernel是如何將就緒事件傳遞給epoll並喚醒對應進程上,因此在這裏主要聚焦於(wait_queue_head_t wq)等成員。
我們看下epoll_ctl(EPOLL_CTL_ADD)是如何將對應的文件描述符插入到eventpoll中的。
藉助於spin_lock(自旋鎖)和mutex(互斥鎖),epoll_ctl調用可以在多個KSE(內核調度實體,即進程/線程)中併發執行。
SYSCALL_DEFINE4(epoll_ctl, int, epfd, int, op, int, fd,
struct epoll_event __user *, event)
{
/* 校驗epfd是否是epoll的描述符 */
// 此處的互斥鎖是為了防止併發調用epoll_ctl,即保護內部數據結構
// 不會被併發的添加修改刪除破壞
mutex_lock_nested(&ep->mtx, 0);
switch (op) {
case EPOLL_CTL_ADD:
...
// 插入到紅黑樹中
error = ep_insert(ep, &epds, tfile, fd);
...
break;
......
}
mutex_unlock(&ep->mtx);
}
上述過程如下圖所示:
在ep_insert中初始化了epitem,然後初始化了本文關注的焦點,即事件就緒時候的回調函數,代碼如下所示:
static int ep_insert(struct eventpoll *ep, struct epoll_event *event,
struct file *tfile, int fd)
{
/* 初始化epitem */
// &epq.pt->qproc = ep_ptable_queue_proc
init_poll_funcptr(&epq.pt, ep_ptable_queue_proc);
// 在這裏將回調函數注入
revents = tfile->f_op->poll(tfile, &epq.pt);
// 如果當前有事件已經就緒,那麼一開始就會被加入到ready list
// 例如可寫事件
// 另外,在tcp內部ack之後調用tcp_check_space,最終調用sock_def_write_space來喚醒對應的epoll_wait下的進程
if ((revents & event->events) && !ep_is_linked(&epi->rdllink)) {
list_add_tail(&epi->rdllink, &ep->rdllist);
// wake_up ep對應在epoll_wait下的進程
if (waitqueue_active(&ep->wq)){
wake_up_locked(&ep->wq);
}
......
}
// 將epitem插入紅黑樹
ep_rbtree_insert(ep, epi);
......
}
向kernel更底層註冊回調函數的是tfile->f_op->poll(tfile, &epq.pt)這一句,我們來看一下對於對應的socket文件描述符,其fd=>file->f_op->poll的初始化過程:
// 將accept后的事件加入到對應的epoll fd中
int client_fd = accept(listen_fd,(struct sockaddr *)&client_addr,&client_len)));
// 將連接描述符註冊到對應的worker裏面
epoll_ctl(reactor->client_fd,EPOLL_CTL_ADD,epifd,&event);
回顧一下上述user space代碼,fd即client_fd是由tcp的listen_fd通過accept調用而來,那麼我們看下accept調用鏈的關鍵路徑:
accept
|->accept4
|->sock_attach_fd(newsock, newfile, flags & O_NONBLOCK);
|->init_file(file,...,&socket_file_ops);
|->file->f_op = fop;
/* file->f_op = &socket_file_ops */
|->fd_install(newfd, newfile); // 安裝fd
那麼,由accept獲得的client_fd的結構如下圖所示:
(注:由於是tcp socket,所以這邊sock->ops=inet_stream_ops,這個初始化的過程在我的另一篇博客<<從linux源碼看socket的阻塞和非阻塞>>中,博客地址如下:
https://my.oschina.net/alchemystar/blog/1791017)
既然知道了tfile->f_op->poll的實現,我們就可以看下此poll是如何將安裝回調函數的。
kernel的調用路徑如下:
sock_poll /*tfile->f_op->poll(tfile, &epq.pt)*/;
|->sock->ops->poll
|->tcp_poll
/* 這邊重要的是拿到了sk_sleep用於KSE(進程/線程)的喚醒 */
|->sock_poll_wait(file, sk->sk_sleep, wait);
|->poll_wait
|->p->qproc(filp, wait_address, p);
/* p為&epq.pt,而且&epq.pt->qproc= ep_ptable_queue_proc*/
|-> ep_ptable_queue_proc(filp,wait_address,p);
繞了一大圈之後,我們的回調函數的安裝其實就是調用了eventpoll.c中的ep_ptable_queue_proc,而且向其中傳遞了sk->sk_sleep作為其waitqueue的head,其源碼如下所示:
static void ep_ptable_queue_proc(struct file *file, wait_queue_head_t *whead,
poll_table *pt)
{
// 取出當前client_fd對應的epitem
struct epitem *epi = ep_item_from_epqueue(pt);
// &pwq->wait->func=ep_poll_callback,用於回調喚醒
// 注意,這邊不是init_waitqueue_entry,即沒有將當前KSE(current,當前進程/線程)寫入到
// wait_queue當中,因為不一定是從當前安裝的KSE喚醒,而應該是喚醒epoll\_wait的KSE
init_waitqueue_func_entry(&pwq->wait, ep_poll_callback);
// 這邊的whead是sk->sk_sleep,將當前的waitqueue鏈入到socket對應的sleep列表
add_wait_queue(whead, &pwq->wait);
}
這樣client_fd的結構進一步完善,如下圖所示:
ep_poll_callback函數是喚醒對應epoll_wait的地方,我們將在後面一起講述。
epoll_wait主要是調用了ep_poll:
SYSCALL_DEFINE4(epoll_wait, int, epfd, struct epoll_event __user *, events,
int, maxevents, int, timeout)
{
/* 檢查epfd是否是epoll\_create創建的fd */
// 調用ep_poll
error = ep_poll(ep, events, maxevents, timeout);
...
}
緊接着,我們看下ep_poll函數:
static int ep_poll(struct eventpoll *ep, struct epoll_event __user *events,
int maxevents, long timeout)
{
......
retry:
// 獲取spinlock
spin_lock_irqsave(&ep->lock, flags);
// 將當前task_struct寫入到waitqueue中以便喚醒
// wq_entry->func = default_wake_function;
init_waitqueue_entry(&wait, current);
// WQ_FLAG_EXCLUSIVE,排他性喚醒,配合SO_REUSEPORT從而解決accept驚群問題
wait.flags |= WQ_FLAG_EXCLUSIVE;
// 鏈入到ep的waitqueue中
__add_wait_queue(&ep->wq, &wait);
for (;;) {
// 設置當前進程狀態為可打斷
set_current_state(TASK_INTERRUPTIBLE);
// 檢查當前線程是否有信號要處理,有則返回-EINTR
if (signal_pending(current)) {
res = -EINTR;
break;
}
spin_unlock_irqrestore(&ep->lock, flags);
// schedule調度,讓出CPU
jtimeout = schedule_timeout(jtimeout);
spin_lock_irqsave(&ep->lock, flags);
}
// 到這裏,表明超時或者有事件觸發等動作導致進程重新調度
__remove_wait_queue(&ep->wq, &wait);
// 設置進程狀態為running
set_current_state(TASK_RUNNING);
......
// 檢查是否有可用事件
eavail = !list_empty(&ep->rdllist) || ep->ovflist != EP_UNACTIVE_PTR;
......
// 向用戶空間拷貝就緒事件
ep_send_events(ep, events, maxevents)
}
上述邏輯如下圖所示:
ep_send_events函數主要就是調用了ep_scan_ready_list,顧名思義ep_scan_ready_list就是掃描就緒列表:
static int ep_scan_ready_list(struct eventpoll *ep,
int (*sproc)(struct eventpoll *,
struct list_head *, void *),
void *priv,
int depth)
{
...
// 將epfd的rdllist鏈入到txlist
list_splice_init(&ep->rdllist, &txlist);
...
/* sproc = ep_send_events_proc */
error = (*sproc)(ep, &txlist, priv);
...
// 處理ovflist,即在上面sproc過程中又到來的事件
...
}
其主要調用了ep_send_events_proc:
static int ep_send_events_proc(struct eventpoll *ep, struct list_head *head,
void *priv)
{
for (eventcnt = 0, uevent = esed->events;
!list_empty(head) && eventcnt < esed->maxevents;) {
// 遍歷ready list
epi = list_first_entry(head, struct epitem, rdllink);
list_del_init(&epi->rdllink);
// readylist只是表明當前epi有事件,具體的事件信息還是得調用對應file的poll
// 這邊的poll即是tcp_poll,根據tcp本身的信息設置掩碼(mask)等信息 & 上興趣事件掩碼,則可以得知當前事件是否是epoll_wait感興趣的事件
revents = epi->ffd.file->f_op->poll(epi->ffd.file, NULL) &
epi->event.events;
if(revents){
/* 將event放入到用戶空間 */
/* 處理ONESHOT邏輯 */
// 如果不是邊緣觸發,則將當前的epi重新加回到可用列表中,這樣就可以下一次繼續觸發poll,如果下一次poll的revents不為0,那麼用戶空間依舊能感知 */
else if (!(epi->event.events & EPOLLET)){
list_add_tail(&epi->rdllink, &ep->rdllist);
}
/* 如果是邊緣觸發,那麼就不加回可用列表,因此只能等到下一個可用事件觸發的時候才會將對應的epi放到可用列表裡面*/
eventcnt++
}
/* 如poll出來的revents事件epoll_wait不感興趣(或者本來就沒有事件),那麼也不會加回到可用列表 */
......
}
return eventcnt;
}
上述代碼邏輯如下所示:
經過上述章節的詳述之後,我們終於可以闡述,tcp在數據到來時是怎麼加入到epoll的就緒隊列的了。
首先我們看下tcp數據包從網卡驅動到kernel內部tcp協議處理調用鏈:
網絡分組到來的內核路徑,網卡發起中斷後調用netif_rx將事件掛入CPU的等待隊列,並喚起軟中斷(soft_irq),再通過linux的軟中斷機制調用net_rx_action,如下圖所示:
注:上圖來自PLKA(<<深入Linux內核架構>>)
緊接着跟蹤next_rx_action
next_rx_action
|-process_backlog
......
|->packet_type->func 在這裏我們考慮ip_rcv
|->ipprot->handler 在這裏ipprot重載為tcp_protocol
(handler 即為tcp_v4_rcv)
我們再看下對應的tcp_v4_rcv
tcp_v4_rcv
|->tcp_v4_do_rcv
|->tcp_rcv_state_process
|->tcp_data_queue
|-> sk->sk_data_ready(sock_def_readable)
|->wake_up_interruptible_sync_poll(sk->sleep,...)
|->__wake_up
|->__wake_up_common
|->curr->func
/* 這裏已經被ep_insert添加為ep_poll_callback,而且設定了排它標識WQ_FLAG_EXCLUSIVE*/
|->ep_poll_callback
這樣,我們就看下最終喚醒epoll_wait的ep_poll_callback函數:
static int ep_poll_callback(wait_queue_t *wait, unsigned mode, int sync, void *key)
{
// 獲取wait對應的epitem
struct epitem *epi = ep_item_from_wait(wait);
// epitem對應的eventpoll結構體
struct eventpoll *ep = epi->ep;
// 獲取自旋鎖,保護ready_list等結構
spin_lock_irqsave(&ep->lock, flags);
// 如果當前epi沒有被鏈入ep的ready list,則鏈入
// 這樣,就把當前的可用事件加入到epoll的可用列表了
if (!ep_is_linked(&epi->rdllink))
list_add_tail(&epi->rdllink, &ep->rdllist);
// 如果有epoll_wait在等待的話,則喚醒這個epoll_wait進程
// 對應的&ep->wq是在epoll_wait調用的時候通過init_waitqueue_entry(&wait, current)而生成的
// 其中的current即是對應調用epoll_wait的進程信息task_struct
if (waitqueue_active(&ep->wq))
wake_up_locked(&ep->wq);
}
上述過程如下圖所示:
最後wake_up_locked調用__wake_up_common,然後調用了在init_waitqueue_entry註冊的default_wake_function,調用路徑為:
wake_up_locked
|->__wake_up_common
|->default_wake_function
|->try_wake_up (wake up a thread)
|->activate_task
|->enqueue_task running
將epoll_wait進程推入可運行隊列,等待內核重新調度進程,然後epoll_wait對應的這個進程重新運行后,就從schedule恢復,繼續下面的ep_send_events(向用戶空間拷貝事件並返回)。
wake_up過程如下圖所示:
可寫事件的運行過程和可讀事件大同小異:
首先,在epoll_ctl_add的時候預先會調用一次對應文件描述符的poll,如果返回事件里有可寫掩碼的時候直接調用wake_up_locked以喚醒對應的epoll_wait進程。
然後,在tcp在底層驅動有數據到來的時候可能攜帶了ack從而可以釋放部分已經被對端接收的數據,於是觸發可寫事件,這一部分的調用鏈為:
tcp_input.c
tcp_v4_rcv
|-tcp_v4_do_rcv
|-tcp_rcv_state_process
|-tcp_data_snd_check
|->tcp_check_space
|->tcp_new_space
|->sk->sk_write_space
/* tcp下即是sk_stream_write_space*/
最後在此函數裏面sk_stream_write_space喚醒對應的epoll_wait進程
void sk_stream_write_space(struct sock *sk)
{
// 即有1/3可寫空間的時候才觸發可寫事件
if (sk_stream_wspace(sk) >= sk_stream_min_wspace(sk) && sock) {
clear_bit(SOCK_NOSPACE, &sock->flags);
if (sk->sk_sleep && waitqueue_active(sk->sk_sleep))
wake_up_interruptible_poll(sk->sk_sleep, POLLOUT |
POLLWRNORM | POLLWRBAND)
......
}
}
值得注意的是,我們在close對應的文件描述符的時候,會自動調用eventpoll_release將對應的file從其關聯的epoll_fd中刪除,kernel關鍵路徑如下:
close fd
|->filp_close
|->fput
|->__fput
|->eventpoll_release
|->ep_remove
所以我們在關閉對應的文件描述符后,並不需要通過epoll_ctl_del來刪掉對應epoll中相應的描述符。
epoll作為linux下非常優秀的事件觸發機製得到了廣泛的運用。其源碼還是比較複雜的,本文只是闡述了epoll讀寫事件的觸發機制,探究linux kernel源碼的過程非常快樂_。
關注筆者公眾號,獲取更多乾貨文章:
本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!
※網頁設計公司推薦不同的風格,搶佔消費者視覺第一線
※Google地圖已可更新顯示潭子電動車充電站設置地點!!
※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益
※別再煩惱如何寫文案,掌握八大原則!
※網頁設計最專業,超強功能平台可客製化
※聚甘新