前言
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~~