网络编程14

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 则会进行检查。

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;
    };
    
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值