Socket

         被叫去讲下Socket。本来对象只是社团里大一的人,只需按着《Python核心编程》上的内容念出来就好。然后自己又本着作死的心态自己看找来的资料,才发觉这又是个深坑。真的什么东西和操作系统挂上钩了,就什么都是坑了。对于Socket的深入内容,我自己不太了解。看这文的时候会发现,很多地方都没详细sample,以及有些地方不确定的我也没去测试。实在没什么时间了,之前还被某老师骂了(”我给你钱不是叫你来干这个的”,略呵呵,一个月三百不到,要不是能有正当理由向别人请教问题我才不会呆呢)。算了,这也算是代价吧。

         接着是混乱的讲解时间。许多内容我都只是一知半解,又想整理得有层次些,避免日后看起来十分蛋疼,就简化了不少。首先是Socket的一些基本说明,这部分参考了http://www.cnblogs.com/dolphinX/p/3460545.html  

         先说下数据传递流程吧。以下是我个人的理解。开始呢,我们会先把进程和端口以及内核的一些服务绑定起来。在进程中输入你想发送的数据后,数据会被复制到Socket的发送缓冲区。接着,内核会按指定格式,将数据切片送进新构造的一个个数据包中。至于数据包是怎么弄的,这完全是内核自己玩的事情,比如linux会申请一个skb结构体,然后……我就没看下去,自己也不清楚。数据包被扔在队列上后,等获取到NIC的特定资源时即发送。发送出去后,即根据IP寻址,找到目标主机。

         但数据包发送也会有一定规矩。比如说,你和目标主机之间用的是TCP或UDP,那数据包的构造以及发送都会不同。TCP的话,会在发送正式的数据之前,先和目标主机建议TCP连接,以确保能通过及时、有效的沟通,使数据包能完整的、按序的发送到目标主机上。该通讯方式称为虚电路。它先是通过三次握手来建立TCP连接的 ——

 

之后会给同一数据分割后构造成的数据包标上序号,按序发送。对方在确认接收到某一包后会回复ACK包给源主机。若在一定时间内没有收到ACK,则认为对方由于网络原因没有收到,会进行重发。至于数据包的大小,这在三次握手建立连接的时候就确定下来了。而对方在收到全部的数据包后,注意是全部,就会将数据包组装成原来要发送的完整数据。至于每个协议层上对数据包的修改处理呢就不另外说了。TCP协议栈之类的也算了,自己都不了解太深。

至于UDP,就直接发送数据,不需要先建立连接。所以开销较小(维持TCP其实开销挺大的),也没那么可靠。该通讯方式称无连接。但不管是TCP还是UDP,都需要依赖IP来寻找目标主机。毕竟每个层的任务都不同。中间路由传输那些懂的太零散的,就不填坑整理了。扫描相关的,以后再讲吧。

         当目标主机收到数据包后,会根据端口,将数据放入与之绑定的进程里。这里,是放入Socket的接收缓冲区中,等待进程读取。好的,数据包的传输流程大概就是这样。

 

         然而,我们也应该了解一下,在网络中我们如何标识一个主机上的特定进程,和一次通信。对于前者,我们使用一个三元组(协议,网络地址,端口号)。这也叫半相关。对于后者,我们使用一个五元组(协议,本地网络地址,本地端口号,远程网络地址,远程端口号)。端口,其实是系统虚拟出来用于标识进程/服务的。NIC接收到数据包,它认出是自己的就读入,但在这之后内核怎么区分这个数据该给谁,就得根据端口,把它传给与端口绑定的进程/服务了。

         而Socket在通信过程中的作用呢?

 

         它的实质是将TCP/IP层复杂的操作(包括在内核中各种复杂的处理方法)抽象成几个简单的接口。

 

         最后,关于背景介绍,感觉还要讲下Linux里对Socket的管理。其实这些介绍都可以在上面链接里找到相应内容,我只是根据个人喜好补充一点。

socket起源于UNIX,在Unix一切皆文件哲学的思想下,socket是一种"打开—读/写—关闭"模式的实现,服务器和客户端各自维护一个"文件",在建立连接打开后,可以向自己文件写入内容供对方读取或者读取对方内容,通讯结束时关闭文件。

而内核通过文件描述符来标识这些临时文件和获知它们的状态。

 

         接下来就介绍一下Socket的主要函数。大致流程如图。至于示例代码,则取《Python核心编程》中的TCP服务端和客户端的建立

 

1、              socket( socket_family, socket_type, protocol = 0)

socket_family用于指定Socket家族,常见的有AF_UNIX —— 用于一机器上的IPC; AF_INET —— 平时IPV4使用; AF_INET6 —— 平时IPV6用

socket_type用于指定Socket类型,玩的话常用的就SOCK_STREAM (流套接字,即虚电路)和SOCK_DGRAM (即无连接)

protocol无视就好。那些我也不懂,也没怎么查过

例: tcpSock = socket.socket( socket.AF_INET, socket.SOCK_STREAM) #python有socket模块

2、              bind( ( “”, Port ) )

在socket()创建套接字后,它仅存在于地址族中,并未建立本地捆绑。bind()则将其与指定端口捆绑

例: ADDR = (HOST, PORT) # HOST = ‘’  PORT = 21567

3、              listen( backlog )

listen让流套接字变为监听状态,使得相应进程可接受其他进程的请求,从而成为服务器进程(即将其变为被动连接)

处于监听状态的套接字将维护一个客户连接请求队列,它最多能容纳backlog个客户请求  // 即为tcp层链接缓冲池。未能进入的会重新连接,直至超时。详见《TCP/IP 详解 卷1》

4、              accept()

accept可让进程(或线程)告诉你有空间的连接,这时系统会从内核中取出已经建立的客户连接(当时是得归你这进程管理的),将其返回给该进程 —— 通过返回一个新的套接字文件描述符来方便进程操作这次套接字对应的连接。

因而,accept默认会阻塞进程,直至有客户来建立连接。

要注意一点的是,负责通讯的是一个新返回的套接字文件,而原来负责监听那个,它依然存在,继续监听。我们可以分别称呼为连接套接字、监听套接字。

例: while True:

         tcpCliSock, addr = tcpSock.accept() # addr为对方IP

5、              connect( (Host, Port) )

这个是Client上的操作了,表示与指定Host上的Port建立连接。在上图中我们看到,Client上并没有预先绑定一个端口。此时,直接调用connect(),系统会随意分配一个未占用的端口给该进程。至于占用端口的持续时间多久,个人并不清楚。

例: ADDR = ( HOST, PORT )  # HOST = ‘ localhost ‘ ,  PORT = 21567

     tcpOnCliSock.connect( ADDR )

6、              send( string [, flag] )

string 表示要发送的数据,flag 则指定传输控制方式(反正我也不太懂)

send()的作用只是将数据复制到发送缓冲区。如果string长度大于发送缓冲区最大值……没测试过,据说是直接分片到NIC处理。如果返回ERROR,是因为网络不好什么的导致TCP队爆掉而不是缓冲区满了。

例: tcpCliSock.send( ‘[%s] %s’ % (ctime, data))

7、              recv( bufsize [, flag] )

bufsize指定了接收缓冲区的最大值。当缓冲区接收完相应数据后会立即返回;若数据大于接收缓冲区的最大值,剩余部分会留在内核的缓冲区中,等待下一个recv()来读取。

例: While True:

         data = tcpOnCliSock.recv( BUFSIZE )  # BUFSIZE = 1024

8、              close()

关闭套接字,并释放其资源。若它有牵涉到TCP连接,则该连接也被释放。这个麻烦的地方主要是对异常的发现和处理,要考虑一些奇葩情况。

例: tcpSock.close()

 

         上述内容都是同步方面的。改为异步的话感觉十分麻烦,要顾及效率十分麻烦。开启一个新的socket其实开销是十分大的。在我翻的资料里看到的,是很老的用select来统一管理socket的方式,下面也说下吧。

select(inputs, outputs, exceptions, timeout)

它的出现,最初是通过系统调用构建一结构来监视多个文件描述符。现在在python里,它是将指定socket构建成一个list,然后通过监视,将它们分别在inputs, out

puts, exceptions三个子list上扔来扔去,确认其读/写或异常状态,以便于处理。

详见 www.cnblogs.com/coser/archive/2012/01/06/2315216.html

         其实异步的话,感觉效率是麻烦的地方。又要扯上操作系统了。在这同步异步、阻塞非阻塞,还有轮询、通知、回调这些方法也不介绍了。详细的可以看blog.csdn.net/pizi0475/article/details/6233083 上面介绍的是Windows里的I/O模型。说下自己看完后的大概印象吧。这里肯定是有很多错的地方,最近没空改了。等之后有时间,看网络编程了,再另外写文并且在这改回来吧。

1、  select模型 —— 进程轮询去查看信息

2、  WSAAsyncSelect模型 —— 系统通知

3、  WSAEventSelect模型 —— 消息到达后直接通知对应线程,不用经过系统

4、  Overlapped I/O 事件通知模型 —— 消息到达后直接放至对应的线程中,并通知

5、  Overlapped I/O 完成例程模型 —— 放置前顺便先依照进程交代的方式对消息内容进行处理

6、  IOCP模型(CP指Completion Port) —— 即完成端口。处理方式由一线程一连接变成了n线程加上一消息队列

关于完成端口,推荐www.cnblogs.com/lancidie/archive/2011/12/19/2293773.html

 

posted on 2015-11-15 22:02  黑色双肩包  阅读(146)  评论(0编辑  收藏  举报

导航