jdk下的bio
SocksSocketImpl
- 这两类操作系统都还存在一个 SocksSocketImpl 类,它其实主要是实现了防火墙安全会话转换协议,包括 SOCKS V4 和 V5(SOCKS:防火墙安全会话转换协议 (Socks: Protocol for sessions traversal across firewall securely) SOCKS 协议提供一个框架,为在 TCP 和 UDP 域中的客户机/服务器应用程序能更方便安全地使用网络防火墙所提供的服务,并提供一个通用框架来使这些协议安全透明地穿过防火墙) 。
AbstractPlainSocketImpl
-
doConnect
- socketConnect,在linux下由原生方法实现
-
其它的bind、create都是类似的,最后用原生方法实现的
-
AbstractPlainSocketImpl只是linux下网络编程的浅包装
-
这个类是很简单的,只是定义了socket相关的框架,都是由子类里面具体的方法实现
ServerSocket
-
有五种构造方法
-
(1.)backlog是连接数,在tcp协议三次握手时,当前用来保存已经建立连接的、以及正在建立连接的,backlog主要是对这两个队列进行控制,具体大小跟操作内核相关,有的是乘二,有的是直接相加。
(2).backlog的默认值只有5
-
setImpl()
(1).设置socket的实际实现对象
(2).
private void setImpl() { if (factory != null) { impl = factory.createSocketImpl(); checkOldImpl(); } else { // No need to do a checkOldImpl() here, we know it's an up to date // SocketImpl! impl = new SocksSocketImpl(); } if (impl != null) impl.setServerSocket(this); }
不过 ServerSocket 和 Socket 为何要使用 SocksSocketImpl ?这是很古怪的行为,具体原 因没有官方解释,目前比较合理的推测是: 这是从 JDK1.4 保持下来的传统,对代理服务器连接的实验性支持。在实际环境中,JRE可能已经通过系统属性-DsocksProxyHost 和-DsocksProxyPort 或 ProxySelector.setDefault()或通过 JRE 安装的默认 ProxySelect 从外部配置为使用 SOCKS 进行代理。但是 PlainSocketImpl 不会参考这些属性或类,所以这些外部配置将被忽略不起作用,而 SocksSocketImpl 则会进行检查。
扫描二维码关注公众号,回复: 12874634 查看本文章
-
PlainSocketImpl
- 在 Linux 下 JDK 的 PlainSocketImpl 非常简单,直接调用 JDK 中的 native 方法。并最终调用了操作系统的相关方法。
Linux 下的 IO 复用编程
select
-
int select (int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
- readfds是可读的、writefds是可写的、exceptfds是异常的fds
-
所以操作系统都提供了支持
-
select能监视的文件描述符是有限的,是1024,虽然可以修改,但是select机制在文件描述符很多的情况时效率会越来越低
poll
-
int poll (struct pollfd *fds, unsigned int nfds, int timeout);
-
和select基本一样
-
poll用一个结构体pollfd描述select中的3种文件描述符
-
虽然监视的最大描述符没有限制,但是是用链表实现的,所以性能相比select并没有高很多,只是监视文件描述符相比select要多很多
epoll
-
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);
-
是linux2.6版本提出来的,是前两者的增强版本
-
使用起来也比前两个更麻烦,有三个方法联合起来使用
-
epoll_create是创建一个epoll的句柄,注意epoll虽然经常用在网络读写,但是在linux角度,将epoll设计成一个文件系统,在net目录下找不到,而是在fs目录下
1.什么是句柄?
简单理解成JVM里面对象的引用,jvm的堆上面有很多对象,方法在线程栈上执行,通过引用可以找到相应的对象,而句柄也是类似的作用
-
epoll_ctl对指定的描述符执行某种相关的操作
1.相当于在select中监听什么事件,服务器关心连接事件,socketChannel关心读写事件,会用register进行一个相关的注册,epoll_ctl就是进行一个相关注册,但不止是注册
(1)epfd是上面epoll_create返回的句柄,op是当前要做什么事(增加、删除或修改),fd是监听的具体的文件描述符(对哪一个channel做监听),epoll_event是我们要监听什么事件
-
epoll_wait是获取fd所能获取到的io事件,events表示轮询出的事件集合,
1.epoll_wait的返回值类似于之前用java写的selector.select()的返回值,是差不多的,告诉我们selector有多少通道就绪返回
-
首先通过epoll_create创建相关的句柄,然后调用epoll_ctl在我们监视的fd上面进行相关事件的注册,相当于register,而epoll_wait则相当于select,epoll_create已经被jdk给隐藏并实现了。
-
epoll 高效原理和底层机制分析
- 从网卡接受数据
- 当计算机收到一个网络数据包,首先是网卡接受数据,通过硬件电路的传输写到内存中的某一个地址,中间牵扯到DMA传输、IO通道选择、中断等等,网卡最终会把数据写到内存,然后操作系统去读取它们
- CPU如何知道接受了数据?
- 使用中断机制
- 进程阻塞
- 创建进程后,需要分配一块内存
- 但是进程被阻塞后,不占用cpu资源
进程阻塞
-
当bio通讯时,某个进程A负责网络通讯,调用socket,绑定、接受连接、读数据read()
-
既然read()方法是阻塞式io,网络上没有数据过来,read()方法会阻塞当前线程,直到有数据过来,进程A才会继续往下走
-
操作系统是多任务系统,会出现进程切换,运行态是指线程获取到了cpu的使用权,正在执行的状态,当前线程处于等待状态,也就是阻塞状态,例如read()方法,当有数据来了,就会由阻塞状态转换成运行态
-
socket = new socket() bind(); accept(); read();
- 不仅是生成一个实例,还会创建一个由文件系统管理的文件,这个文件包括发送缓冲区、接受缓冲区、等待队列
- 执行read()方法,在之前的场景中,进程A被阻塞,就会把进程A从内核空间的工作队列中移动到socket所属的等待队列中,所以进程A也就不会分配CPU资源
内核接收网络数据全过程
-
当网络上游数据传递过来了,网卡接受数据,网卡写入数据,中断,通知CPU,CPU执行中断(硬中断和软中断)
-
软中断会把网卡缓冲区里面的数据交给linux内核的协议栈处理以后,再交给内存中的应用程序本身的socket缓冲区里面去
-
中断的过程除了说在socket接受缓冲区里面填入数据外,还有第二部,就是唤醒进程A,重新被挂到工作队列后面,操作系统又重新对进程A进行一个事件调度
-
操作系统怎么知道接受到的数据,在协议栈解包后应该交给那个socket?
- 端口号
- 网络数据包包含ip地址和端口号,内核就可以通过端口号找到对应的socket
select方法
-
对于服务器而言,一个应用程序同时会和很多客户端进行通讯,对于维护多个socket的情况,服务器应该怎么监视多个socket有没有数据需要处理?
-
// 同时监视多个socket int fds[] = 存放需要监听的socket while(1){ int n = select(...,fds,...); for(int i=0;i < fds.count; i++){ if(FD_ISSET(fds[i],..)){ // fds[i]的数据处理 } } }
-
1.弄一个socket列表
-
2.进程A是对应的服务器程序,它管理3个socket,调用select后,把进程A挂到每一个socket下面的等待列表中,如果有任意一个socket收到数据,唤醒进程A,并把进程A从所有socket的等待列表移除,现在需要把进程A重新放到操作系统的工作队列中,方便进程A对这些socket进行相关检查
-
3.把这里面的socket全部遍历一遍,找到接受到数据的socket,然后做处理
-
-
优点
- 简单有效
-
缺点
-
1.有多少个socket需要监视,就需要把进程A加到多少个socket里面的等待列表,而进程A被唤醒后,又需要把进程A从监视的socket的等待列表中移除,所以需要两次遍历
-
2.所谓的fds列表,需要监听的socket列表,每次都要从用户空间传递内核,所以也需要一定的开销
-
所以fds[]列表不能超过1024,超过性能急速下降,但是操作简单,所以在所有系统上都适用
-
epoll的设计思路
-
功能分离,select的一大缺陷把维护等待列表和进程阻塞合二为一了,每次调用select操作,这两步都要走,epoll就把这两个操作分开了
-
int epfd = epoll_create(...); epoll_ctl(epfd,...);//将所有需要监听的socket添加到epfd中 while(1){ int n = epoll_wait(...); for(接受到数据的socket){ // 处理 } }
-
1.首先调用epoll_create拿到epfd文件句柄,具体的,会在系统内核产生一个eventpoll这么一个对象,而epfd指向eventpoll这个对象
-
2.然后调用epoll_ctl()方法,把所有需要监听的socket添加到epfd中,假如现在有3个socket需要epoll监听,就把eventpoll放到这三个socket的等待队列上
-
3.调用epoll_wait(),把进程A加到eventpoll的等待队列中,
-
4.只处理接受到数据的socket,此处epoll内部维护一个就绪列表,避免不必要的遍历,具体的,如果某一个socket收到相关的数据,它不像select操作进程,而是在eventpoll里面创建一个rdlist的数据结构,而rdlist就会引用收到数据的socket,同时唤醒进程A,进入内核工作队列,进程A只需要读取rdlist,就知道哪些socket收到数据了
-
-
epoll里面,socket列表只需要传递一次,当进行数据遍历时,只需要遍历有数据变化的socket
- epoll并不是在所有情况都比select效率高,在连接数少,并且socket都比较活跃,此时select效率可能更好
- select只有一次系统调用,而epoll需要各种函数的回调,假如要维护十万个连接,select每次都要轮询十万个连接,找到其中二百个活跃的,效率会很低
-
rdllist用双向链表实现,eventpoll用红黑树实现
eventpoll
-
struct eventpoll { /* * This mutex is used to ensure that files are not removed * while epoll is using them. This is held during the event * collection loop, the file cleanup path, the epoll file exit * code and the ctl operations. */ struct mutex mtx; /* Wait queue used by sys_epoll_wait() */ wait_queue_head_t wq; // 红黑树 /* Wait queue used by file->poll() */ wait_queue_head_t poll_wait; // 双向链表 /* List of ready file descriptors */ struct list_head rdllist; /* Lock which protects rdllist and ovflist */ rwlock_t lock; /* RB tree root used to store monitored fd structs */ struct rb_root_cached rbr; /* * This is a single linked list that chains all the "struct epitem" that * happened while transferring ready events to userspace w/out * holding ->lock. */ struct epitem *ovflist; /* wakeup_source used when ep_scan_ready_list is running */ struct wakeup_source *ws; /* The user that created the eventpoll descriptor */ struct user_struct *user; struct file *file; /* used to optimize loop detection check */ u64 gen; struct hlist_head refs; #ifdef CONFIG_NET_RX_BUSY_POLL /* used to track busy poll napi_id */ unsigned int napi_id; #endif #ifdef CONFIG_DEBUG_LOCK_ALLOC /* tracks wakeup nests for lockdep validation */ u8 nests; #endif };
epitem—socket的包装节点
-
struct epitem { union { /* RB tree node links this structure to the eventpoll RB tree */ struct rb_node rbn; /* Used to free the struct epitem */ struct rcu_head rcu; }; /* List header used to link this structure to the eventpoll ready list */ struct list_head rdllink; /* * Works together "struct eventpoll"->ovflist in keeping the * single linked chain of items. */ struct epitem *next; /* The file descriptor information this item refers to */ struct epoll_filefd ffd; /* List containing poll wait queues */ struct eppoll_entry *pwqlist; /* The "container" of this item */ struct eventpoll *ep; /* List header used to link this item to the "struct file" items list */ struct hlist_node fllink; /* wakeup_source used when EPOLLWAKEUP is set */ struct wakeup_source __rcu *ws; /* The structure that describe the interested events and the source fd */ struct epoll_event event; };