工作机制

  1. Java BIOJava传统的IO编程,类和接口在java.io下。
  2. 同步阻塞型IO,如果大量的连接都处于阻塞状态,会造成服务器大量线程资源浪费,使用线程池能做改良。
  1. //服务端
  2. 1.启动ServerSocket注册端口
  3. 2.调用accept监听客户端的Socket请求
  4. 3.Socket中获取字节输入流或输出流
  5. //客户端
  6. 1.通过Socket对象请求与服务端的连接
  7. 2.Socket管道中获取字节输入流或者输出流进行数据的读写操作

BIO阻塞案例

客户端

  1. public static void main(String[] args) {
  2. Socket socket = null;
  3. try {
  4. //创建Socket对象请求服务端的连接
  5. socket = new Socket("127.0.0.1",8192);
  6. OutputStream os = socket.getOutputStream();
  7. //把Socket字节输入流包装成打印流
  8. PrintStream ps = new PrintStream(os);
  9. //ps.print("你好!服务端。");没有换行-阻塞
  10. ps.println("你好!服务端。");
  11. } catch (IOException e) {
  12. e.printStackTrace();
  13. } finally {
  14. if (socket != null) {
  15. try {
  16. socket.close();//关闭客户端套接字
  17. } catch (IOException e) {
  18. e.printStackTrace();
  19. }
  20. }
  21. }
  22. }

服务端

  1. public static void main(String[] args) {
  2. ServerSocket ss = null;
  3. try {
  4. //定义一个ServerSocket对象进行服务端代码注册
  5. ss = new ServerSocket(8192);
  6. //监听客户端请求,获取客户端套接字
  7. Socket socket = ss.accept();
  8. //从Socket管道中得到字节输入流对象
  9. InputStream is = socket.getInputStream();
  10. //利用转换流和字符处理流封装客户端传过来的流
  11. BufferedReader br = new BufferedReader(new InputStreamReader(is));
  12. String line;
  13. //服务端阻塞等待客户端消息,如果客户端没有发送消息一致进入阻塞状态
  14. if ((line = br.readLine()) != null) {
  15. System.out.println(line);
  16. }
  17. socket.close();//断开连接
  18. } catch (IOException e) {
  19. e.printStackTrace();
  20. } finally {
  21. if (ss != null) {
  22. try {
  23. ss.close();
  24. } catch (IOException e) {
  25. e.printStackTrace();
  26. }
  27. }
  28. }
  29. }

BIO多客户端

  1. 案例分析:
  2. 启动服务端,服务端可以接受多个客户端的请求。需要引入多线程处理服务端,实现为一个客户端请求分配
  3. 一个线程。
  4. 程序存在缺陷:
  5. 1.线程之间上下文切换会影响性能。
  6. 2.每个线程都会占用CPU资源和栈空间。
  7. 3.可能存在无意义的客户端连接占用资源。
  8. 4.服务端并发抗压能力弱。

客户端

  1. public static void main(String[] args) {
  2. Socket socket = null;
  3. try {
  4. //创建Socket对象请求服务端的连接
  5. socket = new Socket("127.0.0.1",8192);
  6. OutputStream os = socket.getOutputStream();
  7. //把Socket字节输入流包装成打印流
  8. PrintStream ps = new PrintStream(os);
  9. Scanner scanner = new Scanner(System.in);
  10. while (true) {
  11. System.out.println("请输入信息...");
  12. String msg = scanner.nextLine();
  13. ps.println(msg);
  14. ps.flush();
  15. }
  16. } catch (IOException e) {
  17. e.printStackTrace();
  18. } finally {
  19. if (socket != null) {
  20. try {
  21. socket.close();//关闭客户端套接字
  22. } catch (IOException e) {
  23. e.printStackTrace();
  24. }
  25. }
  26. }
  27. }

服务端

  1. public static void main(String[] args) {
  2. ServerSocket ss = null;
  3. try {
  4. //定义一个ServerSocket对象进行服务端代码注册
  5. ss = new ServerSocket(8192);
  6. //不断的监听客户端的请求
  7. while (true) {
  8. //监听客户端请求,获取客户端套接字
  9. Socket socket = ss.accept();
  10. //创建线程对象接收当前客户端的Socket对象
  11. new Thread(() -> {
  12. try {
  13. //从Socket管道中得到字节输入流对象
  14. InputStream is = socket.getInputStream();
  15. //利用转换流和字符处理流封装客户端传过来的流
  16. BufferedReader br = new BufferedReader(new InputStreamReader(is));
  17. String line;
  18. //服务端阻塞等待客户端消息,如果客户端没有发送消息一致进入阻塞状态
  19. while ((line = br.readLine()) != null) {
  20. System.out.println(line);
  21. }
  22. socket.close();//断开连接
  23. } catch (IOException e) {
  24. e.printStackTrace();
  25. }
  26. }).start();
  27. }
  28. } catch (IOException e) {
  29. e.printStackTrace();
  30. } finally {
  31. if (ss != null) {
  32. try {
  33. ss.close();
  34. } catch (IOException e) {
  35. e.printStackTrace();
  36. }
  37. }
  38. }
  39. }

伪异步IO

  1. 采用线程池和任务队列实现将客户端的Socket封装成一个Task(Runnable对象)交给后端的线程池进行处理。
  2. JDK线程池维护一个消息队列和N个活跃线程,对消息队列中的Socket任务进行处理,由于线程可以设置
  3. 消息队列的大小和线程池的最大线程数,资源可控可以很好的避免服务器在高并发情况下宕机。
  4. 存在问题:
  5. 1.伪异步IO采用线程池实现,避免了线程数量开销大的问题,但底层由于使用同步阻塞IO模型,无法从根本上解决。
  6. 2.当服务器线程池被占满,其他任务会进入任务队列进行等待(被阻塞),如果有一个线程断开了与服务器的
  7. 连接,那么服务器会抛出异常,然后任务队列中的下一个任务抢到线程,连接上服务器。

服务端

  1. //服务端主程序
  2. public static void main(String[] args) {
  3. ServerSocket ss = null;
  4. try {
  5. //创建服务端套接字对象,注册端口
  6. ss = new ServerSocket(8192);
  7. //循环监听客户端的Socket请求
  8. //初始化一个线程池对象
  9. HandlerSocketServerPool pool
  10. = new HandlerSocketServerPool(3,10);
  11. while (true) {
  12. Socket socket = ss.accept();
  13. //把socket封装成任务添加入任务队列
  14. ServerRunnableTarget target = new ServerRunnableTarget(socket);
  15. pool.execute(target);//线程池一旦有空闲线程就触发run方法执行任务
  16. }
  17. } catch (IOException e) {
  18. e.printStackTrace();
  19. } finally {
  20. if (ss != null) {
  21. try {
  22. ss.close();
  23. } catch (IOException e) {
  24. e.printStackTrace();
  25. }
  26. }
  27. }
  28. }
  1. //服务端线程池类
  2. public class HandlerSocketServerPool {
  3. //创建一个线程池的成员变量用于存储一个线程池对象
  4. private ExecutorService executorService;
  5. //创建这个类的对象的时候就需要初始化线程池对象
  6. /**
  7. * ThreadPoolExecutor(int corePoolSize, 核心线程数
  8. * int maximumPoolSize, 最大线程数
  9. * long keepAliveTime, 线程空闲时间
  10. * TimeUnit unit, 时间单位
  11. * BlockingQueue<Runnable> workQueue) 任务队列
  12. */
  13. public HandlerSocketServerPool(int maxThreadNum, int queueSize) {
  14. executorService = new ThreadPoolExecutor(3,
  15. maxThreadNum,
  16. 120,
  17. TimeUnit.SECONDS,
  18. new ArrayBlockingQueue<>(queueSize));
  19. }
  20. //提供一个方法来提交任务给线程池的任务队列来暂存,等待线程池处理
  21. public void execute(Runnable target) {
  22. executorService.execute(target);//如果未达到最大线程数就执行run方法处理任务
  23. }
  24. }
  1. //服务端任务包装类 - 把Socket包装成Task放入任务队列
  2. public class ServerRunnableTarget implements Runnable {
  3. //接收Socket对象
  4. private Socket socket;
  5. public ServerRunnableTarget(Socket socket) {
  6. this.socket = socket;
  7. }
  8. @Override
  9. public void run() {
  10. try {
  11. //处理接收到的Socket通信需求,从Socket管道中得到字节输入流对象
  12. InputStream is = socket.getInputStream();
  13. //利用转换流和字符处理流封装客户端传过来的流
  14. BufferedReader br = new BufferedReader(new InputStreamReader(is));
  15. String line;
  16. //服务端阻塞等待客户端消息,如果客户端没有发送消息一致进入阻塞状态
  17. while ((line = br.readLine()) != null) {
  18. System.out.println(line);
  19. }
  20. } catch (IOException e) {
  21. e.printStackTrace();
  22. }
  23. }
  24. }

客户端

  1. public static void main(String[] args) {
  2. Socket socket = null;
  3. try {
  4. //创建Socket对象请求服务端的连接
  5. socket = new Socket("127.0.0.1",8192);
  6. OutputStream os = socket.getOutputStream();
  7. //把Socket字节输入流包装成打印流
  8. PrintStream ps = new PrintStream(os);
  9. Scanner scanner = new Scanner(System.in);
  10. while (true) {
  11. System.out.println("请输入信息...");
  12. String msg = scanner.nextLine();
  13. ps.println(msg);
  14. ps.flush();
  15. }
  16. } catch (IOException e) {
  17. e.printStackTrace();
  18. } finally {
  19. if (socket != null) {
  20. try {
  21. socket.close();//关闭客户端套接字
  22. } catch (IOException e) {
  23. e.printStackTrace();
  24. }
  25. }
  26. }
  27. }

文件上传

服务端

  1. public static void main(String[] args) {
  2. ServerSocket ss = null;
  3. try {
  4. //创建服务端套接字监听客户端请求
  5. ss = new ServerSocket(8192);
  6. while (true) {
  7. Socket socket = ss.accept();
  8. //交给独立的线程来处理与这个客户端的文件通信需求
  9. new ServerReaderThread(socket).start();
  10. }
  11. } catch (IOException e) {
  12. e.printStackTrace();
  13. } finally {
  14. if (ss != null) {
  15. try {
  16. ss.close();
  17. } catch (IOException e) {
  18. e.printStackTrace();
  19. }
  20. }
  21. }
  22. }
  1. //服务端线程处理类
  2. public class ServerReaderThread extends Thread {
  3. private Socket socket;
  4. public ServerReaderThread(Socket socket) {
  5. this.socket = socket;
  6. }
  7. @Override
  8. public void run() {
  9. OutputStream os = null;
  10. try {
  11. //得到一个数据输入流读取客户端发送过来的请求
  12. DataInputStream dis = new DataInputStream(socket.getInputStream());
  13. //读取客户端的文件类型
  14. String suffix = dis.readUTF();
  15. String filePath = UUID.randomUUID()+suffix;
  16. //把客户端传过来的数据流写到指定位置
  17. os = new FileOutputStream(new File(filePath));
  18. byte[] buffer = new byte[1024];
  19. int len;
  20. while ((len = dis.read(buffer)) != -1) {
  21. os.write(buffer,0,len);
  22. }
  23. } catch (IOException e) {
  24. e.printStackTrace();
  25. } finally {
  26. if (os != null) {
  27. try {
  28. os.close();//关闭输出流
  29. } catch (IOException e) {
  30. e.printStackTrace();
  31. }
  32. }
  33. }
  34. }
  35. }

客户端

  1. public static void main(String[] args) {
  2. Socket socket = null;
  3. try {
  4. //创建客户端Socket,指定主机地址和端口号
  5. socket = new Socket("127.0.0.1",8192);
  6. //把字节流包装成数据输出流
  7. DataOutputStream dos =
  8. new DataOutputStream(socket.getOutputStream());
  9. dos.writeUTF(".png");//把文件后缀上传给服务器
  10. String filePath = "C:\\Users\\ms674\\Desktop\\File\\img1.png";
  11. InputStream is = new FileInputStream(new File(filePath));
  12. byte[] buffer = new byte[1024];
  13. int len;
  14. while ((len = is.read(buffer)) != -1) {
  15. dos.write(buffer,0,len);
  16. }
  17. dos.flush();//刷新缓冲区
  18. } catch (IOException e) {
  19. e.printStackTrace();
  20. } finally {
  21. if (socket != null) {
  22. try {
  23. socket.close();
  24. } catch (IOException e) {
  25. e.printStackTrace();
  26. }
  27. }
  28. }
  29. }