IO多路复用(埋坑

Java · 01-25
IO多路复用(埋坑

简单整理了一下IO多路复用技术,主要是为了给Netty铺垫。

可能的疑问

  • 为什么不用多线程?

    • 在很多rpc模型中可能会用到多线程,但是多线程上下文切换要处理操作句柄,线程多了的时候,代价很高。
  • 单线程操作会不会因为堵塞导致数据丢失?

    • 不会,因为DMA控制器会操作。

模型

C10K

是一个优化网络套接字以同时处理大量客户端连接的问题,表示处理 10000 个并发连接。

Socket模型

关于Socket,我看过最简洁的描述就是

Socket可以看成是一个四元组(Source:port, Desc:port)

他就是一个插头,一头插在客户端,一头插在服务端。 他如何运行的呢? 我们可以观察一下系统调用介绍 1706168204574.png 也就是说在调用的时候,需要传入三个参数,篇幅过长我直接总结,感兴趣的可以通过

man 2 socket

自行查阅。

  • domain : 指定地址类型,比如ipv4 ipv6
  • type: 通信的类型,比如tcp, udp
  • protocol: socket传输协议编号,一般不设置。

返回值就是

On success, a file descriptor for the new socket is returned. On error, -1 is returned, and errno is set to in‐dicate the error.

Socket模型生命周期 1706168532386.png

简单粗暴的IO模型?
BIO

Blocking 显而易见这是堵塞的IO模型

public class SocketBio {
	public static void main(String[] args) throws IOException {
		ServerSocket server = new ServerSocket(9999);
		System.out.println("listening on 9999");
		while (true){
			Socket client = server.accept();
			System.out.printf("client %s #online\n", client.getPort());
			new Thread(new Runnable() {
				@Override
				public void run() {
					InputStream in = null;
					try{
						in = client.getInputStream();
						BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(in));
						while(true){
							String dateLine = bufferedReader.readLine();
							if(dateLine != null){
								System.out.printf("client %s #received: \n",client.getPort(), dateLine);
							}else{
								System.out.printf("client: %s #closed\n",client.getPort());
								client.close();
								break;
							}
						}
					} catch (Exception e) {
						e.printStackTrace();
					}
				}
			}).start();
		}
	}
}

NIO

No-Blocking

public class SocketNio {
	public static void main(String[] args) throws Exception {
		LinkedList clients = new LinkedList<>();
		ServerSocketChannel ss = ServerSocketChannel.open();
		ss.bind(new InetSocketAddress(9999));
		ss.configureBlocking(false);
		ByteBuffer buffer = ByteBuffer.allocateDirect(4096);
		while(true){
			SocketChannel client = ss.accept();
			if(client == null){

			}else{
				client.configureBlocking(false);
				int port = client.socket().getPort();
				System.out.printf("client: %d #accepted\n", port);
				clients.add(client);
			}
			Iterator iterator = clients.iterator();
			while(iterator.hasNext()){
				SocketChannel c = iterator.next();
				buffer.clear();
				int num = c.read(buffer);
				if(num > 0){
					buffer.flip();
					byte[] tmp = new byte[buffer.limit()];
					buffer.get(tmp);
					System.out.printf("client %d #received: %s \n", c.socket().getPort(), new String(tmp));
				}else if(num == -1){
					System.out.printf("Client %d disconnected\n", c.socket().getPort());
					iterator.remove();
					c.close();
				}
			}
		}
	}
}

IO多路复用

看上面的BIONIO的代码,我们就可以发现: BIO的缺点:

  • 进程占据资源
  • 上下文切换
  • ...

NIO的缺点:

  • 当连接多了之后,需要维护的东西很多。

于是可能会想到, 能否只用一个进程去维护多个socket呢?

select

如果有很多请求(但是select限制的最大请求上限是1024)连接到server,select会对所有请求遍历,记录下有数据到达的请求IO,然后返回记录信息,客户端读取。 所以当请求的数量很多但是数据请求很少的时候, 会造成很多空读取。 1706186857657.png

int select(int maxfdp1,fd_set *readset,fd_set *writeset,fd_set *exceptset,const struct timeval *timeout)

参数说明

  • mafdfp1 描述待轮讯的连接的个数
  • fd_set: readset writeset exceptset 指定了让kernel读、写、异常的描述
  • timeout 描述了一个就绪可以花费多长时间。

原理图(copy来的 1706187141159.png

poll

poll和select没有太大的区别,都是通过管理多个描述符来进行轮讯,但是poll没有最大连接数限制,与此同时,poll的一个缺点就是: 大量fd的数组被整体复制于用户态和内核的地址空间之间,而不论这些fd是否就绪,她的开销随着fd的数量增大线性增大。

# include 
int poll ( struct pollfd * fds, unsigned int nfds, int timeout);
Epoll

epoll是在Linux-2.6中提出的,selectpoll的增强版。 epoll使用一个文件描述管理多个fd,把用户关系的fd存放到kernel的一个事件表之中,这样用户空间和kernel的copy只需要一次。

1706187866094.png

大概就是: java端创建一个socket,fd是fd4,bind端口之后开始监听,这时候epoll会调用epoll_create(int size) 创建红黑树空间fd6,这个空间是被监听事件的数目,然后执行epoll_ctl,把fd6和fd4关联起来,当有链接有数据的时候,记录IO的编号,把这个编号存储在一个链表之中,当epoll调用epoll_wait的时候,就把这个事件相关的链表返回,之后调用程序只需要根据记录的信息,读写产生的事件IO即可。

#include 
int epoll_create(int size);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);

先写这么多吧,后续在补充

Java Netty IO
Theme Jasmine