图片播放技术总结
1 概述
由于工作的項目上的需求,需要在瀏覽器上不間斷的播放圖片,即像播放視頻一樣播放圖片。
后端支持采用Java實現,需要用Java編寫一個Http服務器,并提供WebSocket服務。前后端通過Http鏈接或WebSocket提供圖片瀏覽服務,前端采用JS輪詢或WebSocket推送的方式獲取圖片,瀏覽器顯示圖片有兩種方式:一種是采用連續切換圖片源,實現播放效果;另一種采用將圖片畫在canvas上面,實現播放。
要完成這個功能涉及到以下技術:
- Http服務器的實現
- 高速的讀文件
- WebSocket原理及實現
- 基于瀏覽器pull方式的http資源獲取
- 基于服務器端push方式的http資源獲取
- JS播放圖片幀的性能
2 技術分析
2.1 Http服務器的實現
實現HTTP服務器比較容易,實現方式也有如下多種:
- 基于jdk中com.sun包下面的HttpServer來實現。(不推薦,com.sun不在java規范內,jdk升級可能會不兼容)
- 基于jetty或tomcat的嵌入式包來實現。此方式基于Servlet規范來實現的,較簡單且易于理解。
- 基于netty的方式實現。性能好,需要對NIO有了解,編程難度相對大一些。
- 基于vert.x的實現。這種方式底層還是采用netty實現,較簡單,但是也需要熟悉vert.x的編程模型。
我們采用jetty的方式實現,基于servlet3.0規范,可以支持異步請求方式。代碼如下:
public static void main(String[] args){Server server = new Server();ServerConnector connector = new ServerConnector(server);connector.setPort(8080);server.addConnector(connector);ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);context.setContextPath("/");context.addServlet(new ServletHolder(new HelloServlet()), "/hello"); server.setHandler(context);try {// Initialize javax.websocket layerServerContainer wscontainer = WebSocketServerContainerInitializer.configureContext(context);// Add WebSocket endpoint to javax.websocket layerwscontainer.addEndpoint(EventSocket.class);server.start();server.dump(System.err);server.join();} catch (Throwable t){t.printStackTrace(System.err);} }2.2 高速的讀文件
關于Java高速讀取文件可參考這篇文章:How to read files quickly
這篇文章得出四個結論:
- 為了減少I/O操作,每次應該讀一個byte數組,而不是一個byte字節,8K的byte數組就是一個好的選擇。
- 為了減少方法調用的開銷,每次應該獲取一個byte數組的數據,而不是一個byte字節。
- 為了減少線程同步鎖的開銷,要么減少線程同步方法的調用,要么采用非線程安全的類,如:FileChannel 和MappedByteBuffer。
- 為了減少在JVM/OS、internal buffers和應用程序數組之間的數據拷貝,要么使用帶有內存映射的FileChannel類,要么使用a direct or wrapped array ByteBuffer.
下面提高兩種高速讀取文件方法:
for (String filePath : fileList){try(FileChannel ch = new RandomAccessFile(filePath, "r").getChannel()){int size = (int) ch.size();MappedByteBuffer buf = ch.map(MapMode.READ_ONLY, 0, size);// 處理buf....} catch (IOException e) {e.printStackTrace();} } for (String filePath : fileList){try (SeekableByteChannel sbc = Files.newByteChannel(Paths.get(filePath), StandardOpenOption.READ)) {ByteBuffer buf = ByteBuffer.allocate(10);// Read the bytes with the proper encoding for this platform. If// you skip this step, you might see something that looks like// Chinese characters when you expect Latin-style characters.//String encoding = System.getProperty("file.encoding");while (sbc.read(buf) > 0) {buf.rewind();// 處理buf...//System.out.print(Charset.forName(encoding).decode(buf));buf.flip();}} catch (IOException x) {System.out.println("caught exception: " + x);} }2.3 WebSocket原理及實現
WebSocket的原理以及與Http區別可以參考:WebSocket與http的區別,以及它的原理,總體來說,原理及區別如下:
- WebSocket和Http協議沒有太大的關系,WS只是借助Http實現了第一次握手,之后從http協議upgrade為ws://協議。
- WS是持久性連接(類似socket),而HTTP的短連接、長連接都不是持久的。
- WS協議是支持全雙工的,可以pull,亦可以push。
用Java實現WebSocket服務端:
@ClientEndpoint @ServerEndpoint(value="/events/") public class EventSocket{private static int DEFAULT_BUFFER_SIZE = 128 * 1024;// 8192private byte[] bytes;@OnOpenpublic void onWebSocketConnect(Session session, EndpointConfig config) {session.setMaxBinaryMessageBufferSize(DEFAULT_BUFFER_SIZE);}@OnMessagepublic void onWebSocketText(Session session, String message) throws Exception{System.out.println("Received TEXT message: " + message);bytes = ... // 讀取圖片文件字節// 發送圖片文件session.getAsyncRemote().sendBinary( ByteBuffer.wrap( this.bytes ) );}@OnClosepublic void onWebSocketClose(Session session, CloseReason reason){System.out.println("Socket Closed: " + reason);}@OnErrorpublic void onWebSocketError(Session session, Throwable cause){cause.printStackTrace(System.err);} }2.4 瀏覽器并發請求與長連接
瀏覽器請求一般都是拉取服務器的資源,而請求方式分為短連接和長連接兩種,這篇文章介紹很清楚:HTTP的長連接和短連接
瀏覽器對后端資源的請求都是并發的執行的,不同的瀏覽器并發連接數不同。現在大多數瀏覽器都支持http1.1協議,默認都會開啟keep-alive,支持長連接。在瀏覽器對后端的資源發出請求,在開啟keep-alive情況下,都會復用連接通道。如果是不間斷的下載圖片,應該使用的是長連接通道復用功能。
2.5 Web服務器Push技術
實現服務器端Push有以下幾種方式:
- Ajax輪詢。采用setInterval方法不停的調用
- Ajax長輪詢。俗稱Comet方式,不需要重復建立連接,沒有響應就一直等,等到才關閉連接。
- WebSocket
- server-sent-events
Ajax輪詢原理還是pull的方式,不算真正的push,但是對一些老版本的瀏覽器是適用的。WebSocket優點是支持全雙工、可跨域。server-sent-server實現簡單,但只支持server到client單向傳輸,且IE系列都不支持。詳細內容參考: web服務器端推送技術簡介
除了上面一些方法外,還有一些其他方式,如:Flash XML Socket, Java Applet等非主流。
而在本案例中,如果采用WebSocket傳送圖片,可實現真正的服務器端不間斷的推送圖片數據,但是如果要實現并發傳送,必須自己在瀏覽器端來實現,否則,僅僅單連接的情況下不一定比瀏覽器的并發連接快。
2.6 JS播放圖片幀的性能
JS播放圖片有多種方式,如:
- 采用標簽,不停改變img的src屬性,實現播放。
- 采用Html5的Canvas,將Image對象畫在Canvas上,實現播放。
- 將圖片設置為Div的背景,不停的更換背景,實現播放。
采用標簽方式實現如下:
(function() {var i = 0;var pics = [ "andy_white.jpg", "andy_black.jpg" ];var el = document.getElementById('img_to_flip'); // el doesn't changefunction toggle() {el.src = pics[i]; // set the imagei = (i + 1) % pics.length; // update the counter}setInterval(toggle, 2000); })();這種方式下瀏覽器CPU占用率非常高,在IE11和Chrome下,i3的CPU(T440P)占用都在60%左右,內存占用較少,大約在100M左右。CPU的消耗主要在瀏覽器對圖片的渲染上。
用Canvas替代標簽,CPU占用方面,IE11仍然占用那么高,Chrome能降一半。更換背景的方式沒有實驗。基于以上,采用Canvas的方式是一種比較好的選擇。
aaaa
bbbb
轉載于:https://www.cnblogs.com/gjhuai/p/6807492.html
總結