socket编程
最近做项目,需要了解一些socket编程的相关知识,因为本身在大学的时候也接触过socket编程,但是时间已经很久了,并且当时学的不够用心,今天下午看了一些博客,对socket有了一些新的认识,在这里记录一下我学到的一些知识。
一.什么是socket
什么socket,这里我们要先从osi模型和TCP/IP模型开始讲起,OSI一共包含了7层,其中应用层、表示层和会话层是面向用户的,程序员可以在此基础上进行开发,如http、ftp、SMTP(邮件)等协议就是基于应用层建立的,下面的四层协议是由系统内核封装,对用户不可见的。
OSI模型表达的过于复杂,为了简化模型,人们又提出了TCP/IP模型,这种模型将上面的3层模型统一的表示成应用层。
为了使数据分组从源传送到目的地,源端OSI模型的每一层都必须与目的端的对等层进行通信,这种通信方式称为对等层通信。在每一层通信过程中,使用本层自己协议进行通信。在进行网络通信的时候如打开一个网页,数据在发送端(这里指客户端)会从上到下进行封装,然后传输到服务端后,数据会自下而上进行解封装。最后获取上层数据。
我们在进行网络通信的过程中,可以使用http这样应用层的协议,但是当没有协议满足我们的需求的时候,我们需要自主与TCP/UDP进行交互,这时候就有了socket,这是建立在传输层上的一个抽象层,帮助我们与tcp/ip建立连接进行通信,我们可以把它看作两个主机进行双向通信的端点。socket主要有3个参数:通信的目的IP地址、使用的传输层协议(TCP或UDP)和使用的端口号。
二. socket工作流程
socket是一种打开——读/写——关闭的模式的实现,使用TCP协议进行通讯为例。一共分为服务端socket和客户端socket。服务端socket负责监听客户端连接请求,当客户端发送请求时,两端进行通信。具体流程如下:
- 1.服务端根据地址类型、socket类型、协议创建socket。
- 2.服务端为socket绑定ip地址和端口号。
- 3.服务器socket监听端口号请求,随时准备接受客户端发来的请求,这时候服务器的socket并没有被打开。
- 4.客户端创建socket
- 5.客户端打开socket,根据服务器的ip地址和端口号试图连接服务器socket
- 6.服务器socket接收到客户端socket请求,被动打开,开始接收客户端请求,直到客户端返回连接信息。这时候socket进入阻塞状态,所谓阻塞即accept()方法一直到客户端返回连接信息后才返回,开始接收下一个客户端谅解请求。
- 7.客户端连接成功,向服务端发送连接状态信息。
- 8.服务器accept方法返回,连接成功
- 9.客户端向socket写入信息
- 10.服务器读取信息
- 11.客户端关闭
- 12.服务器关闭
1.三次握手
在客户端和服务端建立TCP连接的过程中会发生有名的三次握手
第一次握手:客户端会尝试连接服务器,向服务器发送syn包,syn=j,客户端进入syn_send状态等待服务器确认。
第二次握手:服务器接收客户端syn包并确认(ack=j+1),同时向客户端发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态。
第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED(已确认),完成三次握手。
值得强调的是accept()这个方法,内核会创建两个队列,SYN队列和accept队列,其中accept队列的长度由backlog指定。服务器在调用accept之后,阻塞,等待accept队列有元素,当三次握手结束后服务器会把客户端从syn队列转移到accept队列,而accept()被唤醒,从accept队列中取出请求方,重新建立一个socket用于准备发送和接收数据,原来的socket还在监听哪个端口。换一句话说,socket()返回的套接字用于监听(listen)和接受(accept)客户端请求,这个套接字不能用于与客户端之间发送和接受数据。accept()接受一个客户端的连接请求,并返回一个新的套接字。所谓“新的”就是说这个套接字与socket()返回的套接字不是同一个socket。这个新的套接字用于与这次接受的客户端之间的通信。
2.四次握手
当客户端发送信息完毕之后,客户端会与服务端断开连接,此时会发生4次挥手。
第一次挥手:先由客户端向服务器端发送一个FIN,请求关闭数据传输。
第二次挥手:当服务器接收到客户端的FIN时,向客户端发送一个ACK,其中ack的值等于FIN+SEQ,此时客户端不再向服务端发送消息,但是服务端还可以向客户端发送信息。
第三次挥手:服务端向客户端发送一个FIN,去告诉客户端关闭应用。
第四次挥手:当客户端收到服务端的FIN时,发送一个ACK给服务器。其中ACK的值等于FIN+SEQ。
三. socket实例
demo1 同步实例
这里我抛出来一些简单的socket的demo,先贴出服务端的
1 | from socket import * |
服务端会一直监听11113端口,直到有客户端连接进来,会创建一个新的socket用于交换信息。
下面是客户端的代码
1 | from socket import * |
这个demo基本上涵盖了socket通信的整个流程,但是demo中有一些缺点,因为socket.recv()是阻塞的,所以当服务端执行到recv()的时候,会一直等待,直到客户端发送消息才能够继续往下执行代码。这样的结构对于一些实时性要求比较高的场景很不友好。比如说,我们在用实时的视频流进行监控和一些异常计算,当有异常现象的时候,我们会与巡逻小车进行socket进行通信来处理异常,这就要求我们既要能够实时接收摄像头传来的视频流,又要接巡逻小车发来的指令信号,使用上述的demo作为框架就显得不那么合适了。
2. demo2 异步实例
有一种解决方法就是使用异步通信来解决,这样我们可以对传来的数据进行监听,当监听到有客户端传来请求时,我们会对传来的消息进行解析处理;当客户端没有传来请求时,服务端就会处理自己的事情。在python中可以使用select完成异步通信,下面有一个demo,这里我只抛出来客户端的demo,服务端的异步处理同客户端:
1 | from socket import * |