前言  

socket(套接字),Socket和ServerSocket位于java.net包中,之前虽然对socket有过一些了解,但一直都是云里雾里的,特意仔细的学习了一个socket,用socket模拟一个天气查询的功能,并且解决了几个使用socket过程中比较严重的问题。

 

最简单的客户端和服务端

服务端代码

 1 package cn.hucc.socket.server;
 2 
 3 import java.io.DataInputStream;
 4 import java.io.DataOutputStream;
 5 import java.io.IOException;
 6 import java.net.ServerSocket;
 7 import java.net.Socket;
 8 
 9 /**
10  * 
11  * @auth hucc
12  * @date 2015年10月10日
13  */
14 public class WeatherServer {
15 
16     private static final int PORT = 8888;
17 
18     public static void main(String[] args) {
19 
20         ServerSocket server = null;
21         Socket socket = null;
22         DataInputStream dataInputStream = null;
23         DataOutputStream dataOutputStream = null;
24         try {
25             server = new ServerSocket(PORT);
26             System.out.println("天气服务端已经移动,监听端口:" + PORT);
27             socket = server.accept();
28 
29             // 接受客户端请求
30             dataInputStream = new DataInputStream(socket.getInputStream());
31             String request = dataInputStream.readUTF();
32             System.out.println("from client..." + request);
33 
34             // 响应客户端
35             dataOutputStream = new DataOutputStream(socket.getOutputStream());
36             String response = "天气:晴朗,温度:36度";
37             dataOutputStream.writeUTF(response);
38 
39         } catch (IOException e) {
40             e.printStackTrace();
41         } finally {
42             try {
43                 if (dataInputStream != null) {
44                     dataInputStream.close();
45                 }
46                 if (dataOutputStream != null) {
47                     dataOutputStream.close();
48                 }
49             } catch (IOException e) {
50                 e.printStackTrace();
51             }
52         }
53     }
54 }
View Code

 服务端代码很简单,这里没有直接使用InputStream和OutputStream两个流,而是使用了DataInputStream和DataOutputStream两个类,通过readUTF()和writeUTF()两个方法免去转码的痛苦。

客户端代码

 1 package cn.hucc.socket.client;
 2 
 3 import java.io.DataInputStream;
 4 import java.io.DataOutputStream;
 5 import java.io.IOException;
 6 import java.net.Socket;
 7 
 8 /**
 9  * 
10  * @auth hucc
11  * @date 2015年10月10日
12  */
13 public class WeatherClient {
14     private static final String HOST = "127.0.0.1";
15     private static final int PORT = 8888;
16     
17     public static void main(String[] args) {
18         
19         Socket socket = null;
20         DataInputStream dataInputStream = null;
21         DataOutputStream dataOutputStream = null;
22         try {
23             socket = new Socket(HOST, PORT);
24 
25             //给服务端发送请求
26             dataOutputStream = new DataOutputStream(socket.getOutputStream());
27             String request = "北京";
28             dataOutputStream.writeUTF(request);
29             
30             dataInputStream = new DataInputStream(socket.getInputStream());
31             String response = dataInputStream.readUTF();
32             System.out.println(response);
33         
34         } catch (IOException e) {
35             e.printStackTrace();
36         }finally{
37             try {
38                 if(dataInputStream != null){
39                     dataInputStream.close();
40                 }
41                 if(dataOutputStream != null){
42                     dataOutputStream.close();
43                 }
44                 if(socket != null){
45                     socket.close();
46                 }
47             } catch (IOException e) {
48                 e.printStackTrace();
49             }
50             
51         }
52     }
53 }
View Code

 

运行结果

客户端运行结果:

服务端运行结果:

 

结果分析

客户端和服务端都运行起来了,并且达到了天气查询的效果,但是服务端只服务了一次就停止了,这明显不符合需求,服务端应该响应完客户端之后,继续监听8888端口,等待下一个客户端的连接。

 

让服务端一直提供服务

将服务端的代码写入死循环中,一直监听客户端的请求。修改服务端的代码:

 1 public static void main(String[] args) throws IOException {
 2 
 3         ServerSocket server = null;
 4         Socket socket = null;
 5         DataInputStream dataInputStream = null;
 6         DataOutputStream dataOutputStream = null;
 7         server = new ServerSocket(PORT);
 8         System.out.println("天气服务端已经移动,监听端口:" + PORT);
 9         while(true){
10             try {
11                 socket = server.accept();
12                 
13                 // 接受客户端请求
14                 dataInputStream = new DataInputStream(socket.getInputStream());
15                 String request = dataInputStream.readUTF();
16                 System.out.println("from client..." + request);
17                 
18                 // 响应客户端
19                 dataOutputStream = new DataOutputStream(socket.getOutputStream());
20                 String response = "天气:晴朗,温度:36度";
21                 dataOutputStream.writeUTF(response);
22 
23             } catch (IOException e) {
24                 e.printStackTrace();
25             } finally {
26                 try {
27                     if (dataInputStream != null) {
28                         dataInputStream.close();
29                     }
30                     if (dataOutputStream != null) {
31                         dataOutputStream.close();
32                     }
33                 } catch (IOException e) {
34                     e.printStackTrace();
35                 }
36             }
37         }
38     }
View Code

 

通过while(true)死循环,服务端一直监听8888端口,由于socket是阻塞的,只有服务端完成了当前客户端的响应,才会继续处理下一个客户端的响应。这样一直让主线线程去处理socket请求不合适,因此需要为服务端加上多线程功能,同时处理多个socket请求。

 

给服务端加上多线程

修改代码,将服务端的socket处理抽取出来,并且封装到Runnable接口的run方法中。

 1 package cn.hucc.socket.server;
 2 
 3 import java.io.DataInputStream;
 4 import java.io.DataOutputStream;
 5 import java.io.IOException;
 6 import java.net.Socket;
 7 
 8 /**
 9  * 
10  * @auth hucc
11  * @date 2015年10月10日
12  */
13 public class WeatherThread extends Thread {
14     
15     private Socket socket;
16     
17     public WeatherThread(Socket socket){
18         this.socket = socket;
19     }
20     
21     public void run() {
22         
23         DataInputStream dataInputStream = null;
24         DataOutputStream dataOutputStream = null;
25         try {
26             // 接受客户端请求
27             dataInputStream = new DataInputStream(socket.getInputStream());
28             String request = dataInputStream.readUTF();
29             System.out.println("from client..." + request+" 当前线程:"+Thread.currentThread().getName());
30             
31             // 响应客户端
32             dataOutputStream = new DataOutputStream(socket.getOutputStream());
33             String response = "天气:晴朗,温度:36度";
34             dataOutputStream.writeUTF(response);
35 
36         } catch (IOException e) {
37             e.printStackTrace();
38         } finally {
39             try {
40                 if (dataInputStream != null) {
41                     dataInputStream.close();
42                 }
43                 if (dataOutputStream != null) {
44                     dataOutputStream.close();
45                 }
46             } catch (IOException e) {
47                 e.printStackTrace();
48             }
49         }
50     }
51 }
View Code

修改服务端,添加多线程功能

 1 package cn.hucc.socket.server;
 2 
 3 import java.io.IOException;
 4 import java.net.ServerSocket;
 5 import java.net.Socket;
 6 
 7 /**
 8  * 
 9  * @auth hucc
10  * @date 2015年10月10日
11  */
12 public class WeatherServer {
13 
14     private static final int PORT = 8888;
15 
16     public static void main(String[] args) throws IOException {
17 
18         ServerSocket server = null;
19         Socket socket = null;
20         server = new ServerSocket(PORT);
21         System.out.println("天气服务端已经移动,监听端口:" + PORT);
22         while(true){
23                 socket = server.accept();
24                 new WeatherThread(socket).start();
25         }
26     }
27 }
View Code

此时服务端已经拥有多线程处理能力了,运行结果如下图:

 

弊端分析

尽管服务端现在已经有了多线程处理能力,但是通过运行结果,我们可以看到,服务端每次接收到客户端的请求后,都会创建一个新的线程去处理,而jvm的线程数量过多是,服务端处理速度会变慢。而且如果并发较高的话,瞬间产生的线程数量也会比较大,因此,我们需要再给服务端加上线程池的功能。

 

给服务端加上线程池功能

使用java.util.concurrent.Executor类就可以创建一个简单的线程池,代码如下:

 1 package cn.hucc.socket.server;
 2 
 3 import java.io.IOException;
 4 import java.net.ServerSocket;
 5 import java.net.Socket;
 6 import java.util.concurrent.Executor;
 7 import java.util.concurrent.Executors;
 8 
 9 /**
10  * 
11  * @auth hucc
12  * @date 2015年10月10日
13  */
14 public class WeatherServer {
15 
16     private static final int PORT = 8888;
17 
18     public static void main(String[] args) throws IOException {
19 
20         ServerSocket server = null;
21         Socket socket = null;
22         server = new ServerSocket(PORT);
23         System.out.println("天气服务端已经移动,监听端口:" + PORT);
24         
25         //FixedThreadPool最多开启3(参数)个线程,多余的线程会存储在队列中,等线程处理完了
26         //再从队列中获取线程继续处理
27         Executor executor = Executors.newFixedThreadPool(3);
28         while(true){
29                 socket = server.accept();
30                 executor.execute(new WeatherThread(socket));
31         }
32     }
33 }
View Code

Executor一共有4种线程池实现,这里使用了FixedThreadPool最多开启3(参数)个线程,多余的线程会存储在队列中,等线程处理完了再从队列中获取,继续处理。这样的话无论并发量多大,服务端只会最多3个线程进行同时处理,使服务端的压力不会那么大。

 

运行结果:

通过运行结果,可以看到线程只开了1,2,3三个线程。

 

到这里,socket的简易教程便结束了。O(∩_∩)O~~