关于socket的一些初步研究

标签: socket 研究 | 发表时间:2011-09-28 01:17 | 作者:keakon iDesperadO
出处:http://www.keakon.net/
这些天在研究Tornado的源码,说实话它的代码过于艰深了,需要绕很多弯才能弄清。
我想其中的问题主要是我不太懂socket,于是就花了些时间学习socket,算是有了些收获,顺便记录在此。

首先是socket的概念。
实际上UNIX的设计者很喜欢用类似的方式来处理一类任务,其中数据传输就都被抽象成文件,包括磁盘文件、管道、FIFO和终端等。而socket则是用于连接不同机器或进程的一个文件描述符,通过它可以实现机器或进程间的通信。

其次是socket的流程。实际上它分为有连接和无连接这2种,不过HTTP服务使用前者,所以这里只说前者。
先是服务器端调用socket()创建一个socket;再将这个socket传给bind(),绑定到一个端口;接着调用listen()来限制等待连接的队列容量;最后在一个循环中不断调用accept()来接收请求。
客户端则也通过socket()创建socket;再调用connect()连接到服务器端bind的那个端口。这时候如果等待连接的客户端超过listen()的设置,就会被拒绝访问;否则服务器端会accept()这个请求,并建立起连接。
而accept()会返回新的socket,服务器和客户端就可以通过这个socket,调用send()/recv()或write()/read()进行通信。
最后在需要结束通信时,调用shutdown()或close()来关闭这个socket即可。
总结一下的话,就是建立、监听、连接、接受、收发和断开的过程。

再介绍一下Tornado,它本身是个无阻塞的socket服务器。
说到阻塞这个概念,CPU的速度是很快的,寄存器、CPU缓存和内存还能跟上这个速率,存取时间都在纳秒级左右;但如果换成硬盘、网络,访问时间在毫秒级以上,这就造成了CPU长时间的空闲等待,于是就被称为阻塞了。
传统的解决方式是采用多线程模型,让某些线程去做这些事,当阻塞发生时,其他线程还能继续执行。这种方式需要线程同步,实现比较复杂,而且内存和线程切换的开销都很大。
如果在这些I/O访问时,CPU在发出指令后就立刻返回,继续处理其他事情;而设备则自行去完成连接、传输等任务,并在结束后让CPU完成善后工作;这样就既不会阻塞CPU,也没有多线程的复杂性和开销了。
在C语言中可以通过fcntl()和ioctl()来将socket设为无阻塞,Python中则很直观地调用setblocking(0)即可。

当socket变成无阻塞后,服务器就需要能异步处理这些I/O事件了。
也就是说,在调用accept()、send()和recv()的时候,函数会立刻返回;而在将来的某个时刻,CPU还需要去维护接受的socket和处理读取的数据等。
最简单的方式就是在死循环中不断遍历所有的socket,检查它们是否已经就绪;但是这样的效率显然不高,因为I/O是很耗时的操作,因此有很大概率是尚未就绪的。
与此类似的还有select()和poll(),它们会返回一类socket的集合。虽然和上一种方式的效率差不多,但因为可以设置超时时间(例如每秒1次),也就不会出现忙等时CPU占用率仍然100%的情况。
更先进的方式当然就是使用事件。Linux中提供了epoll,BSD(含Mac OS X和iOS)中提供了kqueue。我对kqueue还不熟,但epoll的行为是只返回有事件发生的socket,这样就能保证CPU不会浪费时间去处理未就绪的socket了。
而epoll的事件还存在2种触发方式:Level-Triggered和Edge-Triggered。LT指的是通过状态来表示事件发生了,例如读了缓冲区,那么这个socket就会被epoll_wait()返回,它的事件包含EPOLLIN。ET则是通过状态的变化来表示事件的发生,例如读取缓冲区时,如果没读完,那么状态一直是可读的;只有把缓冲区中的数据全部读完,状态变为不可读(EAGAIN)以后,再等下次缓冲区状态变为可读时,才会引起EPOLLIN事件,并被epoll_wait()返回。

事实上,Tornado就是依靠这种无阻塞的机制来与客户端(浏览器)连接和通信,所以它可以用单线程支撑大量并发。
但这只能保证Tornado与客户端之间的通信是无阻塞的,一个WEB服务器还不可避免地需要与磁盘文件、数据库等打交道,只有当这些I/O都是无阻塞时,整个服务才是真正无阻塞的。
然而要做到后者比较复杂(有的socket必须阻塞),所以有时不得不用多线程模型来实现。而且因为几乎所有请求都要访问数据库,当并发的请求数不能填满CPU的负荷时,总会遇到需要等待I/O的时候,此时的无阻塞也就变得无意义了。
因此Tornado推荐采用多进程的方式,当一个进程被阻塞时,其他进程继续处理其他请求。这样虽然单个请求会花更长的时间,但总体的效率还能保持在一个很高的水平。

然后测试下无阻塞+epoll方式的效率。
这次我直接在网上找到了一篇《python下epoll的使用与性能测试》,借用了它的代码。
其中稍作了一些改进:输出的header中增加了“Connection: Close”,epoll_wait()和poll()设置了1秒的超时时间,以及将send()前的sprintf()移出循环。
在我的虚拟机上分别跑了一下,1000并发时,C和Python的版本分别约为2300+ QPS和2100+ QPS。
接着试了下《实现了一个比nginx速度更快的HTTP服务器》这篇文章中的代码,发现居然有2900+ QPS,而且还是用文件传递(sendfile)方式。
比较了半天后,clowwindy说可能是因为他对accept出来的socket调用了setsockopt ( infd, SOL_TCP, TCP_CORK, &on, sizeof ( on ) )。这个TCP_CORK和TCP_NODELAY是相对的,前者表示将小的数据包合并在一起发送,后者则是不等待更多包就直接发送。因为前者可以少传递一些数据包,所以在高并发的情况下,这个优势也就被放大了。
于是我马上加上了这行代码,速度就增加到3000+ QPS了。至于Python,直接使用con.setsockopt(socket.IPPROTO_TCP, socket.TCP_CORK, 1)即可(Mac OS X的Python下没有socket.TCP_CORK,应该可以直接用常量3代替)。效果也很明显,增大到2700+ QPS了,约为C的90%。

最后还是那句话,在性能要求并非苛刻的时候,采用什么语言并没多大影响。
一台好的服务器,每秒跑1万hello world应该没问题,每个请求平均下来只需要0.1 ms的处理时间。而这和数据库等I/O请求相比根本不是一个数量级的,因此性能瓶颈其实并不在语言本身。
但是在代码量和可读性上,Python却节约了程序员大量的编码和维护时间。而在当今这种人比机器贵的年代,这就意味着巨大的价值。

相关 [socket 研究] 推荐:

关于socket的一些初步研究

- iDesperadO - keakon的涂鸦馆
这些天在研究Tornado的源码,说实话它的代码过于艰深了,需要绕很多弯才能弄清. 我想其中的问题主要是我不太懂socket,于是就花了些时间学习socket,算是有了些收获,顺便记录在此. 实际上UNIX的设计者很喜欢用类似的方式来处理一类任务,其中数据传输就都被抽象成文件,包括磁盘文件、管道、FIFO和终端等.

有关socket read

- - 五四陈科学院
以下内容由 [五四陈科学院]提供. 实际开发中,网络程序最可能遇到的就是数据没收到、收到错误数据这样诡异的问题.. 很多时候,都是由于对socket read的细节理解的不一致,导致了程序前后的矛盾. 下面详细阐述整个read的过程. read函数是负责从fd中读取内容.. 当读成功时, read返回实际所读的字节数.

Socket的速率控制

- - CSDN博客互联网推荐文章
做一个以精确速率向外输出数据的数据源,要完成这个目标,最基础的是:. 1、找到一种精确的计时器,在精确的时间范围内控制数据源以指定的速度向外发送数据. 2、通过对套接字选项和线程优先级的设置减少网络因素对发送速度造成的影响,从而提高发送精度,保证数据的实际发送量尽可能的达到指定的理论发送量.      针对第一个要求,通过寻找到一种时间精度达到微秒级的精确计数器来保证,在硬件支持的情况下可以通过WindowsAPI获取时钟频率以及震荡次数,通过在事件两端分别调用函数得到震荡次数的差值并结合时钟频率可以计算出精确的时间间隔,通过指定的传输速度和精确的延时可以计算出需要发送的数据量.

ZeroMQ(java)中监控Socket

- - CSDN博客架构设计推荐文章
基本上ZeroMQ(java)中基本的代码都算是过了一遍了吧,不过觉得它在日志这一块貌似基本没有做什么工作,也就是我们通过日志来知道ZeroMQ都发生了什么事情. 而且由于ZeroMQ中将连接的建立和重连接都进行了隔离,用户不需要做什么事情来维护连接,当然这样做的好处是使程序员的编码工作变少了,但是当然也有不好的地方,那就是用户失去了对ZeroMQ整个运行阶段的控制.

java socket参数详解:BackLog

- - 开源软件 - ITeye博客
 java socket参数详解:BackLog. 输入连接指示(对连接的请求)的最大队列长度被设置为 backlog 参数. 如果队列满时收到连接指示,则拒绝该连接. backlog参数必须是大于 0 的正值. 如果传递的值等于或小于 0,则假定为默认值. 经过测试这个队列是按照 FIFO(先进先出)的原则.

HTML5 Web socket和socket.io - wishyouhappy

- - 博客园_首页
   HTML5的新特性,用于双向推送消息(例如网页聊天,手机推送消息等). client利用regular http请求webpage. 请求的webpage 执行javascript脚本,open a connection to server.. 有新的信息时服务器和客户端可以相互发送信息(Real-time traffic from the server to the client .

tcp/ip ,http,socket的关系

- - 行业应用 - ITeye博客
  物理层、数据链路层、网络层、传输层、会话层、表示层和应用层.   通过初步的了解,我知道IP协议对应于网络层,TCP协议对应于传输层,而HTTP协议对应于应用层,.   三者从本质上来说没有可比性,.   socket则是对TCP/IP协议的封装和应用(程序员层面上).   也可以说,TPC/IP协议是传输层协议,主要解决数据如何在网络中传输,.

socket连接和http连接的区别

- - 互联网 - ITeye博客
相信不少初学手机联网开发的朋友都想知道Http与Socket连接究竟有什么区别,希望通过自己的浅显理解能对初学者有所帮助. HTTP协议:简单对象访问协议,对应于应用层  ,HTTP协议是基于TCP连接的. tcp协议:    对应于传输层. ip协议:     对应于网络层 . TCP/IP是传输层协议,主要解决数据如何在网络中传输;而HTTP是应用层协议,主要解决如何包装数据.

Java Socket常见异常处理

- - BlogJava-qileilove
java网络编程Socket通信中,通常会遇到以下异常情况:.   第1个异常是 java.net.BindException:Address already in use: JVM_Bind.   该异常发生在服务器端进行new ServerSocket(port)(port是一个0,65536的整型值)操作时.