Android 客戶端和 web服務器通信


本篇簡單介紹Android客戶端和web服務器使用socket進行通訊,向客戶端發送文件的demo

socket

套接字使用TCP提供了兩台計算機之間的通信機制。客戶端創建一個套接字,並嘗試連接服務端的嵌套字。當連接建立時,服務器會創建一個 Socket 對象。客戶端和服務器現在可以通過對 Socket 對象的寫入和讀取來進行通信。

java.net.Socket類代表一個套接字,並且 java.net.ServerSocket類為服務器程序提供了一種來監聽客戶端,並與他們建立連接的機制。

TCP 是一個雙向的通信協議,因此數據可以通過兩個數據流在同一時間發送

服務端

為了實現向客戶端發送文件,我們基於前面的jFinal 文件上傳 來完成。


public class ServerUtils {

	private volatile static ServerUtils serverInstance;

	private static ServerSocket serverSocket;

	private ServerUtils() {
	}

	public static ServerUtils getServerInstance() {

		if (serverInstance == null) {
			synchronized (ServerUtils.class) {
				if (serverInstance == null) {
					serverInstance = new ServerUtils();
				}
			}
		}
		return serverInstance;
	}

	public void init(int port) {
		try {
			if (serverSocket == null) {
				serverSocket = new ServerSocket(port);
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	public static ServerSocket getServerSocket() {
		return serverSocket;
	}

     /**
       *  發送文件線程
       **/
	public static class SendThread implements Runnable {
		static Socket socket;
		File file;
		static FileInputStream fis;
		static DataOutputStream dos;

		public SendThread(File file) {
			this.file = file;
		}

		@Override
		public void run() {

			try {
                 // 監聽並接受到此嵌套字的連接 (該方法會阻塞等待,直到客戶端連到服務端的指定端口)
				socket = getServerSocket().accept();
              
				// 上傳的模型文件
				File getFile = file;
				fis = new FileInputStream(getFile);

				// 獲取嵌套字的輸出流
				dos = new DataOutputStream(socket.getOutputStream());
                  // 嵌套字的輸入流
				//dis = new DataInputStream(socket.getInputStream());
              
				// 模型名稱和大小
				dos.writeUTF(getFile.getName());
				dos.flush();
				dos.writeLong(getFile.length());
				dos.flush();

				byte[] bytes = new byte[1024];
				int length = 0;

				while ((length = fis.read(bytes, 0, bytes.length)) != -1) {
					dos.write(bytes, 0, length);
					dos.flush();
				}

			} catch (IOException e) {
				e.printStackTrace();

			} finally {
				if (fis != null)
					fis.close();
				if (dos != null)
					dos.close();
				if (socket != null) {
					socket.close();
				}
			}

		}
	}

}

UploadController:



private static ThreadPoolExecutor threadPool;
private static SendThread sendThread;

// jfinal 獲取 Web Uploader 上傳的文件 
final File getFile = getFile().getFile();

threadPool = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());

// 初始化 ServerSocket
ServerUtils.getServerInstance().init(port);

sendThread = new SendThread(getFile);

// 創建一個線程 向客戶端發送文件
threadPool.submit(sendThread);


客戶端



   final Socket socket = new Socket();
        final ThreadPoolExecutor threadPool = new ThreadPoolExecutor(4, 4,
                0L, TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<Runnable>());

        threadPool.submit(new Runnable() {
            @Override
            public void run() {

                try {
                    socket.connect(new InetSocketAddress("192.168.0.100", 6001));
                    // 獲取嵌套字輸入流
                    final DataInputStream dis = new DataInputStream(socket.getInputStream());

                    // 獲取文件名
                    String fileName = dis.readUTF();
                    // 獲取文件大小
                    final long fileLength = dis.readLong();
                    File file = new File(App.RECEIVE_PATH + File.separatorChar + fileName);
                    final FileOutputStream fos = new FileOutputStream(file);

                    threadPool.execute(new Runnable() {
                        @Override
                        public void run() {
                            try {
                                int length = 0;
                                byte[] bytes = new byte[1024];

                                while ((length = dis.read(bytes, 0, bytes.length)) != -1) {
                                    fos.write(bytes, 0, length);
                                    fos.flush();

                                }
                            } catch (IOException e) {
                                e.printStackTrace();
                            }
                        }

                    });

                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        });

應用場景

這里只是一個最基本的上傳demo,每次用戶web頁面上傳文件,服務端都會開啟一個線程來發送文件。客戶端連接並接收上傳的文件,文件發送結束 關閉客戶端。

關於socket 優化以及深入了解

可參見Java Socket編程基礎及深入講解

NIO

上述的demo是基於阻塞式api的,當程序輸入或輸出操作后,在操作返回前會一直阻塞線程。服務器需要為每一個客戶端提供一個線程進行處理。當有大量的客戶端的時候,性能比較底下。

JDK1.4 開始提供了 NIO(new io)開發高性能的服務器,可以使服務器使用一個或有限幾個線程同時處理所有客戶端

IO和NIO的區別

面向流和面向緩沖區

  • io面向流,從上面的demo可以看出所有輸入輸出都是通過流來完成。每次從流中讀出數據它們沒有被緩存在任何地方,直到被讀完。需要需要前后移動數據需要先將數據緩存到一個緩沖區。

  • nio面向緩沖區,面向塊。使用channel(通道)模擬傳統輸入輸出流,所有從channel讀取的數據或是發送的數據都需要先放到buffer(緩沖)中。程序不能直接對channel直接操作。使數據操作更加靈活

阻塞式和非阻塞式

  • 阻塞式如上面所述,需要阻塞直到數據完全讀取寫入。

  • 非阻塞式,一個線程從某通道發送請求讀取數據,但是它僅能得到目前可用的數據,如果目前沒有數據可用時,就什么都不會獲取,而不是保持線程阻塞,所以直至數據變的可以讀取之前,該線程可以繼續做其他的事情。 非阻塞寫也是如此。一個線程請求寫入一些數據到某通道,但不需要等待它完全寫入,這個線程同時可以去做別的事情。 線程通常將非阻塞io的空閑時間用於在其它通道上執行io操作,所以一個單獨的線程現在可以管理多個輸入和輸出通道。

Selector

nio通過Selector(選擇器)來實現一個線程對多個channel的管理。

服務器上的所有channel都需要向Selector注冊,而Selector負責監視這些channel的狀態。當有任意個channel有可用的io操作時,Selectorselect()方法會返回一個大於0的值,表示當前有多少channel有可用的io操作。可以通過selectedKeys()獲取channel集合。所以只需要一個線程不斷調用select()方法即可。

Tips:無可用的channel時,調用select()方法的線程會被阻塞。

Netty

Netty 是業界流行的 NIO 框架之一,它的健壯性、功能、性能、可定制性和可擴展性在同類框架中都說首屈一指的,也已經得到了成百上千商用項目的驗證。

優點有很多..... 反正就是很好用 很耐用,而且開發門檻低。.

Netty Api


免責聲明!

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



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