消息推送也是客户端和服务器连接然后进行交互的一种形式,但是不同于HTTP的连接,这种连接需要长时间的进行,当有消息时可以及时推送到客户端。除此之外还有多个用户,可能需要针对其身份进行不同的推送等等要求。而这种连接的形式在Java中可以使用Socket进行实现。
一、第一版:
1、首先是服务器部分,重要的操作说明
①使用ServerSocket可以开启服务器上的一个端口进行连接监听,类似于服务器监听80端口。
②使用accept(),阻塞式的等待客户端的接入。接入成功时返回连接的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 }
2、对于客户端来说比较简单:
①指定服务器和端口来创建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 }
这一版已经成功的进行和客户端和服务器端的连接,但是有一些问题,下面是对其的总结:
①服务器端不能主动的向客户端进行消息的传递。
②客户端中的只能在发送一条数据之后的服务器响应时才能获取数据,总结为不能及时获取服务器端消息。
以上两条说明这种操作是完全不能作为一种推送服务的。
③服务器端只能处理一个用户的请求,即只能和一个客户端进行连接。
二、第二版:这是对第一版中存在的问题进行解决
1、解决①问题,即服务器端不能主动发送消息的问题,思路是通过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 }
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.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 //1、首先是需要监听,所以是长时间操作,但是不能阻塞主线程的操作,所以使用子线程来进行处理 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 }
3、解决③问题,即服务器只能与一个客户端进行连接的问题,思路是循环调用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进行消息推送的基本尝试已经完成,但是其还是有很多问题,例如:
1、网络操作都是阻塞式实现的,所以不得不采用子线程来进行处理,当连接的用户过多时,性能就称为一个极大的瓶颈。
2、对各种流的处理等等操作。
在Java1.4版本之后就引入了nio包,即new IO,用来进行改善,但是操作比较麻烦。而已经有了大神对底层操作的封装框架,如Mina和Netty等,下一部分就是对Mina的体验使用。