java BIO

目录

Java BIO基本介绍

Java BIO工作机制

传统的BIO编程实例回顾

1、BIO模式下发送和接收消息

2、BIO模式下多发和多收消息

3、BIO模式下接收多个客户端

伪异步I/O编程

基于BIO形式下的文件上传

Java BIO模式下的端口转发思想


Java BIO基本介绍

  • Java BIO就是传统的java io 编程,其相关的类和接口在java.io
  • BlO(blocking I/O):同步阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,可以通过线程池机制改善(实现多个客户连接服务器).

Java BIO工作机制

传统的BIO编程实例回顾

        网络编程的基本模型是Client/Server模型,也就是两个进程之间进行相互通信,其中服务端提供位置信(绑定IP地址和端口),客户端通过连接操作向服务端监听的端口地址发起连接请求,基于TCP协议下进行三次握手连接,连接成功后,双方通过网络套接字(Socket)进行通信。
        传统的同步阻塞模型开发中,服务端ServerSocket负责绑定IP地址,启动监听端口;客户端Socket负责发起连接操作。连接成功后,双方通过输入和输出流进行同步阻塞式通信。
        基于BIO模式下的通信,客户端-服务端是完全同步,完全耦合的。

1、BIO模式下发送和接收消息

服务端

/**
 * 服务端接受消息
 */
public class Server {
    public static void main(String[] args) {
        try {
            System.out.println("——服务端启动——");
            //定义一个ServerSocket对象进行服务端的端口注册
            ServerSocket ss = new ServerSocket(9999);
            //监听客户端的socket链接请求
            Socket socket = ss.accept();
            //从socket管道中得到一个字节输入流对象
            InputStream is = socket.getInputStream();
            //把字节输入流包装成一个缓冲字符输入流
            BufferedReader br = new BufferedReader(new InputStreamReader(is));
            String msg;
            if ((msg = br.readLine()) != null){
                System.out.println("服务端接收到:"+msg);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

客户端

/**
 * 客户端发送消息
 */
public class Client {
    public static void main(String[] args) {
        try {
            //创建socket对象请求服务端的链接
            Socket socket = new Socket("127.0.0.1",9999);
            //从socket对象中获取一个字节输出流
            OutputStream os = socket.getOutputStream();
            //把字节输出流包装成一个打印流
            PrintStream ps = new PrintStream(os);
            ps.println("hello world!");
            ps.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
2、BIO模式下多发和多收消息

服务端

/**
 * 服务端可以反复的接收消息,
 */
public class Server {
    public static void main(String[] args) {
        try {
            System.out.println("——服务端启动——");
            //定义一个ServerSocket对象进行服务端的端口注册
            ServerSocket ss = new ServerSocket(9999);
            //监听客户端的socket链接请求
            Socket socket = ss.accept();
            //从socket管道中得到一个字节输入流对象
            InputStream is = socket.getInputStream();
            //把字节输入流包装成一个缓冲字符输入流
            BufferedReader br = new BufferedReader(new InputStreamReader(is));
            String msg;
            while ((msg = br.readLine()) != null){
                System.out.println("服务端接收到:"+msg);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

客户端

/**
 * 客户端可以反复的发送消息。
 */
public class Client {
    public static void main(String[] args) {
        try {
            //创建socket对象请求服务端的链接
            Socket socket = new Socket("127.0.0.1",9999);
            //从socket对象中获取一个字节输出流
            OutputStream os = socket.getOutputStream();
            //把字节输出流包装成一个打印流
            PrintStream ps = new PrintStream(os);
            //键盘输入
            Scanner sc = new Scanner(System.in);
            while (true){
                System.out.print("请输入:");
                String msg = sc.nextLine();
                ps.println(msg);
                ps.flush();
            }

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
3、BIO模式下接收多个客户端

打开idea点击运行

点击拷贝,然后点击apply和ok,然后点击运行

客户端分别输入信息发现只能接受第一个客户端的信息,因为服务端只有一个线程只能处理一个客户端的消息

服务端

/**
 * 目标:实现服务端可以同时接收多个客户端的Socket通信需求。
 * 思路:是服务端每接收到一个客户端socket请求对象之后都交给一个独立的线程
 */
public class Server {
    public static void main(String[] args) {

        ServerSocket ss = null;
        try {
            System.out.println("——服务端启动——");
            //定义一个ServerSocket对象进行服务端的端口注册
            ss = new ServerSocket(9999);
            //监听客户端的socket链接请求
            while (true){
                Socket socket = ss.accept();
                //创建一个独立的线程来处理与这个客户端的socket
                new ServerThreadReader(socket).start();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}

 服务端使用多线程处理多个客户端

public class ServerThreadReader extends Thread{
    private Socket socket;

    public ServerThreadReader(Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run() {
        InputStream is = null;
        try {
            //从socket管道中得到一个字节输入流对象
            is = socket.getInputStream();
            //把字节输入流包装成一个缓冲字符输入流
            BufferedReader br = new BufferedReader(new InputStreamReader(is));
            String msg;
            if ((msg = br.readLine()) != null){
                System.out.println("服务端接收到:"+msg);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}

客户端

/**
 * 客户端
 */
public class Client {
    public static void main(String[] args) {
        try {
            //创建socket对象请求服务端的链接
            Socket socket = new Socket("127.0.0.1",9999);
            //从socket对象中获取一个字节输出流
            OutputStream os = socket.getOutputStream();
            //把字节输出流包装成一个打印流
            PrintStream ps = new PrintStream(os);
            //键盘输入
            Scanner sc = new Scanner(System.in);
            while (true){
                System.out.print("请输入:");
                String msg = sc.nextLine();
                ps.println(msg);
                ps.flush();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

结果显示服务端可以接受多个客户端发出的消息 

小结
1.每个Socket接收到,都会创建一个线程,线程的竞争、切换上下文影响性能;
2.每个线程都会占用栈空间和CPU资源;
3.并不是每个socket都进行lO操作,无意义的线程处理;
4.客户端的并发访问增加时。服务端将呈现1:1的线程开销,访问量越大,系统将发生线程栈溢出,线程创建失败,最终导致进程宕机或者僵死,从而不能对外提供服务。

伪异步I/O编程

伪异步I/O的通信框架,采用线程池和任务队列实现,当客户端接入时将客户端的Socket封装成一个Task(该任务实现java.lang.Runnable线程任务接口)交给后端的线程池中进行处理。JDK的线程池维护一个消息队列和N个活跃的线程,对消息队列中Socket任务进行处理,由于线程池可以设置消息队列的大小和最大线程数,因此,它的资源占用是可控的,无论多少个客户端并发访问,都不会导致资源的耗尽和宕机。

服务端

/**
 * 目标:开发实现伪异步通信架构
 */
public class Server {
    public static void main(String[] args) {
        try {
            //定义一个ServerSocket对象进行服务端的端口注册
            ServerSocket ss = new ServerSocket(9999);
            //初始化一个线程池
            HandlerSocketServerPool pool = new HandlerSocketServerPool(6,10);
            //监听客户端的socket链接请求
            while (true){
                //2、定义一个循环接收客户端的Socket链接请求
                Socket socket = ss.accept();
                //3、把socket对象交给一个线程池进行处理
                //把socket封装成一个任务对象交给线程池处理
                Runnable target = new ServerRunnableTarget(socket);
                pool.execute(target);
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

创建一个线程池

/**
 * 创建一个线程池来处理消息
 */
public class HandlerSocketServerPool {
    //1、创建一个线程池的成员变量用于存储一个线程池对象
    private ExecutorService executorService;
    //2、创建这个类的对象的时候就需要初始化线程池对象
    public  HandlerSocketServerPool(int maxThread , int queueSize){
        executorService = new ThreadPoolExecutor(3,maxThread,120,
                TimeUnit.SECONDS ,new ArrayBlockingQueue<Runnable>(queueSize));
    }
    //3、提供一个方法来提交任务给线程池的任务队列来暂存,等着线程池来处理
    public void execute(Runnable target){
        executorService.execute(target);
    }
}
/**
 * 创建多线程处理消息
 */
public class ServerRunnableTarget implements Runnable{

    private Socket socket;
    public ServerRunnableTarget(Socket socket){
        this.socket = socket;
    }
    @Override
    public void run() {
        try {
            //从socket管道中得到一个字节输入流对象
            InputStream is = socket.getInputStream();
            //把字节输入流包装成一个缓冲字符输入流
            BufferedReader br = new BufferedReader(new InputStreamReader(is));
            String msg;
            while ((msg = br.readLine()) != null){
                System.out.println("服务端接收到:"+msg);
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

客户端

/**
 * 客户端可以反复的发送消息。
 */
public class Client {
    public static void main(String[] args) {
        try {
            //创建socket对象请求服务端的链接
            Socket socket = new Socket("127.0.0.1",9999);
            //从socket对象中获取一个字节输出流
            OutputStream os = socket.getOutputStream();
            //把字节输出流包装成一个打印流
            PrintStream ps = new PrintStream(os);
            //键盘输入
            Scanner sc = new Scanner(System.in);
            while (true){
                System.out.print("请输入:");
                String msg = sc.nextLine();
                ps.println(msg);
                ps.flush();
            }

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

小结
伪异步io采用了线程池实现,因此避免了为每个请求创建一个独立线程造成线程资源耗尽的问题,但由于底层依然是采用的同步阻塞模型,因此无法从根本上解决问题。
如果单个消息处理的缓慢,或者服务器线程池中的全部线程都被阻塞,那么后续socket的i/o消息都将在队列中排队。新的Socket请求将被拒绝,客户端会发生大量连接超时。

基于BIO形式下的文件上传

服务端

/**
 * 目标:服务端开发,可以实现接收客户端的任意类型文件,并保存到服务端磁盘
 */
public class Server {
    public static void main(String[] args) {
        try {
            ServerSocket ss = new ServerSocket(8888);
            while (true){
                Socket socket = ss.accept();
                //交给一个独立的线程来处理与这个客户端的文件通信需求。
                new ServerReaderThread(socket).start();
            }
        }catch (Exception 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 {
            //1、得到一个数据输入流读取客户端发送过来的数据
            DataInputStream dis = new DataInputStream(socket.getInputStream());
            //2、读取客户端发送过来的文件类型
            String suffix = dis.readUTF();
            System.out.println("文件类型位:"+suffix);
            //3、定义一个字节输出管道负责把客户端发来的文件数据写出去
            os = new FileOutputStream("E:\\photo\\server\\"+ UUID.randomUUID().toString()+suffix);
            //4、从数据输入流中读取文件数据,写出到字节输出流中去
            byte[] bytes = new byte[1024];
            int len;
            while ((len = dis.read(bytes)) != -1){
                os.write(bytes,0,len);
            }
            System.out.println("服务器端接受文件成功!");
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            try {
                os.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

 客户端

/**
 * 目标:实现客户端上传任意类型的文件数据给服务端保存起来。
 */
public class Client {
    public static void main(String[] args) {
        try {
            //1、请求与服务端的Socket链接
            Socket socket = new Socket("127.0.0.1",8888);
            //2、把字节输出流包装成一个数据输出流
            DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
            //3、法发送上传文件的后缀给服务端
            dos.writeUTF(".png");
            //4、把文f件数据发送给服务嵩进行接收
            InputStream is = new FileInputStream("E:\\photo\\figure\\1.png");
            byte [] bytes = new byte[1024];
            int len;
            while ((len = is.read(bytes)) != -1){
                dos.write(bytes,0,len);
            }
            dos.flush();
            //通知服务端这边的数据发送完毕
            socket.shutdownOutput();
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

Java BIO模式下的端口转发思想


客户端

/**
 *目标:BIO模式下的端口转发思想-服务端实现。
 * 服务端实现的需求:
 * 1、注册端口
 * 2、接收客户端的socket连接,交给一个独立的线程来处理。
 * 3、把当前连接的客户端socket存入到一个所谓的在线socket集合中保存
 * 4、接收客户端的消息,然后推送给当前所有在线的socket接收。
 */
public class Server {
    //定义一个静态集合
    public static List<Socket> allSocketOnLine = new ArrayList<>();
    public static void main(String[] args) {

        try {
            //
            ServerSocket ss = new ServerSocket(9999);
            while (true){
                Socket socket = ss.accept();
                //把登录的客户端socket存入到一个在线集合中去
                allSocketOnLine.add(socket);
                //为当前登录成功的socket分配一个独立的线程来处理与之通信
                new ServerReaderThread(socket).start();
            }
        }catch (Exception e){
            e.printStackTrace();
        }

    }
}
public class ServerReaderThread extends Thread{
    private Socket socket;

    public ServerReaderThread(Socket socket) {
        this.socket = socket;
    }
    @Override
    public void run() {
        try {
            //1、从socket中去获取当前客户端的输入流
            BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            String msg;
            while ((msg = br.readLine()) != null){
                //2、服务端接收到了客户端的消息之后,是需要推送给当前所有的在线socket
                sendMsgToAllClient(msg);
            }
        }catch (Exception e){
            System.out.println("当前有人下线了");
            //从在线socket集合中移除本socket
            Server.allSocketOnLine.remove(socket);
        }
    }

    /**
     * 把当前客户端发来的消息推送给全部在线的socket
     * @param msg
     */
    private void sendMsgToAllClient(String msg){
        for (Socket sk : Server.allSocketOnLine) {
            PrintStream ps = null;
            try {
                ps = new PrintStream(sk.getOutputStream());
            } catch (IOException e) {
                e.printStackTrace();
            }
            ps.println(msg);
            ps.flush();
        }
    }
}

  • 28
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值