关于套接字的疑问

2016-03-25 09:55:54 +08:00
 ubear1991

一直有个疑问,关于 IO 多路复用的。

假设应用程序进程监听在 localhost:80 。这实际上是一个套接字吧。 那么,有需要用 IO 多路复用么?监听在多个套接字上才有必要用 IO 多路复用吧。

不知道哪出了问题,没想明白。请有明白的解释一下。 谢谢。

3145 次点击
所在节点    问与答
40 条回复
DuckJK
2016-03-25 17:49:52 +08:00
@iMouseWu 你说的第二个是这样的, I/O 多路复用搭配非阻塞 IO ,也就是说 select 一个 socket ,创建一个线程处理这个 socket ,这时候可以设置 read 是非阻塞(我觉得关键点在这里,这种叫做非阻塞 I/O )。
阻塞 I/O :每次只能调用一次 read 或者 accept ,因为多路复用只会告诉 fd 对应的 socket 可读,但不会告诉有多少数据。所以在处理 handle_read/handle_accept 只能 read/accept 一次,无法确定下一次是否阻塞。所以只能再次循环。

非阻塞 I/O 是循环的 read 或 accept ,直到读完所有的数据(抛出 EWOULDBLOCK 异常)

上面两个都是我从那个知呼里面找到的。
current
2016-03-25 19:34:56 +08:00
IO multiplexing 在某些情况下必须搭配 Non-blocking socket 使用,在另外一些情况下可以使用 Blocking Sockets ,但不会带来任何好处,也不会降低编程的复杂度
current
2016-03-25 19:38:34 +08:00
@DuckJK 你举出的那种情况是客观存在的,网卡收包以后会通过驱动通知相关的 socket ,但是如果后续发现是错包的话会将这个包丢掉,然而 io multiplexer 已经发出了 fd 可读事件,这时候使用阻塞读会造成阻塞,但是这应该是几率很低的一种情况,我个人觉得不具有说服力。

我的理解中,必须使用 non-blocking socket 的情况只有 epoll 的 ET mode
current
2016-03-25 19:43:27 +08:00
@iMouseWu IO multiplexing 和后续的并发处理是完全不相关的两件事, select 出一个 fd 以后,是当场处理还是丢到线程池里面处理,还是用更加猥琐的回调+协程方式去处理都是有可能的,这属于并发处理的范畴

IO Multiplexing 的意义在于提供了一种机制让你可以同时监听大量 socket
对于 blocking sockets, 显然你直接去尝试 read 是不可行的,因为不知道会阻塞在哪儿
对于 non-blocking sockets ,不用 io multiplexing 的话,就只能通过 busy polling 去探查 socket 是否可读,这也是不太能接受的做法
current
2016-03-25 19:49:03 +08:00
@DuckJK V2 的回复不能直达楼层好蛋疼。。。

IO multiplexing 在使用的时候必须配合应用层的 buffer ,这是 TCP 的本质决定的, TCP 是一个字节流协议,没有包的概念,不能保证你每次 read 读到的都是一个完整的『应用层的包』,因此通常人们使用类似 readn, readline, readUntil 这类函数来处理 socket 读

在 IO multiplexing 的场合下,针对 blocking sockets 使用这类函数显然是不科学的,如果你希望读到一个 1024bytes 的应用层包,而 socket 上只有 512bytes 数据,那么整个 IO 线程就阻塞了,直到读满了 1024bytes ,科学的做法是 socket 里面有多少数据就读多少,读出多少就写进应用层 buffer ,应用层再从 buffer 里面读应用层的包,上层的应用逻辑通过 buffer 和 TCP 打交道,这和 non-blocking socket 的处理模式是一样的,也就是我在上面说的『在其他场合可以使用 non-blocking sockets ,但不会带来任何好处』
current
2016-03-25 19:50:17 +08:00
再插一句, epoll 在 ET 模式下的饥饿问题是类似原因
bicoff9527
2016-03-25 20:27:28 +08:00
套接字是五元组,有一个不同就是别的套接字了,其它的感觉楼上都说得很清楚了,但是我觉得 LZ 实际编程写一下会有更透彻的了解,这里面坑很深
mhycy
2016-03-25 21:22:45 +08:00
@current
针对 23 楼的回复,网卡收包以后是先通知内核,内核处理完了再通知应用层。
应用层收到的 TCP 数据不一定可靠,但绝对是合法数据。
(不可靠的原因是校验码碰撞)
iMouseWu
2016-03-25 22:59:59 +08:00
@current 感谢。
这里有个疑问.
"IO Multiplexing 的意义在于提供了一种机制让你可以同时监听大量 socket "
请问下这里监听的 socket 指的是多个服务端 socket 嘛?
如果是服务端,那么可以理解,但是如果是客户端的话,我觉得普通的 accept 也可以达到相同的效果,这个是我一直不理解的地方
current
2016-03-25 23:53:49 +08:00
@mhycy 谢谢,这个问题我确实不太了解
current
2016-03-25 23:54:21 +08:00
@iMouseWu 客户端出于什么情况需要 accept 呢? :)
DuckJK
2016-03-26 00:16:37 +08:00
@current 谢谢,另外问一个问题:如果我使用 Python 的 requests 从网上下载压缩文件,然后解压缩:
1. 下载的文件太大,直接采用 reponse.raw.read ,把 socket 当作 file 来操作,然后 zlib 解压缩这样会产生什么样的后果,因为一次 read 的话是读取不完的。
2.直接解压缩 reponse.content 这种是不是和上一个不同?

麻烦了,谢谢。想了好几天。
mhycy
2016-03-26 00:28:14 +08:00
@DuckJK
zlib 如果支持流解压,那么你只是把数据从一个缓冲区搬到另一个缓冲区而已
如果 zlib 不支持流解压,那么你提供给 zlib 的必须是一个合法的压缩数据

response.content 的动作应该会在 HTTP 头有提供长度的情况下阻塞缓存完整数据
在未提供长度的情况下阻塞到链接断开。
如果是解包时候的处理,同上一段
DuckJK
2016-03-26 00:36:32 +08:00
@mhycy 请问网卡接收数据,这个数据的流程是怎么样的,先到网卡,然后是到内核处理还是怎么样子?如果 zlib 支持流解压缩,那就是跟压缩数据大小没关系,只要是合法压缩数据,都可以解压缩,只是从 readbuffer 到 zlib 的 buffer ,是不是可以这么理解。

reponse.content 在未提供长度的情况下阻塞到链接断开,这个链接断开是跟 read 的长度有关系么?还是到什么样的情况下链接断开?谢谢啦
iMouseWu
2016-03-26 10:45:32 +08:00
@current 一般我们在 socket 编码的时候,是先创建一个 serviceSocket 然后 accept,如果有客户端连接的话就产生一个 SocketChannel
那我可不可以理解为,IO 多路复用的优势是在创建两个 serviceSocket 这种场景么?
current
2016-03-26 11:56:20 +08:00
@iMouseWu
我理解错你的意思了,我以为你说的是客户端进行 accept

server 监听一个 listen socket ,每次 accept 会产生一个 client socket
使用 IO multiplexing 的时候,将 listen socket 和每次 accept 产生的 client socke 都添加到监听集合中
除此之外, io multiplexing 也可以监听 timerfd , eventfd 等,用于监听计时器事件和计数器事件
current
2016-03-26 12:00:27 +08:00
@iMouseWu
while 1
{
ready_set = select(fd_set)
for item in ready_set
if item is listen_sock
client_sock = accept(listen_sock)
fd_set.append(client_sock)
else
buf = read(item)
}

大致这样子
mhycy
2016-03-26 12:19:53 +08:00
@DuckJK
网卡收到的数据通过中断的方式通知内核处理,数据的传递一般是 DMA
(不可能 CPU 直接读不然负载太高了)
( DMA 直接拷贝数据到系统内存,当然也有可能是网卡上的缓冲区,这个说不准)
(通知或许除了中断还有别的处理方式,这个不了解,因为频繁的中断会非常耗费 CPU 资源)
所有的 Socket 都是内核分配管理的,网卡在通知内核收到包以后,内核就去获取这一个包然后解析
(可以理解成中间有个缓冲区,内核从缓冲区取数据)
内核处理包的过程会验证包是否合法,然后路由到合适的地方
(这个路由包含多种意思,一个是数据包的路由另一个是转发到合适的处理程序[的缓冲区])
如果是 TCP ,底层的会进行拥塞控制处理(组包、验证、依据情况发回 ACK 信息各种各样)
(驱动能在拥塞控制之前接管所有 TCP 处理,这也是锐速的原理)
组成合法的流数据以后再发往给上层应用的缓冲区,通知上层应用


“如果 zlib 支持流解压缩,那就是跟压缩数据大小没关系,只要是合法压缩数据,都可以解压缩,只是从 readbuffer 到 zlib 的 buffer ,是不是可以这么理解。 ”
就是这么回事,我并不知道 zlib 是否支持流解压,也没查。。这个依据实际情况判断
(推广到别的编码方式也一样)

“ reponse.content 在未提供长度的情况下阻塞到链接断开,这个链接断开是跟 read 的长度有关系么?还是到什么样的情况下链接断开?谢谢啦”
这个原因是 HTTP 头未提供数据长度的情况下客户端并不知道数据有多大
只能阻塞到 TCP 链路主动断开才能获知已经收完数据
(如果有防火墙的地方,这个数据很可能是损坏的,因为防火墙有可能提前掐断这个链路)
iMouseWu
2016-03-26 13:49:00 +08:00
@current 看了你的解释,大致明白了一点,说下我的理解
我的理解是,按照这种方式对于 clinet 和 service 需要进行多次通信的,I/O 多路复用确实有很大的优势,但是如果是基于 http 这种协议,只需要一次通信的,感觉优势就没有那么的大了。
PS:redis 是不是也是只需要进行一次通信的,但是 redis 也用了 I/O 多路复用,很是不解
xueqt
2016-08-24 14:25:51 +08:00
借楼插个问题
我看不少人说 “ accept 产生的新的 fd ,端口可以是 listen 的端口,也可以不是,具体由客户端决定”
但是,我看的新的 fd 用的就是 listen 的那个端口,这个可以由 client 指定?如果可以怎么指定

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/266221

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX