网络编程(二)BIO

同步阻塞 IO(BIO)— 原生态

原理

graph TB
    subgraph 客户端
		id1[Socket]==连接服务器==>id11[通道];
		id3[客户端请求]--流-->id2[OutputStream];
		id2-->id1;
		id1-.读取.->id12[InputStream];
		id12-.流.->id13[BufferedReader];
		id13-.信息.->id14[msgRes];
    end
    subgraph 服务器ServerSocket
		id11==>id21[通道];
		id4[Socket]==accept==>id21;
		id4--读取-->id5[InputStream];
		id5--流-->id6[BufferedReader]
		id6--信息-->id7[msg];
		id8[服务器响应]-.流.->id9[OutputStream];
		id9.->id4;
    end

实现

1.gif

服务器端实现

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.Charset;

public class BioServer {
    //编码格式
    private  static Charset charset = Charset.forName("UTF-8");
    public static void main(String[] args) {
        int port = 1100;
        //ServerSocket提供链接的场地
        try ( ServerSocket  socketServer = new ServerSocket(port)){
            while (true){
                //接收连接,如果没有连接建立,这里会阻塞
                //Socket建立和客户端连接的通道
                Socket socket = socketServer.accept();
                //从通道中读取客户端发送给服务器的信息
                BufferedReader reader = new BufferedReader(
                        new InputStreamReader(socket.getInputStream(), charset)
                );
                String msg = null;
                //连接进来后,会在这里等待客户端发送消息。
                while ((msg =reader.readLine())!=null){
                    System.out.println("服务器收到信息:"+msg);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

客户端实现

import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Scanner;

public class BioClient implements  Runnable{
    private  String address;
    private  int  port;

    public static void main(String[] args) {
        //客户端和服务器建立连接
        BioClient client = new BioClient("localhost", 1100);
        client.run();
    }
    @Override
    public void run() {
        try(Socket socket = new Socket(address, port);
            OutputStream outputStream = socket.getOutputStream()) {
            Scanner scanner = new Scanner(System.in);
            System.out.println("请输入发送给服务器的信息:");
            String msg = scanner.nextLine();
            outputStream.write(msg.getBytes("UTF-8"));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public int getPort() {
        return port;
    }

    public void setPort(int port) {
        this.port = port;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }
    public BioClient(String address, int port) {
        super();
        this.address=address;
        this.port = port;
    }
}

这就是一个简单的 BIO,我们可以看到:服务器端代码必须要在连接处和接收消息处阻塞,只用得到了相应的信息,才会往下继续执行。如果多个客户端一起请求,大家都会阻塞在此处,这样的程序效率及其低下,甚至长期等待不能用。有什么办法能够解决这样的问题呢?。

同步阻塞 IO(BIO)— 多线程

原理

graph TB
    subgraph 客户端
		id1[Socket]==连接服务器==>id11[通道];
		id3{客户端请求}--流-->id2{OutputStream};
		id2-->id1;
		id1-.读取.->id12[InputStream];
		id12-.流.->id13[BufferedReader];
		id13-.信息.->id14[msgRes];
    end
    subgraph 服务器ServerSocket
		id11==>id21[通道];
		id4[Socket]==accept==>id21;
		id4==投入线程池==>id22[newFixedThreadPool]
		id22--读取-->id5[InputStream];
		id5--流-->id6[BufferedReader]
		id6--信息-->id7[msg];
		id8[服务器响应]-.流.->id9{OutputStream};
		id9.->id22;
    end

服务器端多线程实现

import cn.mldn.info.HostInfo;

import java.io.IOException;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
public class BIOEchoServerThread {
    public static void main(String[] args) throws Exception{
        // 设置监听端口
        ServerSocket serverSocket = new ServerSocket(HostInfo.PORT) ;
        System.out.println("服务器端已经启动,监听的端口为:" + HostInfo.PORT);
        boolean flag = true ;
        while(flag) {
            Socket client = serverSocket.accept() ;
            new Thread(new EchoClientHandler(client)).start();
        }
        serverSocket.close() ;
    }

    private static class EchoClientHandler implements Runnable {
        // 每一个客户端都需要启动一个任务(task)来执行。
        private Socket client ;
        private Scanner scanner ;
        private PrintStream out ;
        // 循环标记
        private boolean flag = true ;
        public EchoClientHandler(Socket client) {
            // 保存每一个客户端操作
            this.client = client ;
            try {
                this.scanner = new Scanner(this.client.getInputStream()) ;
                // 设置换行符
                this.scanner.useDelimiter("\n") ;
                this.out = new PrintStream(this.client.getOutputStream()) ;
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        @Override
        public void run() {
            while(this.flag) {
                // 现在有数据进行输入
                if (this.scanner.hasNext()) {
                    // 去掉多余的空格内容
                    String val = this.scanner.next().trim() ;
                    System.err.println("{服务器端}" + val);
                    if("byebye".equalsIgnoreCase(val)) {
                        this.out.println("ByeByeByte...");
                        this.flag = false ;
                    } else {
                        out.println("【ECHO】" + val);
                    }
                }
            }
            this.scanner.close();
            this.out.close();
            try {
                this.client.close();
            } catch (IOException e) {
            }
        }
    }
}

这里是利用多线程的方式来实现,在连接处多开线程去处理,这样多个客户端连接时,就不会大家一致排队阻塞在此处,但是阻塞还是存在的。还有什么更好的办法来解决这样的问题吗?

服务器端线程池实现

import cn.mldn.info.HostInfo;

import java.io.IOException;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class BIOEchoServer {
    public static void main(String[] args) throws Exception{
        // 设置监听端口
        ServerSocket serverSocket = new ServerSocket(HostInfo.PORT) ;
        System.out.println("服务器端已经启动,监听的端口为:" + HostInfo.PORT);
        boolean flag = true ;
        ExecutorService executorService = Executors.newFixedThreadPool(10) ;
        while(flag) {
            Socket client = serverSocket.accept() ;
            executorService.submit(new EchoClientHandler(client)) ;
        }
        executorService.shutdown() ;
        serverSocket.close() ;
    }

    private static class EchoClientHandler implements Runnable {
        // 每一个客户端都需要启动一个任务(task)来执行。
        private Socket client ;
        private Scanner scanner ;
        private PrintStream out ;
        // 循环标记
        private boolean flag = true ;
        public EchoClientHandler(Socket client) {
            // 保存每一个客户端操作
            this.client = client ;
            try {
                this.scanner = new Scanner(this.client.getInputStream()) ;
                // 设置换行符
                this.scanner.useDelimiter("\n") ;
                this.out = new PrintStream(this.client.getOutputStream()) ;
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        @Override
        public void run() {
            while(this.flag) {
                // 现在有数据进行输入
                if (this.scanner.hasNext()) {
                    // 去掉多余的空格内容
                    String val = this.scanner.next().trim() ;
                    System.err.println("{服务器端}" + val);
                    if("byebye".equalsIgnoreCase(val)) {
                        this.out.println("ByeByeByte...");
                        this.flag = false ;
                    } else {
                        out.println("【ECHO】" + val);
                    }
                }
            }
            this.scanner.close();
            this.out.close();
            try {
                this.client.close();
            } catch (IOException e) {
            }
        }
    }
}

如上所示,我们再次改动,改成以线程池的办法来连接。但是线程池也有问题:
请求大于线程,没有足够的线程来处理,响应时间很长,甚至服务器拒绝。
阻塞等待接收客户端的数据时,这段时间占着线程,而池中线程数是有限的,并发量大时,将导致没有线程处理请求,请求的响应时间长,甚至拒绝服务。
从上面三部分的代码可以看出来,代码性能等问题,全是阻塞惹的祸,如果没有阻塞就好了,如果能不阻塞,在没有数据时,就去干点别的事情,有数据了才处理数据。

客户端实现

import cn.mldn.info.HostInfo;
import cn.mldn.util.InputUtil;

import java.io.PrintStream;
import java.net.Socket;
import java.util.Scanner;

public class BIOEchoClient {
    public static void main(String[] args) throws Exception {
        // 定义连接的主机信息
        Socket client = new Socket(HostInfo.HOST_NAME,HostInfo.PORT) ;
        // 获取服务器端的响应数据
        Scanner scan = new Scanner(client.getInputStream()) ;
        scan.useDelimiter("\n") ;
        // 向服务器端发送信息内容
        PrintStream out = new PrintStream(client.getOutputStream()) ;
        // 交互的标记
        boolean flag = true ;
        while(flag) {
            String inputData = InputUtil.getString("请输入要发送的内容:").trim() ;
            // 把数据发送到服务器端上
            out.println(inputData);
            if(scan.hasNext()) {
                String str = scan.next().trim() ;
                System.out.println(str);
            }
            if ("byebye".equalsIgnoreCase(inputData)) {
                flag = false ;
            }
        }
        client.close();
    }
}

上一篇 网络编程(一)网络编程基础
下一篇 网络编程(三)NIO