专栏首页码农知识点什么是socket?

什么是socket?

Socket编程进行的是端到端的通信,基于网络层和传输层的实现。在网络层,Socket 函数需要指定到底是 IPv4 还是IPv6。传输层需要指定是tcp还是udp。 基于TCP协议的socket调用过程:

tcp socket调用过程.jpg

其中用于监听连接的socket和真正用来传数据的socket是不同的socket。在tcp中,socket是一个文件流,在内核中的表现形式是一个文件,含有文件描述符,以及用来读写数据的读缓冲队列和写缓冲队列。 数据结构如下:

socket数据结构.jpg

基于UDP协议的socket调用过程:

udp socket调用过程.jpg

UDP不需要维护连接状态,每次通信都调用sendto 和 recvfrom,都可以传入 IP和端口。

服务器如何建立更多连接?

存在的问题:一个tcp连接服务器需要的信息包括对端 IP和 对端端口,理论最大 TCP 连接数 = 客户端 IP 数×客户端端口数。对于IPv4,最大 TCP 连接数 = 客户端 IP 数(232)×客户端端口数(216) =2^48个。但是受文件描述符(socket就是一个文件)和内存限制,单机操作系统不可能支撑这么大的连接数。

解决办法:

1.多进程方式

在Linux下,创建子进程使用fork函数,会复制文件描述符的列表,内存空间和一条记录当前执行到哪个程序的进程。通过返回值来区分父进程和子进程,子进程返回0,父进程返回的是子进程的pid。进程复制示意图如下:

进程复制.jpg

2.多线程方式

相比进程,线程较为轻量化。在 Linux 下,通过 pthread_create 创建一个线程,也就是调用哦do_fork。虽然新的线程会在task列表上多处一项,但是很多资源是和父线程共用的,只是多了一个指向子线程的引用而已。线程复制示意图如下:

线程复制.jpg

3.IO多路复用

线程的资源是有限的,操作系统仍无法支撑很多线程解决C10K问题,io多路复用就是通过一个线程维护多个socket。

示意图如下:

io多路复用.png

select

如果某个线程管理着所有的socket,它们的文件描述符都放在fd_set中。select通过不断轮询fd_set集合来监听socket的变化。

缺点:

(1)被监控的fds集合限制为1024,1024太小了,我们希望能够有个比较大的可监控fds集合

(2)fds集合需要从用户空间拷贝到内核空间的问题,我们希望不需要拷贝

(3)当被监控的fds中某些有数据可读的时候,我们希望通知更加精细一点,就是我们希望能够从通知中得到有可读事件的fds列表,而不是需要遍历整个fds来收集。

poll:相比select,poll改变了fds集合的描述方式,使用了pollfd结构而不是select的fd_set结构,使得poll支持的fds集合限制远大于select的1024。只是解决了第一个问题。

epoll:采用了Linux的socket 事件wakeup callback机制,当某个文件描述符发送变化的时候,就会主动通知。示意图如下:

epoll机制.jpg

epoll提供了三个函数,epoll_create,epoll_ctl和epoll_wait,epoll_create是创建一个epoll句柄,在这个文件项中保存着一个红黑树,包含了所有监听的socket。当epoll_ctl添加一个socket时,会向红黑树中加入一个节点,它的结构中保存着epoll对象,同时该节点也会指向一个文件结构,关联着被通知的socket fds。epoll_wait则是等待事件的产生。 (1)fds拷贝问题: epoll引入了epoll_ctl系统调用,将高频调用的epoll_wait和低频的epoll_ctl隔离开。同时,epoll_ctl通过(EPOLL_CTL_ADD、EPOLL_CTL_MOD、EPOLL_CTL_DEL)三个操作来分散对需要监控的fds集合的修改,做到了有变化才变更,将select或poll高频、大块内存拷贝(集中处理)变成epoll_ctl的低频、小块内存的拷贝(分散处理),避免了大量的内存拷贝。同时,对于高频epoll_wait的可读就绪的fd集合返回的拷贝问题,epoll通过内核与用户空间mmap(内存映射)同一块内存来解决。mmap将用户空间的一块地址和内核空间的一块地址同时映射到相同的一块物理内存地址(不管是用户空间还是内核空间都是虚拟地址,最终要通过地址映射映射到物理地址),使得这块物理内存对内核和对用户均可见,减少用户态和内核态之间的数据交换。 (2)按需遍历fds问题: 通过上面的socket的睡眠队列唤醒逻辑我们知道,socket唤醒睡眠在其睡眠队列的wait_entry(process)的时候会调用wait_entry的回调函数callback,并且,我们可以在callback中做任何事情。为了做到只遍历就绪的fd,我们需要有个地方来组织那些已经就绪的fd。为此,epoll引入了一个中间层,一个双向链表(ready_list),一个单独的睡眠队列(single_epoll_wait_list),并且,与select或poll不同的是,epoll的process不需要同时插入到多路复用的socket集合的所有睡眠队列中,相反process只是插入到中间层的epoll的单独睡眠队列中,process睡眠在epoll的单独队列上,等待事件的发生。同时,引入一个中间的wait_entry_sk,它与某个socket sk密切相关,wait_entry_sk睡眠在sk的睡眠队列上,其callback函数逻辑是将当前sk排入到epoll的ready_list中,并唤醒epoll的single_epoll_wait_list。而single_epoll_wait_list上睡眠的process的回调函数就明朗了:遍历ready_list上的所有sk,挨个调用sk的poll函数收集事件,然后唤醒process从epoll_wait返回。 (3)这种通知方式使得监听的 Socket 数据增加的时候,效率不会大幅下降,能够同时监听的 Socket 的数目也非常的多了。上限就为系统定义的能够打开的最大文件个数。

参考资料: https://time.geekbang.org/column/article/9293 https://cloud.tencent.com/developer/article/1005481 http://www.cnblogs.com/Anker/p/3265058.html

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • JUC学习笔记(一)—锁工具类

    LockSupport类:用于阻塞线程,基于线程的阻塞。而wait,notify是基于对象的,用于synchronized同步块中。 使用和原理: 浅谈Ja...

    Monica2333
  • JUC学习笔记(四)—线程池

    线程池 【死磕Java并发】—–J.U.C之线程池:ThreadPoolExecutor

    Monica2333
  • Java线程中断

    首先,一个线程不应该由其他线程来强制中断或停止,而是应该由线程自己自行停止。所以,Thread.stop, Thread.suspend, Thread.res...

    Monica2333
  • python线程回顾

    创建对象 对象 = threading.Thread(target=入口, args=(), kwargs={})

    小闫同学啊
  • 关于Java 线程的运行状态

    首先需要说明的是,所指状态为JVM线程状态,而非操作系统线程状态。同一时间,一个线程只会存在于一种状态。

    WindWant
  • 如何才能够系统地学习Java并发技术?

    这里不仅仅是指使用简单的多线程编程,或者使用juc的某个类。当然这些都是并发编程的基本知识,除了使用这些工具以外,Java并发编程中涉及到的技术原理十分丰富。为...

    Java技术江湖
  • Java并发之高级自旋锁CLH锁和MCS锁

    自旋锁(spin lock)是一个典型的对临界资源的互斥手段,自旋锁是基于CAS原语的,所以它是轻量级的同步操作,它的名称来源于它的特性。自旋锁是指当一个线程尝...

    我是攻城师
  • 如何才能够系统地学习Java并发技术?

    这里不仅仅是指使用简单的多线程编程,或者使用juc的某个类。当然这些都是并发编程的基本知识,除了使用这些工具以外,Java并发编程中涉及到的技术原理十分丰富。为...

    黄小斜
  • java 线程阻塞的问题

    中断线程最好的,最受推荐的方式是,使用共享变量(shared variable)发出信号,告诉线程必须停止正在运行的任务。线程必须周期性的核查这一变量(尤其在冗...

    田春峰-JCJC错别字检测
  • 如何才能够系统地学习Java并发技术?

    这里不仅仅是指使用简单的多线程编程,或者使用juc的某个类。当然这些都是并发编程的基本知识,除了使用这些工具以外,Java并发编程中涉及到的技术原理十分丰富。为...

    黄泽杰

扫码关注云+社区

领取腾讯云代金券