java网络编程

BIO

java的阻塞模型,阻塞的点有两个,就是accept,和接受用户的输入的时候

package network;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;

public class SocketIO {
    public static void main(String[] args) throws IOException {
        ServerSocket server = new ServerSocket(9090);

        System.out.println("服务端启动");

        Socket socket = server.accept(); //阻塞

        System.out.println("客户端的端口号是:"+socket.getPort());

        InputStream is = socket.getInputStream();
        BufferedReader br = new BufferedReader(new InputStreamReader(is));

        System.out.println(br.readLine()); //阻塞
        while (true){

        }
    }
}

然后尝试连接

但是此时服务器只能处理一个请求,如果这时候再添加一个连接,是没有效果的

客户端连接到java 的服务端是需要先和内核进行tcp三次握手创建连接的

java中的一些代码其实有的是调用的是内核的封装好的方法
new ServerSocket(9090);
相当于

socket() = 6fd
bind(6fd, 9090)
listen(6fd)

Socket client(7fd) = server.accept(); 相当于 accept(6fd) ==> 7fd
对于这种情况,通常是每来一个连接,创建一个线程来专门进行IO处理accept,这是最早的BIO
但是线程的连接由于java本身的原因是不可以很多个的,也就没法收到很多的连接

这个时候对于内核进行了升级,出现了NIO
也就是内核允许在BIO出现的两个阻塞 不再进行阻塞

NIO

package network;


import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.LinkedList;

public class SocketNIO {
    public static void main(String[] args) throws IOException, InterruptedException {
        LinkedList<SocketChannel> clients = new LinkedList<>();
        ServerSocketChannel ss = ServerSocketChannel.open();
        ss.bind(new InetSocketAddress(9090));
        ss.configureBlocking(false); //设置非阻塞
        while(true){
            Thread.sleep(1000);
            SocketChannel client = ss.accept(); //这个时候不会阻塞
            if(client == null){
                System.out.println("null----");
            } else {
                client.configureBlocking(false);
                int port = client.socket().getPort();
                System.out.println("port: "+port);
                clients.add(client);
            }
            //buffer这里是串行化公用一个buffer了,一般每个channel都有鸽子的ByteBuffer
            ByteBuffer buffer = ByteBuffer.allocateDirect(4096); //为读写的区域指定大小,这块区域可以读写,可以在堆里,也可以在堆外
            for(SocketChannel sc: clients){
                int num = sc.read(buffer); //先把读到的东西放进buffer里面,这里也不会阻塞
                if(num > 0){
                    buffer.flip();
                    byte[] bytes = new byte[buffer.limit()];
                    buffer.get(bytes);

                    String res = new String(bytes);
                    System.out.println(sc.socket().getPort()+": "+res);
                    buffer.clear();
                }
            }
        }
    }
}

这里面现在就可以只有一个主线程来操作这个,其实也可以分为两个线程来
一个主线程来不停地接受accept,通常较boss,
另一个线程来不断地遍历这些客户端,处理他们的输入输出,通常较works

NIO的问题:不断地遍历所有的客户来看他们是否有输入输出,假设现在有10000个连接,那么每次循环你都要循环这些连接,看他们有没有输入,但是当这些连接只有少数的给你发数据,这就会造成资源浪费

这个时候如果有东西可以告诉你有哪些连接给你发数据就好了,没有发数据的就不管
内核提供了这样的功能,就是多路复用器,比如select,poll,epoll.kqueue
多路复用器的作用就是可以理解为监听那些连接给你有发数据,这样程序的复杂度就会少很多,
因为这样的事情我交给了内核来做,内核来管理,java程序这边我只要一句话就行

多路复用

package network;


import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set;

public class Select {


    private ServerSocketChannel server = null;
    private Selector selector = null;
    private int port = 9090;
    public void initServer() throws IOException {
        server = ServerSocketChannel.open();
        server.configureBlocking(false);
        server.bind(new InetSocketAddress(port));
        //将这个server注册到selector里面,并且制定了我想得到的东西
        server.register(selector, SelectionKey.OP_ACCEPT);
    }
    public void start() throws IOException {
        initServer();
        System.out.println("服务器启动了。。。");
        while(true){
            while(selector.select(0) > 0){
                Set<SelectionKey> selectionKeys = selector.selectedKeys();

                Iterator<SelectionKey> iterator = selectionKeys.iterator();
                while(iterator.hasNext()){
                    SelectionKey key = iterator.next();

                    iterator.remove();

                    if(key.isAcceptable()){ //第一次进来是还不能读的
                        acceptHandler(key);
                    } else if(key.isReadable()){
                        readHandler();
                    }
                }
            }
        }
    }
    public void acceptHandler(SelectionKey key) throws IOException {
        ServerSocketChannel channel = (ServerSocketChannel) key.channel();
        SocketChannel client = channel.accept();

        client.configureBlocking(false);

        ByteBuffer buffer = ByteBuffer.allocate(8192);
        client.register(selector, SelectionKey.OP_READ, buffer);  //设置可以读
        System.out.println("新客户端  "+ client.getRemoteAddress());
    }
    public void readHandler(SelectionKey key) throws IOException {
        //通过这个key,我可以取到曾经的buffer和曾经的clinet
        SocketChannel client = (SocketChannel) key.channel();
        ByteBuffer buffer = (ByteBuffer) key.attachment();
        buffer.clear();
        int read = 0;
        while(true) {
            read = client.read(buffer);
            if(read > 0){
                buffer.flip(); //把数据取出来,然后再返回给客户端
                while(buffer.hasRemaining()) {
                    client.write(buffer);
                }
                buffer.clear();
            } else if(read == 0){
                break;
            } else {
                client.close();
                break;
            }
        }
    }

    public static void main(String[] args) throws IOException {
        Select s = new Select();
        s.start();
    }
}

个人qq:835493858 有事联系我
原文地址:https://www.cnblogs.com/wpbing/p/14386045.html