带线程池的socket客户端与服务端
前言
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 }
服务端代码很简单,这里没有直接使用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 }
运行结果
客户端运行结果:

服务端运行结果:

结果分析
客户端和服务端都运行起来了,并且达到了天气查询的效果,但是服务端只服务了一次就停止了,这明显不符合需求,服务端应该响应完客户端之后,继续监听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 }
通过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 }
修改服务端,添加多线程功能
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 }
此时服务端已经拥有多线程处理能力了,运行结果如下图:

弊端分析
尽管服务端现在已经有了多线程处理能力,但是通过运行结果,我们可以看到,服务端每次接收到客户端的请求后,都会创建一个新的线程去处理,而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 }
Executor一共有4种线程池实现,这里使用了FixedThreadPool最多开启3(参数)个线程,多余的线程会存储在队列中,等线程处理完了再从队列中获取,继续处理。这样的话无论并发量多大,服务端只会最多3个线程进行同时处理,使服务端的压力不会那么大。
运行结果:

通过运行结果,可以看到线程只开了1,2,3三个线程。
到这里,socket的简易教程便结束了。O(∩_∩)O~~
