Socket编程基础
一:socket又称“套接字”,相当于“排插”,一一对应,一个客户端对应一个服务端,原始的socket通信为,每当一个客户端socket新接入,服务端serverSocket就得就得新建一个线程。缺点:客户端一多就容易搞崩服务端。(为传统的BIO编程)
例子1:
服务端代码:
package bhz.bio; import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; // 服务端 public class Server { final static int PROT = 8765; public static void main(String[] args) { ServerSocket server = null; try { server = new ServerSocket(PROT); System.out.println(" server start .. "); //进行阻塞 Socket socket = server.accept(); //新建一个线程执行客户端的任务:每来一个Socket就新建一个线程,一多就容易将服务器撑爆。 new Thread(new ServerHandler(socket)).start(); } catch (Exception e) { e.printStackTrace(); } finally { if(server != null){ try { server.close(); } catch (IOException e) { e.printStackTrace(); } } server = null; } } }
服务端接收请求后进行run()响应
package bhz.bio; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.Socket; // 服务端接收请求响应后的具体操作 public class ServerHandler implements Runnable{ private Socket socket ; public ServerHandler(Socket socket){ this.socket = socket; } @Override public void run() { BufferedReader in = null; PrintWriter out = null; try { // 接收客户端数据 in = new BufferedReader(new InputStreamReader(this.socket.getInputStream())); // 向客户端响应数据 out = new PrintWriter(this.socket.getOutputStream(), true); String body = null; while(true){ // 读取客户端数据 body = in.readLine(); if(body == null) break; System.out.println("Server :" + body); out.println("服务器端回送响的应数据."); } } catch (Exception e) { e.printStackTrace(); } finally { if(in != null){ try { in.close(); } catch (IOException e) { e.printStackTrace(); } } if(out != null){ try { out.close(); } catch (Exception e) { e.printStackTrace(); } } if(socket != null){ try { socket.close(); } catch (IOException e) { e.printStackTrace(); } } socket = null; } } }
客户端请求:
package bhz.bio; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.Socket; public class Client { final static String ADDRESS = "127.0.0.1"; final static int PORT = 8765; public static void main(String[] args) { Socket socket = null; BufferedReader in = null; PrintWriter out = null; try { // 创建Socket连接 socket = new Socket(ADDRESS, PORT); // 接收服务端请求数据 in = new BufferedReader(new InputStreamReader(socket.getInputStream())); // 向服务端发送响应数据 out = new PrintWriter(socket.getOutputStream(), true); //向服务器端发送数据 out.println("接收到客户端的请求数据..."); out.println("接收到客户端的请求数据1111..."); // 读取服务端响应的数据 String response = in.readLine(); System.out.println("Client: " + response); } catch (Exception e) { e.printStackTrace(); } finally { if(in != null){ try { in.close(); } catch (IOException e) { e.printStackTrace(); } } if(out != null){ try { out.close(); } catch (Exception e) { e.printStackTrace(); } } if(socket != null){ try { socket.close(); } catch (IOException e) { e.printStackTrace(); } } socket = null; } } }
二:为摆脱服务端每接收一次客户端请求就得创建一个线程的问题,可以自定义线程池,限制线程、队列的大小。(为传统的BIO编程)
自定义线程池:
package bhz.bio2; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.ExecutorService; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; // 服务端自定的线程池 public class HandlerExecutorPool { private ExecutorService executor; // 构造方法,定义参数值 public HandlerExecutorPool(int maxPoolSize, int queueSize){ this.executor = new ThreadPoolExecutor( Runtime.getRuntime().availableProcessors(), maxPoolSize, 120L, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(queueSize)); } public void execute(Runnable task){ this.executor.execute(task); } }
服务端使用线程池:
package bhz.bio2; import java.io.BufferedReader; import java.io.PrintWriter; import java.net.ServerSocket; import java.net.Socket; public class Server { final static int PORT = 8765; public static void main(String[] args) { ServerSocket server = null; BufferedReader in = null; PrintWriter out = null; try { server = new ServerSocket(PORT); System.out.println("server start"); Socket socket = null; // 自定义一个线程池 HandlerExecutorPool executorPool = new HandlerExecutorPool(50, 1000); while(true){ // 阻塞,接收客户端请求 socket = server.accept(); // 将客户端请求放在线程池中,进行服务端响应 executorPool.execute(new ServerHandler(socket)); } } catch (Exception e) { e.printStackTrace(); } finally { if(in != null){ try { in.close(); } catch (Exception e1) { e1.printStackTrace(); } } if(out != null){ try { out.close(); } catch (Exception e2) { e2.printStackTrace(); } } if(server != null){ try { server.close(); } catch (Exception e3) { e3.printStackTrace(); } } server = null; } } }
服务端执行请求响应操作:
package bhz.bio2; import java.io.BufferedReader; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.Socket; public class ServerHandler implements Runnable { private Socket socket; public ServerHandler (Socket socket){ this.socket = socket; } // 执行线程操作 @Override public void run() { BufferedReader in = null; PrintWriter out = null; try { // 接收客户端请求数据 in = new BufferedReader(new InputStreamReader(this.socket.getInputStream())); // 发送服务端响应数据 out = new PrintWriter(this.socket.getOutputStream(), true); String body = null; while(true){ body = in.readLine(); if(body == null) break; System.out.println("Server:" + body); out.println("Server response"); } } catch (Exception e) { e.printStackTrace(); } finally { if(in != null){ try { in.close(); } catch (Exception e1) { e1.printStackTrace(); } } if(out != null){ try { out.close(); } catch (Exception e2) { e2.printStackTrace(); } } if(socket != null){ try { socket.close(); } catch (Exception e3) { e3.printStackTrace(); } } socket = null; } } }
客户端请求:
package bhz.bio2; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.Socket; import java.net.UnknownHostException; public class Client { final static String ADDRESS = "127.0.0.1"; final static int PORT =8765; public static void main(String[] args) { Socket socket = null; BufferedReader in = null; PrintWriter out = null; try { // 创建Socket连接 socket = new Socket(ADDRESS, PORT); // 接收服务端返回数据 in = new BufferedReader(new InputStreamReader(socket.getInputStream())); // 发送客户端响应数据 out = new PrintWriter(socket.getOutputStream(), true); out.println("Client request"); String response = in.readLine(); System.out.println("Client:" + response); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } finally { if(in != null){ try { in.close(); } catch (Exception e1) { e1.printStackTrace(); } } if(out != null){ try { out.close(); } catch (Exception e2) { e2.printStackTrace(); } } if(socket != null){ try { socket.close(); } catch (Exception e3) { e3.printStackTrace(); } } socket = null; } } }
三:NIO的Socket通信:同步非阻塞
1.NIO三个重要概念:Buffer(缓冲区)、Channel(通道)、选择器(Selector)。
Buffer(缓冲区):用于通信传输时的缓冲
Channel(通道):客户端和服务端都有一个通道,分别为:socketChannel和ServerSocketChannel,这两个类型管道都得注册到选择器(Seletoer)上,由选择器进行统一的通信管理。
选择器(Selector):对客户端和服务端的通信管道进行统一管理
2.Buffer的基本概念操作:
package bhz.nio.test; import java.nio.IntBuffer; public class TestBuffer { public static void main(String[] args) { // 1 基本操作 //创建指定长度的缓冲区 IntBuffer buf = IntBuffer.allocate(10); buf.put(13);// position位置:0 - > 1 缓冲区加入数据 buf.put(21);// position位置:1 - > 2 buf.put(35);// position位置:2 - > 3 //把位置复位为0,也就是position位置:3 - > 0 buf.flip(); System.out.println("使用flip复位:" + buf); System.out.println("容量为: " + buf.capacity()); //容量一旦初始化后不允许改变(warp方法包裹数组除外) System.out.println("限制为: " + buf.limit()); //由于只装载了三个元素,所以可读取或者操作的元素为3 则limit=3 System.out.println("获取下标为1的元素:" + buf.get(1)); System.out.println("get(index)方法,position位置不改变:" + buf); buf.put(1, 4); System.out.println("put(index, change)方法,position位置不变:" + buf);; for (int i = 0; i < buf.limit(); i++) { //调用get方法会使其缓冲区位置(position)向后递增一位 System.out.print(buf.get() + "\t"); } System.out.println("buf对象遍历之后为: " + buf); // 2 wrap方法使用 /** // wrap方法会包裹一个数组: 一般这种用法不会先初始化缓存对象的长度,因为没有意义,最后还会被wrap所包裹的数组覆盖掉。 // 并且wrap方法修改缓冲区对象的时候,数组本身也会跟着发生变化。 int[] arr = new int[]{1,2,5}; IntBuffer buf1 = IntBuffer.wrap(arr); System.out.println(buf1); IntBuffer buf2 = IntBuffer.wrap(arr, 0 , 2); //这样使用表示容量为数组arr的长度,但是可操作的元素只有实际进入缓存区的元素长度 System.out.println(buf2); */ // 3 其他方法 /** IntBuffer buf1 = IntBuffer.allocate(10); int[] arr = new int[]{1,2,5}; buf1.put(arr); System.out.println(buf1); //一种复制方法 IntBuffer buf3 = buf1.duplicate(); System.out.println(buf3); //设置buf1的位置属性 //buf1.position(0); buf1.flip(); System.out.println(buf1); System.out.println("可读数据为:" + buf1.remaining()); int[] arr2 = new int[buf1.remaining()]; //将缓冲区数据放入arr2数组中去 buf1.get(arr2); for(int i : arr2){ System.out.print(Integer.toString(i) + ","); } */ } }
2.使用管道Channel和选择器Seletor的服务端(接收客户端请求):
package bhz.nio; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.util.Iterator; public class Server implements Runnable{ //1 多路复用器(管理所有的通道),channel会注册到这个seletor对象上 private Selector seletor; //2 建立缓冲区 private ByteBuffer readBuf = ByteBuffer.allocate(1024); //3 private ByteBuffer writeBuf = ByteBuffer.allocate(1024); // 构造方法,初始化参数 public Server(int port){ try { //1 打开路复用器 this.seletor = Selector.open(); //2 打开服务器通道 ServerSocketChannel ssc = ServerSocketChannel.open(); //3 设置服务器通道为非阻塞模式,为true的话和传统的阻塞socket没区别 ssc.configureBlocking(false); //4 绑定地址 ssc.bind(new InetSocketAddress(port)); //5 把服务器通道注册到多路复用器上,并且监听阻塞事件 ssc.register(this.seletor, SelectionKey.OP_ACCEPT); System.out.println("Server start, port :" + port); } catch (IOException e) { e.printStackTrace(); } } // 线程开启后执行 @Override public void run() { while(true){ try { //1 必须要让多路复用器开始监听 this.seletor.select(); //2 返回多路复用器已经选择的结果集,客户端chanal和服务端的serverChanal都会注册到seletor上(注册的值为Key),此处拿到的keys包含了客户端的和服务端的key Iterator<SelectionKey> keys = this.seletor.selectedKeys().iterator(); //3 对结果集进行遍历 while(keys.hasNext()){ //4 获取一个选择的元素 SelectionKey key = keys.next(); //5 直接从容器中移除就可以了 keys.remove(); //6 如果是有效的 if(key.isValid()){ //7 如果为阻塞状态,说明key为服务端的serverSocketChanal。此处NIO为接收客户端请求,同步不阻塞 if(key.isAcceptable()){ this.accept(key); } //8 如果为可读状态 if(key.isReadable()){ // 读取每一个客户端的SocketChannle发送过来的数据 this.read(key); } //9 写数据 if(key.isWritable()){ //this.write(key); //ssc } } } } catch (IOException e) { e.printStackTrace(); } } } private void write(SelectionKey key){ //ServerSocketChannel ssc = (ServerSocketChannel) key.channel(); //ssc.register(this.seletor, SelectionKey.OP_WRITE); } // 接收读取客户端数据 private void read(SelectionKey key) { try { //1 清空缓冲区旧的数据 this.readBuf.clear(); //2 获取之前注册的socket通道对象 SocketChannel sc = (SocketChannel) key.channel(); //3 读取数据 int count = sc.read(this.readBuf); //4 如果没有数据 if(count == -1){ key.channel().close(); key.cancel(); return; } //5 有数据则进行读取 读取之前需要进行复位方法(把position 和limit进行复位) this.readBuf.flip(); //6 根据缓冲区的数据长度创建相应大小的byte数组,接收缓冲区的数据 byte[] bytes = new byte[this.readBuf.remaining()]; //7 接收缓冲区数据 this.readBuf.get(bytes); //8 打印客户端传送过来的结果数据 String body = new String(bytes).trim(); System.out.println("Server : " + body); // 9..可以写回给客户端数据 } catch (IOException e) { e.printStackTrace(); } } // 接收客户端请求 private void accept(SelectionKey key) { try { //1 获取服务通道 ServerSocketChannel ssc = (ServerSocketChannel) key.channel(); //2 执行阻塞方法,拿到客户端管道,客户端管道也要注册到selector选择器上 SocketChannel sc = ssc.accept(); //3 设置阻塞模式:非阻塞 sc.configureBlocking(false); //4 注册到多路复用器上,并设置读取标识 sc.register(this.seletor, SelectionKey.OP_READ); } catch (IOException e) { e.printStackTrace(); } } public static void main(String[] args) { // 开启服务,执行线程run()方法 new Thread(new Server(8765)).start();; } }
3.客户端发送请求:
package bhz.nio; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SocketChannel; public class Client { //需要一个Selector public static void main(String[] args) { //创建连接的地址 InetSocketAddress address = new InetSocketAddress("127.0.0.1", 8765); //声明连接通道 SocketChannel sc = null; //建立缓冲区 ByteBuffer buf = ByteBuffer.allocate(1024); try { //打开通道 sc = SocketChannel.open(); //进行连接 sc.connect(address); // 连接成功 while(true){ //定义一个字节数组,然后使用系统录入功能: byte[] bytes = new byte[1024]; System.in.read(bytes); //把数据放到缓冲区中 buf.put(bytes); //对缓冲区进行复位 buf.flip(); //写出数据 sc.write(buf); //清空缓冲区数据 buf.clear(); } } catch (IOException e) { e.printStackTrace(); } finally { if(sc != null){ try { sc.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
四:AIO的Socket通信:AIO是在NIO的基础上,新增了异步通道,在管道前面使用了Asynchronous的,服务端和客户端管道类分别为:AsynchronousServerSocketChannel和AsynchronousSocketChannel。
AIO通信的服务端为:先创建线程池,线程组使用线程池并负责客户端管道的接入,服务端开启线程组。
1.服务端ServerCompletionHandler类的创建(服务端接收请求后的具体执行内容):
package bhz.aio; import java.nio.ByteBuffer; import java.nio.channels.AsynchronousSocketChannel; import java.nio.channels.CompletionHandler; import java.util.concurrent.ExecutionException; public class ServerCompletionHandler implements CompletionHandler<AsynchronousSocketChannel, Server> { // 连接成功的操作方法,每一个客户端连接进来都进行此操作 @Override public void completed(AsynchronousSocketChannel asc, Server attachment) { //当有下一个客户端接入的时候 直接调用Server的accept方法,这样反复执行下去,保证多个客户端都可以阻塞 attachment.assc.accept(attachment, this); read(asc); } // 读取客户端管道数据 private void read(final AsynchronousSocketChannel asc) { //读取数据 ByteBuffer buf = ByteBuffer.allocate(1024); // 异步,不堵塞,执行不成功也直接往下走 asc.read(buf, buf, new CompletionHandler<Integer, ByteBuffer>() { @Override public void completed(Integer resultSize, ByteBuffer attachment) { //进行读取之后,重置标识位 attachment.flip(); //获得读取的字节数 System.out.println("Server -> " + "收到客户端的数据长度为:" + resultSize); //获取读取的数据 String resultData = new String(attachment.array()).trim(); System.out.println("Server -> " + "收到客户端的数据信息为:" + resultData); // 发送服务端响应数据 String response = "服务器响应, 收到了客户端发来的数据: " + resultData; write(asc, response); } @Override public void failed(Throwable exc, ByteBuffer attachment) { exc.printStackTrace(); } }); } // 服务端写响应数据 private void write(AsynchronousSocketChannel asc, String response) { try { // 设置缓冲区大小 ByteBuffer buf = ByteBuffer.allocate(1024); // 向客户端发送响应数据 buf.put(response.getBytes()); buf.flip(); asc.write(buf).get(); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } // 连接失败的操作方法 @Override public void failed(Throwable exc, Server attachment) { exc.printStackTrace(); } }
2.服务端:
package bhz.aio; import java.net.InetSocketAddress; import java.nio.channels.AsynchronousChannelGroup; import java.nio.channels.AsynchronousServerSocketChannel; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class Server { //线程池 private ExecutorService executorService; //线程组 private AsynchronousChannelGroup threadGroup; //服务器通道 public AsynchronousServerSocketChannel assc; public Server(int port){ try { //创建一个缓存池 executorService = Executors.newCachedThreadPool(); //创建线程组:异步,负责接收client的接入 threadGroup = AsynchronousChannelGroup.withCachedThreadPool(executorService, 1); //创建服务器通道 assc = AsynchronousServerSocketChannel.open(threadGroup); //进行绑定接收端口 assc.bind(new InetSocketAddress(port)); System.out.println("server start , port : " + port); //进行阻塞:一直等客户端的请求,this指当前对象。new ServerCompletionHandler()为进行通信的具体操作内容 assc.accept(this, new ServerCompletionHandler()); //一直阻塞 不让服务器停止 Thread.sleep(Integer.MAX_VALUE); } catch (Exception e) { e.printStackTrace(); } } public static void main(String[] args) { Server server = new Server(8765); } }
3.客户端:
package bhz.aio; import java.io.UnsupportedEncodingException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.AsynchronousSocketChannel; import java.util.concurrent.ExecutionException; public class Client implements Runnable{ private AsynchronousSocketChannel asc ; public Client() throws Exception { asc = AsynchronousSocketChannel.open(); } // 连接服务端地址 public void connect(){ asc.connect(new InetSocketAddress("127.0.0.1", 8765)); } // 向服务端写数据 public void write(String request){ try { asc.write(ByteBuffer.wrap(request.getBytes())).get(); read(); } catch (Exception e) { e.printStackTrace(); } } //接收服务端数据 private void read() { ByteBuffer buf = ByteBuffer.allocate(1024); try { asc.read(buf).get(); buf.flip(); byte[] respByte = new byte[buf.remaining()]; buf.get(respByte); System.out.println(new String(respByte,"utf-8").trim()); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } } @Override public void run() { while(true){ } } public static void main(String[] args) throws Exception { Client c1 = new Client(); c1.connect(); Client c2 = new Client(); c2.connect(); Client c3 = new Client(); c3.connect(); new Thread(c1, "c1").start(); new Thread(c2, "c2").start(); new Thread(c3, "c3").start(); Thread.sleep(1000); c1.write("c1 aaa"); c2.write("c2 bbbb"); c3.write("c3 ccccc"); } }
客户端发送请求,接收服务端的数据:
服务器响应, 收到了客户端发来的数据: c1 aaa
服务器响应, 收到了客户端发来的数据: c2 bbbb
服务器响应, 收到了客户端发来的数据: c3 ccccc
五:思考:BIO和NIO,NIO和AIO的区别?(这三者现在开发都不会用了)