java远程通信之socket、selector、select/poll/epoll/BIO/NIO/AIO的理解

1 篇文章 0 订阅
1 篇文章 0 订阅

服务与服务之间的通信,在Java领域中有很多可实现远程通讯的技术,例如:RMI、MINA、ESB、Burlap、Hessian、SOAP、EJB和JMS等。

远程通信之socket

Java BIO (blocking I/O): 同步并阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善

代码demo 1

package com.socket.demo;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.ServerSocket;
import java.net.Socket;
/**
 * autor:lld
 * 2020/6/1119:59
 */
public class ServerSocketDemo {
    public static void main(String[] args) {
        ServerSocket serverSocket = null;
        try {
            serverSocket = new ServerSocket(8080);
            //通过accept去阻塞(连接组啥)并监听客户端连接
            Socket socket =serverSocket.accept();
            System.out.println(socket.getPort());
            //获取到连接之后,拿到输入输出流
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            //读取客户端的数据
            String clinetMessage = bufferedReader.readLine();
            System.out.println("接收到客户端的数据为:"+clinetMessage);
            BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
            bufferedWriter.write("收到\n");
            bufferedWriter.flush();

            bufferedReader.close();
            bufferedWriter.close();
        }catch (IOException e){
            e.printStackTrace();
        }
    }
}
package com.socket.demo;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * autor:
 * 2020/6/1119:59
 */
public class ClientSocketDemo {

    public static void main(String[] args) {

        try {
            Socket socket = new Socket("localhost",8080);

            BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
            bufferedWriter.write("服务端你好\n");
            bufferedWriter.flush();
            //获取到连接之后,拿到输入输出流
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            //读取客户端的数据
            String serverMessage = bufferedReader.readLine();
            System.out.println("接收到服务端的数据为:"+serverMessage);

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

    }
}

### 代码demo 1服务端保持不关闭,通过线程池来管理线程,允许多个客户端建立连接通道

package test;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Server {

	public Server() {
		
	}
	public static ExecutorService executorService = Executors.newCachedThreadPool();
	public static void main(String[] args) {
		ServerSocket serverSocket = null;
		try {
			serverSocket = new ServerSocket(8080);
			while(true){
				Socket socket = serverSocket.accept();
				executorService.execute(() -> {
					try {
						BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
						String s = bufferedReader.readLine();
						System.out.println("接收到客户端的信息:" + s);
						BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
						bufferedWriter.write("村上春树1\n");
						bufferedWriter.flush();

						bufferedReader.close();
						bufferedWriter.close();
					} catch (IOException e) {
						e.printStackTrace();
					}
				});
			}
		} catch (IOException e) {
			e.printStackTrace();
		}finally {
			//TODO
			if(serverSocket!=null){
				try {
					serverSocket.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}

}

package test;
import java.io.*;
import java.net.Socket;
import java.net.UnknownHostException;
public class Client {
	public Client() {
		// TODO Auto-generated constructor stub
	}
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		try {
			Socket socket = new Socket("127.0.0.1", 8080);

			BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
			bufferedWriter.write("服务端你好\n");
			bufferedWriter.flush();
			BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
			String s = bufferedReader.readLine();
			System.out.println("接收到服务端的消息:"+s);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

NIO (non-blocking I/O): 同步非阻塞,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。(serverSocketchannel一个支持IO重用,所以一个线程就可以处理多个客户端的连接),多路复用机制

这个机制可以通过鱼竿钓鱼的来辅助理解,假设有100个鱼竿,selector多路复用器就会去轮询,如果有鱼竿上钩了就会通知我们,去启动一个线程进行处理

DEMO1
package test;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;

public class NioServer {
    static Selector selector;
    public static void main(String[] args) {
        try {
            selector = Selector.open();
            ServerSocketChannel serverSocketChannel =  ServerSocketChannel.open();
            //selector 必须是非阻塞
            serverSocketChannel.configureBlocking(false);
            serverSocketChannel.bind(new InetSocketAddress(8080));
            //SelectionKey.OP_ACCEPT  监听的操作
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
            while(true){
                //阻塞机制
                selector.select();
                //获取到监听集合
                Set<SelectionKey> selectionKeys =  selector.selectedKeys();
                Iterator<SelectionKey> iterator = selectionKeys.iterator();
                while (iterator.hasNext()){
                    SelectionKey selectionKey =  iterator.next();
                    iterator.remove();
                    //判断是否为链接事件
                    if(selectionKey.isAcceptable()){
                        handleAccept(selectionKey);
                    }
                    //判断是否为读事件
                    else  if(selectionKey.isReadable()){
                        handleRead(selectionKey);
                    }
                }
            }



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


    }


    private static void handleAccept(SelectionKey selectionKey){
        ServerSocketChannel serverSocketChannel = (ServerSocketChannel) selectionKey.channel();
        try {
            SocketChannel socketChannel = serverSocketChannel.accept();
            //非阻塞
            socketChannel.configureBlocking(false);
            socketChannel.write(ByteBuffer.wrap("你好,我是服务器端".getBytes()));
            //注册读事件
            socketChannel.register(selector,SelectionKey.OP_READ);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    private static void handleRead(SelectionKey selectionKey){
        SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
        try {
            //不一定有值
            socketChannel.read(byteBuffer);
            System.out.println("客户端的返回值:"+new String(byteBuffer.array()));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

package test;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;

public class NioClient {

    static Selector selector;
    public static void main(String[] args) {
        try {
            selector = Selector.open();
            SocketChannel socketChannel = SocketChannel.open();
            socketChannel.configureBlocking(false);
            socketChannel.connect(new InetSocketAddress("localhost",8080));
            //注册连接事件
            socketChannel.register(selector, SelectionKey.OP_CONNECT);
            while(true){
                //阻塞机制
                selector.select();
                Set<SelectionKey> selectionKeys = selector.selectedKeys();
                Iterator<SelectionKey> iterator = selectionKeys.iterator();
                while(iterator.hasNext()){
                    SelectionKey selectionKey = iterator.next();
                    //判断是否为连接事件
                    if(selectionKey.isConnectable()){
                        handleConnection(selectionKey);
                    }else if(selectionKey.isReadable()){
                        handleRead(selectionKey);
                    }
                }

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


    private static void handleConnection(SelectionKey selectionKey) throws IOException {
        SocketChannel socketChannel = (SocketChannel) selectionKey.channel();

        if (socketChannel.isConnectionPending()){
            socketChannel.finishConnect();
        }
        socketChannel.configureBlocking(false);
        socketChannel.write(ByteBuffer.wrap("服务端你好,我是客户端".getBytes()));
        socketChannel.register(selector,SelectionKey.OP_READ);
    }
    private static void handleRead(SelectionKey selectionKey) throws IOException {
        SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
        socketChannel.read(byteBuffer);
        System.out.println("服务端发送的消息为:"+new String(byteBuffer.array()));
    }
}

AIO异步非阻塞,服务器实现模式为一个有效请求一个线程,客户端的I/O请求都是由OS先完成了再通知服务器应用去启动线程进行处理异步非阻塞,服务器实现模式为一个有效请求一个线程,客户端的I/O请求都是由OS先完成了再通知服务器应用去启动线程进行处理

相当于100个鱼竿在钓鱼的过程中,selector多路复用器不去轮询,让鱼上钩之后,触发事件进行通知去启用一个线程来处理。

select / poll /epoll 理解

(1)select->时间复杂度O(n)
它仅仅知道了,有I/O事件发生了,却并不知道哪几个流(可能一个,多个,甚至全部),只能通过无差别的轮询,找到能读出数据或者写入数据的流,对他们进行操作。同时处理的流越多,无差别轮询就越长
(2)poll->时间复杂度O(n)
poll本质上和select没有区别,它将用户传入的数组拷贝到内核空间,然后查询每个fd对应的设备状态,但是他没有最大连接数的限制,原因是它是基于链表来存储的
(3)epoll->时间复杂度为O(1)
epoll可以理解为event poll,不同于忙轮询和无差别轮询,epoll会把哪个流发生了怎样的I/o事件通知我们,所以我们说epoll实际上是事件驱动(每个时间关联fd)的,此时我们对这些流的操作都是有意义的。

select,poll,epoll都是IO多路复用的机制。I/O多路复用就通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作,但select,poll,epoll本质上都是同步I/O,因为他们都需要在读写时间就绪后自己负责进行读写,也就是说这个读写过程是阻塞的,而异步I/O则无需自己负责进行读写,异步I/O的实现会负责吧数据从内核拷贝到用户空间。
epoll跟select都能提供多路I/O复用的解决方案。在现在的Linux内核里有都能够支持,其中epoll是Linux所持有,而select则应该是POSIX所规定,一般操作系统均有实现。

使用场景

BIO方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4以前的唯一选择,但程序直观简单易理解。

NIO方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程比较复杂,JDK1.4开始支持。

AIO方式使用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂,JDK7开始支持。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值