工作机制
Java BIO是Java传统的IO编程,类和接口在java.io下。同步阻塞型IO,如果大量的连接都处于阻塞状态,会造成服务器大量线程资源浪费,使用线程池能做改良。
//服务端1.启动ServerSocket注册端口2.调用accept监听客户端的Socket请求3.从Socket中获取字节输入流或输出流//客户端1.通过Socket对象请求与服务端的连接2.从Socket管道中获取字节输入流或者输出流进行数据的读写操作
BIO阻塞案例
客户端
public static void main(String[] args) { Socket socket = null; try { //创建Socket对象请求服务端的连接 socket = new Socket("127.0.0.1",8192); OutputStream os = socket.getOutputStream(); //把Socket字节输入流包装成打印流 PrintStream ps = new PrintStream(os); //ps.print("你好!服务端。");没有换行-阻塞 ps.println("你好!服务端。"); } catch (IOException e) { e.printStackTrace(); } finally { if (socket != null) { try { socket.close();//关闭客户端套接字 } catch (IOException e) { e.printStackTrace(); } } }}
服务端
public static void main(String[] args) { ServerSocket ss = null; try { //定义一个ServerSocket对象进行服务端代码注册 ss = new ServerSocket(8192); //监听客户端请求,获取客户端套接字 Socket socket = ss.accept(); //从Socket管道中得到字节输入流对象 InputStream is = socket.getInputStream(); //利用转换流和字符处理流封装客户端传过来的流 BufferedReader br = new BufferedReader(new InputStreamReader(is)); String line; //服务端阻塞等待客户端消息,如果客户端没有发送消息一致进入阻塞状态 if ((line = br.readLine()) != null) { System.out.println(line); } socket.close();//断开连接 } catch (IOException e) { e.printStackTrace(); } finally { if (ss != null) { try { ss.close(); } catch (IOException e) { e.printStackTrace(); } } }}
BIO多客户端
案例分析:启动服务端,服务端可以接受多个客户端的请求。需要引入多线程处理服务端,实现为一个客户端请求分配一个线程。程序存在缺陷:1.线程之间上下文切换会影响性能。2.每个线程都会占用CPU资源和栈空间。3.可能存在无意义的客户端连接占用资源。4.服务端并发抗压能力弱。
客户端
public static void main(String[] args) { Socket socket = null; try { //创建Socket对象请求服务端的连接 socket = new Socket("127.0.0.1",8192); OutputStream os = socket.getOutputStream(); //把Socket字节输入流包装成打印流 PrintStream ps = new PrintStream(os); Scanner scanner = new Scanner(System.in); while (true) { System.out.println("请输入信息..."); String msg = scanner.nextLine(); ps.println(msg); ps.flush(); } } catch (IOException e) { e.printStackTrace(); } finally { if (socket != null) { try { socket.close();//关闭客户端套接字 } catch (IOException e) { e.printStackTrace(); } } }}
服务端
public static void main(String[] args) { ServerSocket ss = null; try { //定义一个ServerSocket对象进行服务端代码注册 ss = new ServerSocket(8192); //不断的监听客户端的请求 while (true) { //监听客户端请求,获取客户端套接字 Socket socket = ss.accept(); //创建线程对象接收当前客户端的Socket对象 new Thread(() -> { try { //从Socket管道中得到字节输入流对象 InputStream is = socket.getInputStream(); //利用转换流和字符处理流封装客户端传过来的流 BufferedReader br = new BufferedReader(new InputStreamReader(is)); String line; //服务端阻塞等待客户端消息,如果客户端没有发送消息一致进入阻塞状态 while ((line = br.readLine()) != null) { System.out.println(line); } socket.close();//断开连接 } catch (IOException e) { e.printStackTrace(); } }).start(); } } catch (IOException e) { e.printStackTrace(); } finally { if (ss != null) { try { ss.close(); } catch (IOException e) { e.printStackTrace(); } } }}
伪异步IO
采用线程池和任务队列实现将客户端的Socket封装成一个Task(Runnable对象)交给后端的线程池进行处理。JDK线程池维护一个消息队列和N个活跃线程,对消息队列中的Socket任务进行处理,由于线程可以设置消息队列的大小和线程池的最大线程数,资源可控可以很好的避免服务器在高并发情况下宕机。存在问题:1.伪异步IO采用线程池实现,避免了线程数量开销大的问题,但底层由于使用同步阻塞IO模型,无法从根本上解决。2.当服务器线程池被占满,其他任务会进入任务队列进行等待(被阻塞),如果有一个线程断开了与服务器的 连接,那么服务器会抛出异常,然后任务队列中的下一个任务抢到线程,连接上服务器。
服务端
//服务端主程序public static void main(String[] args) { ServerSocket ss = null; try { //创建服务端套接字对象,注册端口 ss = new ServerSocket(8192); //循环监听客户端的Socket请求 //初始化一个线程池对象 HandlerSocketServerPool pool = new HandlerSocketServerPool(3,10); while (true) { Socket socket = ss.accept(); //把socket封装成任务添加入任务队列 ServerRunnableTarget target = new ServerRunnableTarget(socket); pool.execute(target);//线程池一旦有空闲线程就触发run方法执行任务 } } catch (IOException e) { e.printStackTrace(); } finally { if (ss != null) { try { ss.close(); } catch (IOException e) { e.printStackTrace(); } } }}
//服务端线程池类public class HandlerSocketServerPool { //创建一个线程池的成员变量用于存储一个线程池对象 private ExecutorService executorService; //创建这个类的对象的时候就需要初始化线程池对象 /** * ThreadPoolExecutor(int corePoolSize, 核心线程数 * int maximumPoolSize, 最大线程数 * long keepAliveTime, 线程空闲时间 * TimeUnit unit, 时间单位 * BlockingQueue<Runnable> workQueue) 任务队列 */ public HandlerSocketServerPool(int maxThreadNum, int queueSize) { executorService = new ThreadPoolExecutor(3, maxThreadNum, 120, TimeUnit.SECONDS, new ArrayBlockingQueue<>(queueSize)); } //提供一个方法来提交任务给线程池的任务队列来暂存,等待线程池处理 public void execute(Runnable target) { executorService.execute(target);//如果未达到最大线程数就执行run方法处理任务 }}
//服务端任务包装类 - 把Socket包装成Task放入任务队列public class ServerRunnableTarget implements Runnable { //接收Socket对象 private Socket socket; public ServerRunnableTarget(Socket socket) { this.socket = socket; } @Override public void run() { try { //处理接收到的Socket通信需求,从Socket管道中得到字节输入流对象 InputStream is = socket.getInputStream(); //利用转换流和字符处理流封装客户端传过来的流 BufferedReader br = new BufferedReader(new InputStreamReader(is)); String line; //服务端阻塞等待客户端消息,如果客户端没有发送消息一致进入阻塞状态 while ((line = br.readLine()) != null) { System.out.println(line); } } catch (IOException e) { e.printStackTrace(); } }}
客户端
public static void main(String[] args) { Socket socket = null; try { //创建Socket对象请求服务端的连接 socket = new Socket("127.0.0.1",8192); OutputStream os = socket.getOutputStream(); //把Socket字节输入流包装成打印流 PrintStream ps = new PrintStream(os); Scanner scanner = new Scanner(System.in); while (true) { System.out.println("请输入信息..."); String msg = scanner.nextLine(); ps.println(msg); ps.flush(); } } catch (IOException e) { e.printStackTrace(); } finally { if (socket != null) { try { socket.close();//关闭客户端套接字 } catch (IOException e) { e.printStackTrace(); } } }}
文件上传
服务端
public static void main(String[] args) { ServerSocket ss = null; try { //创建服务端套接字监听客户端请求 ss = new ServerSocket(8192); while (true) { Socket socket = ss.accept(); //交给独立的线程来处理与这个客户端的文件通信需求 new ServerReaderThread(socket).start(); } } catch (IOException e) { e.printStackTrace(); } finally { if (ss != null) { try { ss.close(); } catch (IOException e) { e.printStackTrace(); } } }}
//服务端线程处理类public class ServerReaderThread extends Thread { private Socket socket; public ServerReaderThread(Socket socket) { this.socket = socket; } @Override public void run() { OutputStream os = null; try { //得到一个数据输入流读取客户端发送过来的请求 DataInputStream dis = new DataInputStream(socket.getInputStream()); //读取客户端的文件类型 String suffix = dis.readUTF(); String filePath = UUID.randomUUID()+suffix; //把客户端传过来的数据流写到指定位置 os = new FileOutputStream(new File(filePath)); byte[] buffer = new byte[1024]; int len; while ((len = dis.read(buffer)) != -1) { os.write(buffer,0,len); } } catch (IOException e) { e.printStackTrace(); } finally { if (os != null) { try { os.close();//关闭输出流 } catch (IOException e) { e.printStackTrace(); } } } }}
客户端
public static void main(String[] args) { Socket socket = null; try { //创建客户端Socket,指定主机地址和端口号 socket = new Socket("127.0.0.1",8192); //把字节流包装成数据输出流 DataOutputStream dos = new DataOutputStream(socket.getOutputStream()); dos.writeUTF(".png");//把文件后缀上传给服务器 String filePath = "C:\\Users\\ms674\\Desktop\\File\\img1.png"; InputStream is = new FileInputStream(new File(filePath)); byte[] buffer = new byte[1024]; int len; while ((len = is.read(buffer)) != -1) { dos.write(buffer,0,len); } dos.flush();//刷新缓冲区 } catch (IOException e) { e.printStackTrace(); } finally { if (socket != null) { try { socket.close(); } catch (IOException e) { e.printStackTrace(); } } }}