消息推送也是客戶端和服務器鏈接而後進行交互的一種形式,可是不一樣於HTTP的鏈接,這種鏈接須要長時間的進行,當有消息時能夠及時推送到客戶端。除此以外還有多個用戶,可能須要針對其身份進行不一樣的推送等等要求。而這種鏈接的形式在Java中可使用Socket進行實現。java
1、初版:服務器
一、首先是服務器部分,重要的操做說明網絡
①使用ServerSocket能夠開啓服務器上的一個端口進行鏈接監聽,相似於服務器監聽80端口。框架
②使用accept(),阻塞式的等待客戶端的接入。接入成功時返回鏈接的Socket對象。經過該對象能夠進行數據的交換。socket
1 import java.io.BufferedReader; 2 import java.io.BufferedWriter; 3 import java.io.InputStreamReader; 4 import java.io.OutputStreamWriter; 5 import java.net.ServerSocket; 6 import java.net.Socket; 7 8 public class Service { 9 public static void main(String[] args) { 10 Service service=new Service(); 11 service.start(); 12 } 13 14 private void start() { 15 ServerSocket socketService=null; 16 Socket socket=null; 17 BufferedReader reader=null; 18 BufferedWriter writer=null; 19 try { 20 //開啓一個Socket服務器,監聽9898端口 21 socketService=new ServerSocket(9898); 22 System.out.println("service start..."); 23 //等待客戶端連入,一直阻塞直到接入,返回鏈接Socket對象 24 socket=socketService.accept(); 25 System.out.println("client connection..."); 26 //如下就是數據交換 27 reader=new BufferedReader(new InputStreamReader(socket.getInputStream())); 28 writer=new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())); 29 String message=null; 30 while(!(message=reader.readLine()).equals("bye")){ 31 System.out.println(message); 32 writer.write(socket.getLocalAddress()+":"+message+"\n"); 33 writer.flush(); 34 } 35 } catch (Exception e) { 36 e.printStackTrace(); 37 }finally{ 38 try { 39 reader.close(); 40 writer.close(); 41 socket.close(); 42 socketService.close(); 43 } catch (Exception e) { 44 e.printStackTrace(); 45 } 46 } 47 } 48 }
二、對於客戶端來講比較簡單:ide
①指定服務器和端口來建立Socket鏈接,性能
②使用該Socket對象來進行數據傳輸便可,這裏是將控制檯上的輸入內容傳遞至服務器。須要注意的是讀取操做是以"\n「爲結束標記的,因此須要傳輸時須要加上,不然不被認爲是結束。測試
1 import java.io.BufferedReader; 2 import java.io.BufferedWriter; 3 import java.io.InputStreamReader; 4 import java.io.OutputStreamWriter; 5 import java.net.Socket; 6 7 public class Client { 8 public static void main(String[] args) { 9 //這裏建立一個Client對象而不是直接在main()操做,是能夠是使用的變量等沒必要是static類型的 10 Client client=new Client(); 11 client.start(); 12 } 13 14 private void start() { 15 BufferedReader readFromSys=null; 16 BufferedReader readFromService=null; 17 BufferedWriter writeToService=null; 18 Socket socket=null; 19 try { 20 //傳入地址和端口號,和服務器創建鏈接 21 socket=new Socket("127.0.0.1",9898); 22 //如下是數據傳輸部分 23 readFromSys=new BufferedReader(new InputStreamReader(System.in)); 24 readFromService=new BufferedReader(new InputStreamReader(socket.getInputStream())); 25 writeToService=new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())); 26 27 String message; 28 String responce; 29 while(!(message=readFromSys.readLine()).equals("bye")){ 30 //傳輸數據須要以 \n 結尾,不然不被認爲是結束。客戶端和服務器端都同樣 31 writeToService.write(message+"\n"); 32 writeToService.flush(); 33 responce=readFromService.readLine(); 34 System.out.println(responce); 35 } 36 } catch (Exception e) { 37 e.printStackTrace(); 38 }finally{ 39 try { 40 writeToService.close(); 41 readFromService.close(); 42 readFromSys.close(); 43 socket.close(); 44 } catch (Exception e) { 45 e.printStackTrace(); 46 } 47 } 48 49 } 50 }
這一版已經成功的進行和客戶端和服務器端的鏈接,可是有一些問題,下面是對其的總結:spa
①服務器端不能主動的向客戶端進行消息的傳遞。.net
②客戶端中的只能在發送一條數據以後的服務器響應時才能獲取數據,總結爲不能及時獲取服務器端消息。
以上兩條說明這種操做是徹底不能做爲一種推送服務的。
③服務器端只能處理一個用戶的請求,即只能和一個客戶端進行鏈接。
2、第二版:這是對初版中存在的問題進行解決
一、解決①問題,即服務器端不能主動發送消息的問題,思路是經過Socket鏈接的輸出流進行數據的傳遞便可。
1 import java.io.BufferedReader; 2 import java.io.BufferedWriter; 3 import java.io.InputStreamReader; 4 import java.io.OutputStreamWriter; 5 import java.net.ServerSocket; 6 import java.net.Socket; 7 import java.util.Timer; 8 import java.util.TimerTask; 9 10 public class Service { 11 public static void main(String[] args) { 12 Service service=new Service(); 13 service.start(); 14 } 15 16 private void start() { 17 ServerSocket socketService=null; 18 Socket socket=null; 19 BufferedReader reader=null; 20 BufferedWriter writer=null; 21 try { 22 //開啓一個Socket服務器,監聽9898端口 23 socketService=new ServerSocket(9898); 24 System.out.println("service start..."); 25 26 socket=socketService.accept(); 27 System.out.println("client connection..."+socket.hashCode()); 28 29 try { 30 reader=new BufferedReader(new InputStreamReader(socket.getInputStream())); 31 writer=new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())); 32 33 //!!!服務器向客戶端發送消息 34 sendMessage(writer); 35 36 String message=null; 37 while(!(message=reader.readLine()).equals("bye")){ 38 System.out.println(message); 39 writer.write(socket.getLocalAddress()+":"+message+"\n"); 40 writer.flush(); 41 } 42 } catch (Exception e) { 43 e.printStackTrace(); 44 }finally{ 45 try { 46 socket.close(); 47 socketService.close(); 48 } catch (Exception e) { 49 e.printStackTrace(); 50 } 51 } 52 } 53 54 private void sendMessage(final BufferedWriter writer) { 55 //這裏簡單開啓一個定時任務,每一個幾秒向客戶端發送一條消息,進行模擬操做。 56 new Timer().schedule(new TimerTask() { 57 @Override 58 public void run() { 59 try { 60 writer.write("消息發送測試!\n"); 61 writer.flush(); 62 } catch (Exception e) { 63 e.printStackTrace(); 64 } 65 } 66 }, 1000, 3000); 67 } 68 }
二、解決②問題,即客戶端不能及時讀取服務器端消息的問題,思路是對鏈接Socket的輸入流進行監聽,一旦有輸入當即進行處理。
1 import java.io.BufferedReader; 2 import java.io.BufferedWriter; 3 import java.io.InputStreamReader; 4 import java.io.OutputStreamWriter; 5 import java.net.Socket; 6 7 public class Client { 8 public static void main(String[] args) { 9 //這裏建立一個Client對象而不是直接在main()操做,是能夠是使用的變量等沒必要是static類型的 10 Client client=new Client(); 11 client.start(); 12 } 13 14 private void start() { 15 BufferedReader readFromSys=null; 16 BufferedReader readFromService=null; 17 BufferedWriter writeToService=null; 18 Socket socket=null; 19 try { 20 //傳入地址和端口號,和服務器創建鏈接 21 socket=new Socket("127.0.0.1",9898); 22 //如下是數據傳輸部分 23 readFromSys=new BufferedReader(new InputStreamReader(System.in)); 24 readFromService=new BufferedReader(new InputStreamReader(socket.getInputStream())); 25 writeToService=new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())); 26 27 String message; 28 29 //用於對服務器端輸入的數據流進行監聽 30 socketInputStreamListener(readFromService); 31 32 while(!(message=readFromSys.readLine()).equals("bye")){ 33 //傳輸數據須要以 \n 結尾,不然不被認爲是結束。客戶端和服務器端都同樣 34 writeToService.write(message+"\n"); 35 writeToService.flush(); 36 } 37 } catch (Exception e) { 38 e.printStackTrace(); 39 }finally{ 40 try { 41 writeToService.close(); 42 readFromService.close(); 43 readFromSys.close(); 44 socket.close(); 45 } catch (Exception e) { 46 e.printStackTrace(); 47 } 48 } 49 50 } 51 52 private void socketInputStreamListener(final BufferedReader readFromService) { 53 //一、首先是須要監聽,因此是長時間操做,可是不能阻塞主線程的操做,因此使用子線程來進行處理 54 new Thread(new Runnable(){ 55 @Override 56 public void run() { 57 try { 58 String responce; 59 while(!(responce=readFromService.readLine()).equals(null)){ 60 System.out.println(responce); 61 } 62 } catch (Exception e) { 63 e.printStackTrace(); 64 } 65 } 66 }).start(); 67 } 68 }
三、解決③問題,即服務器只能與一個客戶端進行鏈接的問題,思路是循環調用accept(),該方法用於阻塞式的等待客戶端的鏈接,而後將已經創建的鏈接的任務放在子線程中進行處理便可。而這裏的鏈接任務就是數據的傳遞部分。
1 import java.io.BufferedReader; 2 import java.io.BufferedWriter; 3 import java.io.InputStreamReader; 4 import java.io.OutputStreamWriter; 5 import java.net.ServerSocket; 6 import java.net.Socket; 7 import java.util.Timer; 8 import java.util.TimerTask; 9 10 public class Service { 11 public static void main(String[] args) { 12 Service service=new Service(); 13 service.start(); 14 } 15 16 private void start() { 17 ServerSocket socketService=null; 18 Socket socket=null; 19 try { 20 //開啓一個Socket服務器,監聽9898端口 21 socketService=new ServerSocket(9898); 22 System.out.println("service start..."); 23 24 // 25 while(true){ 26 socket=socketService.accept(); 27 System.out.println("client connection..."+socket.hashCode()); 28 exectureConnectRunnable(socket); 29 } 30 } catch (Exception e) { 31 e.printStackTrace(); 32 }finally{ 33 try { 34 socket.close(); 35 socketService.close(); 36 } catch (Exception e) { 37 e.printStackTrace(); 38 } 39 } 40 } 41 42 //這裏的工做必須在子線程中工做,由於下面有一段死循環操做,若是不在子線程中則會阻塞主線程,不能和其餘客戶端進行鏈接。 43 private void exectureConnectRunnable(final Socket socket) { 44 new Thread(new Runnable(){ 45 @Override 46 public void run() { 47 BufferedReader reader=null; 48 BufferedWriter writer=null; 49 //如下就是數據交換 50 try { 51 reader=new BufferedReader(new InputStreamReader(socket.getInputStream())); 52 writer=new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())); 53 54 //服務器向客戶端發送消息 55 sendMessage(writer); 56 57 String message=null; 58 while(!(message=reader.readLine()).equals("bye")){ 59 System.out.println(message); 60 writer.write(socket.getLocalAddress()+":"+message+"\n"); 61 writer.flush(); 62 } 63 } catch (Exception e) { 64 e.printStackTrace(); 65 }finally{ 66 try { 67 reader.close(); 68 writer.close(); 69 } catch (Exception e) { 70 e.printStackTrace(); 71 } 72 } 73 } 74 }).start(); 75 } 76 77 private void sendMessage(final BufferedWriter writer) { 78 //這裏簡單開啓一個定時任務,每一個幾秒向客戶端發送一條消息,進行模擬操做。 79 new Timer().schedule(new TimerTask() { 80 @Override 81 public void run() { 82 try { 83 writer.write("消息發送測試!\n"); 84 writer.flush(); 85 } catch (Exception e) { 86 e.printStackTrace(); 87 } 88 } 89 }, 1000, 3000); 90 } 91 }
到此爲止,使用原生的Socket進行消息推送的基本嘗試已經完成,可是其仍是有不少問題,例如:
一、網絡操做都是阻塞式實現的,因此不得不採用子線程來進行處理,當鏈接的用戶過多時,性能就稱爲一個極大的瓶頸。
二、對各類流的處理等等操做。
在Java1.4版本以後就引入了nio包,即new IO,用來進行改善,可是操做比較麻煩。而已經有了大神對底層操做的封裝框架,如Mina和Netty等,下一部分就是對Mina的體驗使用。