BIO通信代码示例

发布时间:2024年01月13日

1.Client

package bio;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;

public class Client {

    public static void main(String[] args) throws IOException {
        // 客户端启动必备
        Socket socket = null;
        // 实例化与服务端通信的输入输出流
        ObjectInputStream input = null;
        ObjectOutputStream output = null;
        // 服务器的通信地址
        InetSocketAddress addr = new InetSocketAddress("127.0.0.1", 8888);
        
        try {
            
            socket = new Socket();
            // 连接服务器
            socket.connect(addr);
            System.out.println("Connect Server success !");
            output = new ObjectOutputStream(socket.getOutputStream());
            input = new ObjectInputStream(socket.getInputStream());
            System.out.println("Ready send message");
            // 向服务器输出请求
            output.writeUTF("Cover");
            output.flush();
            // 接收服务器的输出
            System.out.println(input.readUTF());
        } finally {
            if (socket != null) {
                socket.close();
            }
            if (output != null) {
                output.close();
            }
            
            if (input != null) {
                input.close();
            }
        }
        
        
    }
}

2.单线程版的Server

在这里插入图片描述

package bio;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;

public class ServerSingle {
    public static void main(String[] args) throws IOException {
        // 服务端启动必备
        ServerSocket serverSocket = new ServerSocket();
        // 表示服务端在哪个端口上监听
        serverSocket.bind(new InetSocketAddress(8888));
        System.out.println("Start Server....");
        int connectCount = 0;
        
        try {
            while(true) {
                System.out.println("一轮循环");
                Socket socket = serverSocket.accept();
                System.out.println("accept client socket .... total = " + (++connectCount));

                try (
                        ObjectInputStream inputStream = new ObjectInputStream(socket.getInputStream());
                        ObjectOutputStream outputStream = new ObjectOutputStream(socket.getOutputStream());
                ) {

                    // 接收客户端的输出,也就是服务端的输入
                    String userName = inputStream.readUTF();
                    System.out.println("Accept Client message:" + userName);
                    // 服务器的输出,也就是客户端的输入
                    outputStream.writeUTF("Hello" + userName);
                    outputStream.flush();

                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    socket.close();
                }
            }            
        } finally {
            serverSocket.close();
        }

    }
}


单线程版的Server,只能一个一个地处理客户端的请求,如果正在处理的客户端发生了阻塞,比如说没有发送数据给服务器端,那么后续的客户端将会受限阻塞的客户端,无法处理下一个请求
有人可能会说一个线程处理一个客户端请求,这样一个线程发生阻塞时,不影响其他的客户端处理,于是乎又在上面代码进行改良

3.多线程的Server

在这里插入图片描述

package bio;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;

public class Server {
    public static void main(String[] args) throws IOException {
        //服务端启动必备
        ServerSocket serverSocket = new ServerSocket();
        // 表示服务器在哪个端口上监听
        serverSocket.bind(new InetSocketAddress(8888));
        System.out.println("Start Server ....");
        try {
            while (true) {
                new Thread(new ServerTask(serverSocket.accept())).start();
            }
        } finally {
            serverSocket.close();
        }
    }
    
    
    private static class ServerTask implements Runnable {
        
        private Socket socket = null;

        public ServerTask(Socket socket) {
            this.socket = socket;
        }
        
        @Override
        public void run() {
            // 实例化与客户端通信的输入输出流
            try (
                    ObjectInputStream inputStream = new ObjectInputStream(socket.getInputStream());
                    ObjectOutputStream outputStream = new ObjectOutputStream(socket.getOutputStream())
            ) {
                
                //接收客户端的输出,也就是服务器的输入
                String userName = inputStream.readUTF();
                System.out.println("Accept client message:" + userName);
                
                // 服务器的输出,也就是客户端的输入
                outputStream.writeUTF("Hello," + userName);
                outputStream.flush();
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

一个客户端请求一个线程处理,解决了上面当一个客户端发生阻塞时,其他客户端请求无法处理的问题,但是又有新的问题来了
一个客户端请求就需要创建一个线程来处理,对于操作系统来说,线程是一种昂贵的资源,线程的创建和销毁都需要CPU的调度,这会给操作系统带来一定的压力,即便机器配置很高,但会把服务器的资源吃的很满,这个时候我们可以用线程池来做,避免线程的频繁创建与销毁,我们再来改良一下

4.线程池版的Server

在这里插入图片描述

package bio;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class ServerPool {
    
    private static final Integer CPU_NUM = Runtime.getRuntime().availableProcessors();
    
    private static final Integer CORE_SIZE = CPU_NUM;
    
    private static final Integer MAX_SIZE = CPU_NUM * 2;

    private static ThreadPoolExecutor pool = new ThreadPoolExecutor(CORE_SIZE, MAX_SIZE, 0L, TimeUnit.SECONDS,
            new ArrayBlockingQueue<>(100), (Runnable r) -> {
        Thread t = new Thread(r);
        t.setName("bio");
        return t;
    }, new ThreadPoolExecutor.AbortPolicy());

    public static void main(String[] args) throws IOException {
        // 服务端启动必备
        ServerSocket serverSocket = new ServerSocket();
        // 表示服务端在哪个端口上监听
        serverSocket.bind(new InetSocketAddress(8888));
        System.out.println("Start Server....");
        try {
            while (true) {
                pool.execute(new ServerTask(serverSocket.accept()));
            }
        } finally {
            serverSocket.close();
        }
    }
    
    private static class ServerTask implements Runnable {
        private Socket socket = null;

        public ServerTask(Socket socket ) {
            this.socket = socket;
        }
        @Override
        public void run() {
            // 实例化与客户端通信的输入输出流
            try (
                ObjectInputStream inputStream = new ObjectInputStream(socket.getInputStream());
                ObjectOutputStream outputStream = new ObjectOutputStream(socket.getOutputStream());
                ) {
                
                // 接受客户端的输出,也就是服务端的输入
                String userName = inputStream.readUTF();
                System.out.println("Accept Client Message:" + userName);
                // 服务器的输出,也就是客户端的输入
                outputStream.writeUTF("Hello, " + userName);
                outputStream.flush();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                try {
                    socket.close();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }
}

经过一番优化,其实线程池版的Server已经可以抗住一定规模的客户端请求了,如果你的用户规模不是很高,或者说机器数量还可以的话,上面的实现还算可以,但是仍然存在问题,假如当前客户端请求没有发送数据,服务端启动的线程就需要等待,直到数据发送过来,而且也不支持数据灵活读取,不能读取一段数据,接收一段数据,因为BIO是面向流的,只能等这个流被完整读取,才可以进行处理,哪怕服务端要先读取一段,也是不行的,如果还是扛不住,就得需要进一步的优化,比方可以考虑NIO方式,NIO是可以实现单机百万并发连接的
感兴趣的朋友,可以关注我下期的NIO

文章来源:https://blog.csdn.net/Cover_sky/article/details/135414156
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。