一、Socket 的使用
1、单线程Socket的使用
/** * 单线程版本 * 问题描述:只能服务单个客户端 * 解决方案:多线程版本 */ public class Socket_V1 { public static void main(String[] args) throws Exception { ServerSocket serverSocket = new ServerSocket(1234); System.out.println("启动服务器:"); while(true) { System.out.println("等待客户端链接(阻塞)……"); Socket socket = serverSocket.accept(); System.out.println("有链接进来了……"); handle(socket); } } private static void handle(Socket socket) { try { InputStream inputStream = socket.getInputStream(); byte[] b = new byte[1024]; String str = null; while(true) { str = null; System.out.println("等待输入……"); int read = inputStream.read(b); if(read == -1) { System.out.println("客户端关闭。"); break; } str = new String(b); System.out.println(str); } } catch (IOException e) { e.printStackTrace(); } } }
2、多线程版本
/** * 多线程版本 * 问题描述:多线程开销大 * 解决方案:NIO */ public class Socket_V2 { public static void main(String[] args) throws Exception { ServerSocket serverSocket = new ServerSocket(1234); // 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。 ExecutorService threadPool = Executors.newFixedThreadPool(10); System.out.println("启动服务器:"); while(true) { System.out.println("等待客户端链接(阻塞)……"); final Socket socket = serverSocket.accept(); System.out.println("有链接进来了……"); //使用线程池来,支持并发 threadPool.execute(new Runnable() { public void run() { handle(socket); } }); } } private static void handle(Socket socket) { try { InputStream inputStream = socket.getInputStream(); byte[] b = new byte[1024]; String str = null; while(true) { str = null; System.out.println("等待输入……"); int read = inputStream.read(b); if(read == -1) { System.out.println("客户端关闭。"); break; } str = new String(b); System.out.println(str); } } catch (IOException e) { e.printStackTrace(); } } }
3、NIO
/** * NIO版本 * 原理:IO多路复用 * NIO与旧Socket对比: * ServerSocketChannel <-等价于-> ServerSocket * SocketChannel <-等价于-> Socket * Selector 选择器 * SelectionKey 事件类型 */ public class Socket_NIO { public static void main(String[] args) throws Exception { //获得Socket通道 ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); //将通道设置为非阻塞模式 serverSocketChannel.configureBlocking(false); //将通道绑定到指定的端口 serverSocketChannel.socket().bind(new InetSocketAddress(1234)); //获取通道选择器 Selector selector = Selector.open(); //将连接事件,注册到选择器内(步骤1) serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); System.out.println("服务器启动成功。端口已经监听……"); while(true) { selector.select();//阻塞等待已经注册的事件 Iterator<SelectionKey> iterator = selector.selectedKeys().iterator(); while(iterator.hasNext()) { SelectionKey next = iterator.next(); iterator.remove(); if(next.isAcceptable()) { //如果是连接事件,注册读写事件 System.out.println("获取到连接事件。"); ServerSocketChannel sschannel = (ServerSocketChannel) next.channel(); //该强转是步骤1内存放的值 SocketChannel channel = sschannel.accept(); //获取客户端连接的通道 channel.configureBlocking(false);//设置为非阻塞 channel.register(selector, SelectionKey.OP_READ); //步骤2 }else if(next.isReadable()) { //如果是可读事件,则执行读操作 System.out.println("获取到可读事件。"); SocketChannel channel = (SocketChannel) next.channel(); //获取socket通道,该强转是步骤2内存放的值 ByteBuffer dst = ByteBuffer.allocate(1024); //缓冲区 int read = channel.read(dst); if(read < 0 ) { System.out.println("客户端关闭。"); next.cancel(); break; } System.out.println("客户端输入:"+new String(dst.array())); } //还有其他事件……略 } } } }
关于nio的一点理解:
优点:少了线程切换的开销。(io多路复用)
对比多线程模式,线程用完cpu时间片之后,就切换线程。 此时io阻塞的线程(不是就绪状态,无法获取cpu时间片),io非阻塞线程(网速很慢),只能获取到一部分数据,则浪费了相应的cpu时间片。