消息推送也是客户端和服务器连接然后进行交互的一种形式,但是不同于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的体验使用。

posted on 2016-05-31 17:58  a发愣的树熊  阅读(1078)  评论(1编辑  收藏  举报