本篇簡單介紹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 優化以及深入了解
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操作時,Selector的select()方法會返回一個大於0的值,表示當前有多少channel有可用的io操作。可以通過selectedKeys()獲取channel集合。所以只需要一個線程不斷調用select()方法即可。
Tips:無可用的channel時,調用select()方法的線程會被阻塞。
Netty
Netty 是業界流行的 NIO 框架之一,它的健壯性、功能、性能、可定制性和可擴展性在同類框架中都說首屈一指的,也已經得到了成百上千商用項目的驗證。
優點有很多..... 反正就是很好用 很耐用,而且開發門檻低。.
