並發、並行、同步、異步、阻塞、非阻塞


並發、並行、同步、異步、阻塞、非阻塞

最近在寫爬蟲 ,對於這幾個概念比較模糊,所以特意學習了一下。

進程(process):進程是具有一定獨立功能的程序關於某個數據集合上的一次運行活動,進程是系統進行資源分配和調度的一個獨立單位。

線程(thread):線程是進程的一個實體,是CPU調度和分派的基本單位,它是比進程更小的能獨立運行的基本單位.線程自己基本上不擁有系統資源,只擁有一點在運行中必不可少的資源(如程序計數器,一組寄存器和棧),但是它可與同屬一個進程的其他的線程共享進程所擁有的全部資源。

一個線程可以創建和撤銷另一個線程;同一個進程中的多個線程之間可以並發執行。

微線程:又叫協程。 tasklet運行在偽並發中,使用channel機制進行同步數據交換。python中的greenlet提供了微線程的操作。不同於多線程,它給我們提供了一種更加輕量的異步編程模式。

  協程(Coroutine)提供了不同於線程的另一種方式,它首先是串行化的。其次,在串行化的過程中,協程允許用戶顯式釋放控制權,將控制權轉移另一個過程。釋放控制權之后,原過程的狀態得以保留,直到控制權恢復的時候,可以繼續執行下去。所以協程的控制權轉移也稱為“掛起”和“喚醒”。

並發(concurrency):並發是指二個和多個事件在同一時間間隔內發生。並發是在邏輯層面上的同時工作。

並行(parallelism):並行是指二個或多個事件在同一時刻發生。 並行是在物理層面上的同時工作。

同步(synchronous):在發出一個功能調用時,在沒有得到結果之前,該調用就不返回。

異步(asynchronous):異步的概念和同步相對。當一個異步過程調用發出后,調用者不能立刻得到結果。實際處理這個調用的部件在完成后,通過狀態、通知和回調來通知調用者。

 阻塞:阻塞調用是指調用結果返回之前,當前線程會被掛起。函數只有在得到結果之后才會返回。

非阻塞:非阻塞和阻塞的概念相對應,指在不能立刻得到結果之前,該函數不會阻塞當前線程,而會立刻返回。

要注意同步和異步 與 阻塞和非阻塞 這兩組概念之間的區別。

 

同步IO和異步IO的區別就在於:數據拷貝的時候進程是否阻塞。

 

阻塞IO和非阻塞IO的區別就在於:應用程序的調用是否立即返回。

對於上面的兩句話,你也許會感到疑問,看了下面的內容你就清楚了。

 

這兩組是可以互相組合的。

 

同步阻塞,同步非阻塞,異步阻塞,異步非阻塞。詳細見最后一個鏈接。

同步阻塞I/O:在這個模型中,用戶空間的應用程序執行一個系統調用,這會導致應用程序阻塞。這意味着應用程序會一直阻塞,直到系統調用完成為止(數據傳輸完成或發生錯誤)。調用應用程序處於一種不再消費 CPU 而只是簡單等待響應的狀態,因此從處理的角度來看,這是非常有效的。

同步非阻塞I/O:同步阻塞 I/O 的一種效率稍低的變種是同步非阻塞 I/O。在這種模型中,設備是以非阻塞的形式打開的。這意味着 I/O 操作不會立即完成,需要應用程序調用許多次來等待操作完成。這可能效率不高,因為在很多情況下,當內核執行這個命令時,應用程序必須要進行忙碌等待,直到數據可用為止,或者試圖執行其他工作。

異步阻塞I/O:在這種模型中,配置的是非阻塞 I/O,然后使用阻塞 select 系統調用來確定一個 I/O 描述符何時有操作。使 select 調用非常有趣的是它可以用來為多個描述符提供通知,而不僅僅為一個描述符提供通知。對於每個提示符來說,我們可以請求這個描述符可以寫數據、有讀數據可用以及是否發生錯誤的通知。

異步非阻塞I/O:異步非阻塞 I/O 模型是一種處理與 I/O 重疊進行的模型。讀請求會立即返回,說明 read 請求已經成功發起了。在后台完成讀操作時,應用程序然后會執行其他處理操作。當 read 的響應到達時,就會產生一個信號或執行一個基於線程的回調函數來完成這次 I/O 處理過程在一個進程中為了執行多個 I/O 請求而對計算操作和 I/O 處理進行重疊處理的能力利用了處理速度與 I/O 速度之間的差異。當一個或多個 I/O 請求掛起時,CPU 可以執行其他任務;或者更為常見的是,在發起其他 I/O 的同時對已經完成的 I/O 進行操作。

 

 

想更加詳細的了解這方面的內容,建議閱讀下面的鏈接內容。本文過於簡陋 ^-^,請諒解。下面的才是精華 :

  加州大學伯克利分校的學術報告,關於並發與並行的分析:http://www.eecs.berkeley.edu/Pubs/TechRpts/2008/EECS-2008-151.html

  網絡程序設計中的並發復雜性:http://d.g.wanfangdata.com.cn/Periodical_rjxb201101010.aspx

    同步與異步的概念:http://blog.chinaunix.net/uid-21411227-id-1826898.html

  進程和線程的區別:http://www.cnblogs.com/lmule/archive/2010/08/18/1802774.html

  http://blog.csdn.net/hguisu/article/details/7453390

  https://www.ibm.com/developerworks/cn/linux/l-async/

 
 
分類:  網絡技術

[ASP.NET]從ASP.NET Postback機制,到POST/GET方法

寫這篇博客的起源來自於自己最近在學習ASP.NET時對於 PostBack機制的困惑。因為自己在解決困惑地同時,會不斷產生新的疑問,因此博客最后深入到了http 包的格式和Internet所使用的TCP/IP模型,算是來了一堂基礎復習課。但我相信這些基礎的牢固性,會影響到web方向的深入學習,因此整理成文,便於復習,便於探討。

寫博的時候並沒有將http協議包格式等底層的東西調整到最前面寫,因為我覺得既然我是這樣思考的,何不這樣呈現?為了便於描述,我用下圖這棵樹表示寫這篇博文的思路,IsPostBack是表面的引起疑問的葉子,順着這片葉子,可以逐漸追溯到http協議這個樹干。“Post與Get方法”那一塊被安排在樹干上,是因為從這里開始,觸及了Web開發的主體;枝葉上的ASP.NET部分,只是基於這個主體又加入自己的技術的延伸。別的技術比如JSP,Struts等Java方向的技術,也是基於這個主體的另一種技術方向的延伸,因此它們都會像樹枝一樣,從主干上發散開。

博文中有任何覺得不對的地方,歡迎在留言中和我探討。畢竟我也只是剛接觸Web不久的Fresh Man,行文之時,心下惴惴,因此歡迎指出錯誤和討論。

PostBack機制

什么是Postback?IsPostBack的作用是什么?

PostBack機制是ASP.NET特有的機制,為什么說特有,我們從web請求和響應說起。

web的基本原理就是請求和響應。以asp為例,Browser端的HTML文本,以及javascript代碼,運行后向server端發送script,server端的.asp腳本,接受request,處理后發出respond。這種server端script和client端script交互,完成一次次對於用戶操作(提交表單,載入新的URL)的響應。

以一個html文件和asp文件為例:

html代碼:

復制代碼
<!DOCtype html PUBLIC "-//W3C//DTD XhTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><title>order</title></head>
<body>
<h2>Form Example</h2>
<p>
Please input and submit
<form method="POST" ACTION="Response.asp">
<p>
姓: <input NAME="fname" SIZE="48"/>
<p>
名: <input NAME="lname" SIZE="48"/>
<p>
稱呼: <input NAME="title" type=RADIO VALUE="先生"/>先生
<input NAME="title" type=RADIO VALUE="女士"/>女士
<p><input type=SUBMIT VALUE="提交"/><input type=RESET VALUE="清除"/>
</form>
</body>
</html>
復制代碼
Response.asp腳本代碼(VB語言)
復制代碼
<HTML>
<HEAD></HEAD>
<BODY>
<%
Title = Request.Form("title")
LastName = Request.Form("lname")
If Title = "先生" Then
Response.Write LastName & "先生"
ElseIf Title = "女士" Then
Response.Write LastName & "女士"
Else
Response.Write Request.Form("fname") & " " & LastName
End If %>
</BODY>
</HTML>
復制代碼

可以看到HTML文件的form控件中,"action"屬性指定了form提交后處理它的Server端腳本。

而在ASP.NET系統中,我們沒有Client端和Server端腳本,我們只有aspx文件,而aspx也會被Render為HTML,在Client端通過瀏覽器顯示,因為瀏覽器只能識別HTML標簽。

下面的例子引自Artech的淺談ASP.NET的Postback,這篇文章講解了Postback的實現方式,簡單說來:Render之后的HTML會自動加入一個form,其中用hidden的input來存儲id和事件參數。在__Postback這個函數中,form中的內容會被提交。Artech所給的aspx代碼在Browser端的呈現出來的源碼如下:

復制代碼
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <title>
            Test Page
        </title>
    </head>
    <body>
        <form name="form1" method="post" action="Default.aspx" id="form1">
            <div>
                <input type="hidden" name="__EVENTTARGET" id="__EVENTTARGET" value="" />
                <input type="hidden" name="__EVENTARGUMENT" id="__EVENTARGUMENT" value="" />
                <input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="/wEPDwUKMTA0NDQ2OTE5OWRk281L4eAk7iZT10hzg+BeOyoUWBQ=" />
            </div>

<script type="text/javascript">
                <!--
var theForm = document.forms['form1'];
if (!theForm) {
    theForm = document.form1;
}
function __doPostBack(eventTarget, eventArgument) {
    if (!theForm.onsubmit || (theForm.onsubmit() != false)) {
        theForm.__EVENTTARGET.value = eventTarget;
        theForm.__EVENTARGUMENT.value = eventArgument;
        theForm.submit();
    }
}

</script>

<div>
<span id="LabelMessage" style="color:Red;"></span>
</div>
<div>
    <input type="submit" name="Button1" value="Button1" id="Button1" />
    <input type="button" name="Button2" value="Button2" onclick="javascript:__doPostBack('Button2','')" id="Button2" />
    <input type="button" name="Button3" value="Button3" onclick="javascript:__doPostBack('Button3','')" id="Button3" />
</div>
</form>
</body>
</html>
復制代碼

以上代碼在Artech博文中也可以找到,我把它貼過來,以方便引述。這段代碼是Default.aspx被render到browser端的html源碼,Postback的實現機制就是定義了一個form,這個Form中包含隱藏的input,從而保存需要post的值,那么,向哪里post?Form中"action"屬性指定了post的目標,那這里呢?我們可以看到其值為"Default.aspx",也就是它自己。這和上面那個ASP的例子中是不同的。

我的理解是:所謂Postback,是指在ASP.NET機制中,不是Client端發post請求到server端腳本,在這個機制中,aspx被render到browser后,其Form的post目標依然是這個aspx。Postback由此得名,因為post回來了。。

因此當我們觸發網頁的按鈕時,若此按鈕涉及到后台操作(在后端有C#響應代碼,而非僅僅調用前端javascript函數),aspx頁面便會重新加載,因為Postback觸發了它。(這句話待商榷,我對Postback與page life cycle了解再深刻些后,會再編輯這句話)。

這個機制所需要解決的第一個問題是:當開發人員編寫代碼時,aspx的加載有兩種原因:點擊按鈕觸發post來讓aspx加載;用戶輸入url來加載aspx。對於不同的原因,可能開發人員希望代碼進入不同的處理邏輯。IsPostback這個ASP.NET所給的變量,就是用來給developer確定是否這個網頁是因為postback而加載,而是通過輸入url或者刷新頁面的方式來加載。

最簡單的例子,在Page_Load()方法里經常會 if(!IsPostBack) BindForm();//給表單所有控件賦值的方法。意思是提交后我就不綁定表單了,而是走Click的具體事件方法。

而IsPostBack是何時被賦值的,ASP.NET代碼中是根據什么條件來判斷是否是postback的網頁,這一個樹枝暫時還沒有研究下去,如果能有前輩能在留言中給我一些線索的話,感激不盡 :)

有關於Page_Load()方法,它是ASP.NET中的網頁載入過程中page load事件的默認響應函數,具體請參見:

ASP.NET 頁生命周期概述  以及 [ASP.NET]Page Life Cycle整理

因此Postback的機制本質實現其實是form的post,那么post和get,這些具體是什么?

POST與GET方法

正如之前所說,web實際就是request與respond的交互,那么,這些request與respond,其實就是http包。

對於一個Request,它可能不僅僅是一個要求載入頁面的request,也可能是要求發送一些數據的request,或者要求刪除服務端一些數據的request,如何區分這些request?http給request定義了四個謂詞:POST,GET,PUT,DELETE。從名字就可以看出來他們的功用,分別對應着改,查,增,刪。

而最常使用的是GET與POST。

他們的共同點和區別在哪里?

共同點是:GET POST其實都可以實現向服務器傳送數據。在HTML中,本身表單form的提交就有兩種方式,一種是get的方法,一種是post 的方法。而這兩種方法保存參數的方式是不同的,如果使用Fiddler或其他的http包查看工具,可以發現post方法提交的Form,其參數存在body中,而get方法提交的form,其參數則直接加入到url的后面。

通過包來看兩者之間本質的差別的例子,可以參見淺談 HTTP Method:表單中的 GET 與 POST 有什麼差別?

通過輸入url來訪問頁面,其request都是get。

兩者特性上的差別:form方式因為是將內容放在body中發過去,而get僅僅是發一個get請求過去,內容在url中,因此速度比post快,而安全性則低於post。

GET的response會被cache,而POST的response不會。因為GET的初衷就是獲取內容,因此可以通過cache來存儲不變的內容,而POST則是用來提交內容,其響應基於所提交的內容可能不斷變化。

兩者之間其他差別參見下圖,圖來自[HTTP]Http GET、POST Method

有一篇文章提到了在使用Ajax時,POST會比GET慢數倍,各位有興趣可以看一下:打破沙鍋-AJAX POST比GET效率差?

之前提到既然request和response實際就是HTTP包,那POST與GET這些謂詞又是如何在包中呈現的?

POST與GET報文格式

 以Artech的那個例子為例,那個例子如果運行起來,會看到三個按鈕,點擊其中一個,會顯示哪個按鈕的click被fire了:

點擊Button3后,request與response在fiddler中的內容如圖:

在fiddle中,可以看到POST就出現在Request的第一行中。

Response的Header的內容是:HTTP/1.1 200 OK

 

 一個http請求報文的格式如下圖 (圖片來自計算機網絡應用層之HTTP協議)

 分為請求行(Request line),首部行(Header line),空行(Brank line)和實體(body),謂詞方法的位置就在請求行的開頭。

HTTP響應respond的報文格式如下:

其中200為狀態碼,表示請求成功。返回內容在實體中呈現,比如之前fiddler的截圖,響應包中的內容就是原先那個HTML的內容,也就是說,瀏覽器收到這個response后,用戶會看到整個頁面被刷新了一下。

我們知道了http的報文格式,那什么是HTTP協議?

HTTP協議

HTTP是HyperText Transfer Protocol即超文本傳輸協議的縮寫,是Web應用層協議之一。

在說這個之前,先提幾個HTTP的特性,這部分引用了Tank的 HTTP協議詳解 中部分內容

HTTP協議是無狀態的:同一個客戶端的這次請求和上次請求是沒有對應關系,對http服務器來說,它並不知道這兩個請求來自同一個客戶端。 為了解決這個問題, Web程序引入了Cookie機制來維護狀態。引入了ViewState來存儲控件內容或者用戶自定義信息。

關於Cookie的介紹,讀寫方式,以及它在ASP.NET form authentication中所起的作用,參見:Fish Li的細說Cookie

關於ViewState:我的理解是一種在用戶端存儲控件內容和一些開發人員自定義內容的機制。其實現方式是在用戶端的HTML代碼中插入名字為"__ViewState"的hidden input,那些需要存儲的內容,都經過Base64編碼后以字符串的形式存儲在這個hidden input中。因為是以字符串的形式存儲在客戶端的HTML文件中,因此屬於長期儲存,不會自動過期或消失,用戶甚至可以拷貝下來保存。更多內容,參見 [.NET] ASP.NET 狀態管理(State Management):ViewState

打開一個網頁需要瀏覽器發送很多次Request:

1. 當你在瀏覽器輸入URL http://www.cnblogs.com 的時候,瀏覽器發送一個Request去獲取 http://www.cnblogs.com 的html.  服務器把Response發送回給瀏覽器.

2. 瀏覽器分析Response中的 HTML,發現其中引用了很多其他文件,比如圖片,CSS文件,JS文件。

3. 瀏覽器會自動再次發送Request去獲取圖片,CSS文件,或者JS文件。

4. 等所有的文件都下載成功后。 網頁就被顯示出來了。

 

由於HTTP是使用TCP作為其運輸協議的,因此http協議也需要連接,也需要三次握手的過程。

TCP連接分為持久連接和非持久連接:

在非持久連接的情況下,服務器在發送響應后,關閉TCP連接。我們定義往返時間RTT為一個小分組從客戶機到服務器再回到客戶所花費的時間。所以RTT包括分組傳播時延、排列時延以及分組處理時延。

若http采用非持久連接時,我們可以估算出完成一次傳輸所消耗的時間:完成了三次握手的前兩部分后,客戶機將三次握手的第三部分(確認)與一個HTTP請求報文結合起來發送到該TCP連接。一旦請求報文到達服務器,服務器向該TCP連接發送HTML文件。從上面的描述,我們可以知道,對於一個非持久連接,請求一個HTTP請求/響應需要的總時間為兩個RTT+服務器傳輸HTML文件的時間,也就是兩個來回所耗的時間加上傳輸時間。

在持久連接情況下,服務器在發出響應后讓TCP連接繼續打開着。同一對客戶/服務器之間的后續請求和響應可以通過這個連接發送。整個Web頁面(上例中為包含一個基本HTMLL文件和10個圖像的頁面)自不用說可以通過單個持久TCP連接發送:甚至存放在同一個服務器中的多個Web頁面也可以通過單個持久TCP連接發送。通常,HTTP服務器在某個連接閑置一段特定時間后關閉它,而這段時間通常是可以配置的。
持久連接分為不帶流水線(without pipelining)和帶流水線(with pipelining)兩個版本。如果是不帶流水線的版本,那么客戶只在收到前一個請求的response后才發出新的request。帶流水線則是不等到response就發下一個request。這樣,在密集請求的情況下,不帶流水線的每一個請求響應過程都比帶流水線的慢一個RTT,因為后一個基本不必等,前一個至少每次等一個RTT。
 
HTTP/1.1的默認模式使用帶流水線的持久連接。這種情況下,HTTP客戶每碰到一個引用就立即發出一個請求,因而HTTP客戶可以一個接一個緊挨着發出各個引用對象的請求。
 
HTTP是Web應用層協議之一,那么它在整個網絡之中如何傳遞呢?

網絡分層結構

我們知道互聯網的框架主要是兩種:OSI的七層框架,和TCP/IP體系結構。而Internet網絡體系結構以TCP/IP為核心。基於TCP/IP的參考模型將協議分成四個層次。

這兩種模型的對應關系如圖,圖片來自網絡互聯參考模型(詳解)

而我們之前應用層的http報文,從最高層往下經歷層層封裝,最后到達最底層轉換為01流。發往目的地,中間會經過交換機和路由器的中轉,解包,確認下一個轉發地址后再次封包轉發。如下圖 (圖片來自網絡互聯參考模型(詳解))

 

 

 

------------------------------------------------

Felix原創,轉載請注明出處,感謝博客園! 

 
分類:  Web
標簽:  ASP.NEThttp protocol

Highcharts結合PhantomJS在服務端生成高質量的圖表圖片

 

項目背景

最近忙着給部門開發一套交互式的報表系統,來替換原有的靜態報表系統。

老系統是基於dotnetCHARTING開發的,dotnetCHARTING的優勢是圖表類型豐富,接口調用簡單,使用時只需綁定數據源即可(指定連接字符和sql語句,簡單的配置一下就能出圖),支持生成靜態圖表圖片;缺點就是生成好的圖是圖片,傳到了前台就失去了交互性(當然它還提供了一個jsCharting,不過感覺交互性做的還是不夠好),再有就是這東東是收費的呀,用的話需要折騰破解版本。

我最終選擇了Highcharts(Interactive JavaScript charts for your webpage)來展現前台圖表,通過Highcharts良好的交互性實現與服務端的數據交互,將數據可視化。

dotnetCHARTING在數據加載的設計上做的還是很不錯的,我在開發過程中借鑒了其處理思想,自己實現了一套數據加載方案,能夠很方便的把數據傳給Highcharts。這套數據加載方案,簡單的說就是指定好數據庫連接信息和sql查詢信息,服務端采用ADO.NET執行查詢生成DataSet,然后分析DataSet將數據轉換為Highcharts能夠直接使用的json格式。

報表的處理細節還是蠻多的,這里就不在一一討論了,如題,接下來重點跟大家分享一下,服務端生成圖表圖片那部分的處理細節。

Highcharts服務端生成圖表圖片流程簡介

生成圖片的數據流向倒是比較簡單,如下圖所示:

ASP.NET在服務端生成圖表圖片的方式

根據上述生成圖片的步驟,核心其實就是對第二步的處理,也就是如何將SVG數據在服務端做處理,生成圖表圖片。

這樣的話,我們的處理思路就很清晰了,直接在服務端把SVG處理為圖片不就可以了,這么想,也就這么做,剛好網上也有人這么弄過,於是也就直接借鑒了其代碼,代碼不上了,介紹下用到的dll:

在nuget中搜索svg,可以找到一個SVG Rendering Library的包,可以用這個包將SVG格式的數據保存為圖片,用法也比較簡單,大家可以到其官網查閱使用方法。

這個大家不必自己去實現,因為highcharts官網已經給出了第三方的ASP.NET導出圖表的模塊(他就是基於這個SVG Rendering Library實現的):

https://github.com/imclem/Highcharts-export-module-asp.net

SVG Rendering Library的問題

在使用SVG Rendering Library服務端生成圖表圖片的過程中,發現一些問題:

  • 生成的圖片中文字體模糊發虛,整體圖片質量差,跟實際在網頁中顯示的效果差別還真不小
  • 圖表上數據點的dataLabel無法顯示(一開始以為是highcharts配置的問題,后來鑒定是SVG Rendering Library的問題,這個必須修改svg.dll才能解決)

先看一下圖片質量的問題,首先是Chrome中實際展現的圖表的截圖:

在來一張使用svg.dll在后台生成的圖:

對比着兩張圖,可以和明顯的看出生成的圖片中漢字發虛(尤其是下面的月份)。正是這個原因,促使我去尋找一個更好的方案來替代SVG Rendering Library,以確保服務端生成圖表圖片的質量。

心想highcharts在瀏覽器中的顯示效果已經不錯了,要不做截圖,但是截圖的話跟服務端也沒關系了呀,突然想到了在服務端渲染截圖這么個思路。但是具體怎么做呢?先找找資料吧。

神器PhantomJS華麗登場

第一次接觸Phantomjs是半年前左右,當時正在開發web漏洞檢測工具,需要執行頁面上的js,進行分析,沒有經驗的我,各處找資料,看到PhantomJS后,心想,這貨不是已經有人做過了么,干嘛還重復造車輪子,后來隨着業務變更,也沒有深入研究它。

這次搜索“服務端,截圖”這個關鍵字的時候,再次看到了PhantomJS,對它的印象不深了,先去官網看看介紹吧,PhantomJS: Headless WebKit with JavaScript API,哦,原來是個可以執行js並集成了webkit的動動,只是沒有可視化的部分而已。

PhantomJS能干啥呢?

  • HEADLESS WEBSITE TESTING(非可視化的Web測試)
  • SCREEN CAPTURE,Programmatically capture web contents, including SVG and Canvas.(截屏啊,支持SVG啊,吼吼,這不正是我想要的么)
  • PAGE AUTOMATION(頁面自動化,可以使用jQuery操作DOM)
  • NETWORK MONITORING(監視頁面加載,還可以結合Jenkins做自動化分析,流弊啊!)

對Phantomjs做過一番了解后,就確定用它來處理服務端生成圖表圖片的問題了。我設計的處理流程如下:

 

畫的很挫,能看明白處理過程就好,接下來分享一下具體處理過程中需要解決的問題。

新方案的處理細節

Highcharts中導出圖表的配置

圖表的其他配置不需要修改,只需修改導出圖片的配置即可,導出的配置如下:

var chart = new Highcharts.Chart({
    //...
    exporting: {
        url: '/Chart/Export',    // 導出圖表的服務端處理地址
        filename: 'chart_from_phantomjs'    // 返回下載的文件名
    },
    //...
});

我們使用Chrome調試一下,看看下載圖片的時候,Highcharts都向服務端提交了哪些信息,截圖如下:

Highcharts向/Chart/Export發送了一個Post請求,提交的信息如上圖所示,在服務端,我們需要根據type來生成不同的圖片格式,可以通過svg獲取Highcharts提交的圖表數據。

ASP.NET中SVG的處理

首先直接將Highcharts傳遞的SVG數據保存為本地文件,PhantomJS需要通過http://xxx/xxx.svg的形式請求SVG圖像,直接請求ASP.NET會以將svg數據以文件的形式返回,因此需要對svg的請求做單獨處理。代碼如下: 

/// <summary>
/// 處理Svg文件請求,避免直接返回文件
/// </summary>
public class SvgHandler : IHttpHandler
{
    public void ProcessRequest(HttpContext context)
    {
        var file = context.Server.MapPath(context.Request.Url.AbsolutePath);
        if (File.Exists(file))
        {
            context.Response.ContentType = "image/svg+xml";
            context.Response.WriteFile(file);
        }
        else
        {
            context.Response.Write("請求的文件不存在");
        }
    }

    public bool IsReusable 
    {
        get
        {
            return true;
        }
    }
}

 最后在Web.config中配置一下:

<httpHandlers>
  <add verb="*" path="*.svg" type="Highcharts.Exporting.Helper.SvgHandler, Highcharts.Exporting, Version=1.0.0.0, Culture=neutral"/>
</httpHandlers>

ASP.NET與PhantomJS的交互處理

由於PhantomJS是個獨立的進程,這樣ASP.NET在與之交互的時候需要讓PhantomJS一直運行,不然每次啟動一個新的進程開銷也比較大。

PhantomJS支持js腳本調用,我們可以通過編寫腳本實現PhantomJS以服務的方式長期運行,代碼篇幅較長,下面會給出源碼。 

PhantomJS中通過接收post請求,從請求信息中獲取url信息,url就是要渲染的SVG地址,將對應SVG渲染截圖,並返回BASE64編碼的數據處理,代碼如下:

page.open(req.post.url,function(status){
    if(status !== "success"){
    res.send(status);
    } else {
        setTimeout(function() {
           // 發送渲染后的圖片
           var pic = page.renderBase64('png');
          res.send(pic);    
        }, req.post.timeout || 1000);
    }
});    

PhantomJS截圖服務腳本:點此下載。啟動方法:PhantomJS server.js [port]如不指定端口號,則默認使用8000端口:

ASP.NET對PhantomJS返回的圖像數據做處理

ASP.NET需要將PhantomJS返回的BASE64數據反編碼,得到PNG圖像數據,然后結合需要返回的圖片類型做格式轉換,並以文件的形式返回給客戶端瀏覽器,核心代碼如下:

// 提交SvgUrl到PhantomJS,讓其生成圖片
WebClient webClient = new WebClient();
NameValueCollection postValues = new NameValueCollection();
postValues.Add("url", siteUrl + svgFile);
byte[] data = webClient.UploadValues(phantomJSUrl, postValues);
// 從返回的Base64編碼中獲取圖片數據
string imageInfo = Encoding.UTF8.GetString(data);
if (!String.IsNullOrEmpty(imageInfo))
{
    data = Convert.FromBase64String(imageInfo);
    MemoryStream ms = new MemoryStream();
    ms.Write(data, 0, data.Length);
    image = Image.FromStream(ms);
    ms.Close();
}

返回Highcharts請求的圖片信息:

MemoryStream tStream = new MemoryStream();
var image = ImageHelper.SvgImageFromPhantomJs(tSvg);

string tExt = "png";
string tTypeString = "-m image/png";

switch (tType)
{
    case "image/png":
        tTypeString = "-m image/png";
        tExt = "png";
        break;
    case "image/jpeg":
        tTypeString = "-m image/jpeg";
        tExt = "jpg";
        break;
    case "application/pdf":
        tTypeString = "-m application/pdf";
        tExt = "pdf";
        break;
    case "image/svg+xml":
        tTypeString = "-m image/svg+xml";
        tExt = "svg";
        break;
}

if (tTypeString != "")
{switch (tExt)
    {
        case "jpg":
            image.Save(tStream, ImageFormat.Jpeg);
            break;
        case "png":
            image.Save(tStream, ImageFormat.Png);
            break;
        case "pdf":
            PdfWriter tWriter = null;
            Document tDocumentPdf = null;
            try
            {
                image.Save(tStream, ImageFormat.Png);
                tDocumentPdf = new Document(new Rectangle(image.Width, image.Height));
                tDocumentPdf.SetMargins(0.0f, 0.0f, 0.0f, 0.0f);
                iTextSharp.text.Image tGraph = iTextSharp.text.Image.GetInstance(tStream.ToArray());
                tGraph.ScaleToFit(image.Width, image.Height);

                tStream = new MemoryStream();
                tWriter = PdfWriter.GetInstance(tDocumentPdf, tStream);
                tDocumentPdf.Open();
                tDocumentPdf.NewPage();
                tDocumentPdf.Add(tGraph);
                tDocumentPdf.CloseDocument();
            }
            catch (Exception ex)
            {
                throw ex;
            }
            finally
            {
                tDocumentPdf.Close();
                tDocumentPdf.Dispose();
                tWriter.Close();
                tWriter.Dispose();
            }
            break;

        case "svg":
            MemoryStream tData = new MemoryStream(Encoding.UTF8.GetBytes(tSvg));
            tStream = tData;
            break;
    }
}

return tStream;

最后將tStream的圖像數據以文件的形式返回給前台:

[HttpPost]
[ValidateInput(false)]
public ActionResult Export()
{
    string siteUrl = String.Format("{0}://{1}:{2}/", Request.Url.Scheme, Request.Url.Host, Request.Url.Port);

    MemoryStream tStream = new MemoryStream();
    string tType = Request.Form["type"];
    string tSvg = Request.Form["svg"];
    string tFileName = Request.Form["filename"];
    if (String.IsNullOrEmpty(tFileName))
    {
        tFileName = "chart";
    }

    ChartHelper chartHelper = new ChartHelper();
    tStream = chartHelper.GetSvgImageFromPhantomJs(siteUrl, tType, tSvg);
    return File(tStream.ToArray(), tType, tFileName);
}

借助PhantomJS生成的圖表圖片

來一張效果圖,跟原來的對比一下:

可見漢字部分清晰了不少吧。

總結

在服務端使用PhantomJS生成圖表圖片好處就是能將圖像渲染到最佳效果(直接使用WebKit內核渲染),缺點就是速度慢了些。

服務端生成Pdf圖表可以使用iTextSharp生成。

附ASP.NET導出Highcharts的源碼:點此下載


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM