0%

计算机网络(1)——socket编程

socket编程

最近做项目,需要了解一些socket编程的相关知识,因为本身在大学的时候也接触过socket编程,但是时间已经很久了,并且当时学的不够用心,今天下午看了一些博客,对socket有了一些新的认识,在这里记录一下我学到的一些知识。

一.什么是socket

什么socket,这里我们要先从osi模型和TCP/IP模型开始讲起,OSI一共包含了7层,其中应用层、表示层和会话层是面向用户的,程序员可以在此基础上进行开发,如http、ftp、SMTP(邮件)等协议就是基于应用层建立的,下面的四层协议是由系统内核封装,对用户不可见的。
upload successful

OSI模型表达的过于复杂,为了简化模型,人们又提出了TCP/IP模型,这种模型将上面的3层模型统一的表示成应用层。
upload successful

为了使数据分组从源传送到目的地,源端OSI模型的每一层都必须与目的端的对等层进行通信,这种通信方式称为对等层通信。在每一层通信过程中,使用本层自己协议进行通信。在进行网络通信的时候如打开一个网页,数据在发送端(这里指客户端)会从上到下进行封装,然后传输到服务端后,数据会自下而上进行解封装。最后获取上层数据。

我们在进行网络通信的过程中,可以使用http这样应用层的协议,但是当没有协议满足我们的需求的时候,我们需要自主与TCP/UDP进行交互,这时候就有了socket,这是建立在传输层上的一个抽象层,帮助我们与tcp/ip建立连接进行通信,我们可以把它看作两个主机进行双向通信的端点。socket主要有3个参数:通信的目的IP地址、使用的传输层协议(TCP或UDP)和使用的端口号。

二. socket工作流程

upload successful
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连接的过程中会发生有名的三次握手

upload successful

第一次握手:客户端会尝试连接服务器,向服务器发送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次挥手
upload successful

第一次挥手:先由客户端向服务器端发送一个FIN,请求关闭数据传输。

第二次挥手:当服务器接收到客户端的FIN时,向客户端发送一个ACK,其中ack的值等于FIN+SEQ,此时客户端不再向服务端发送消息,但是服务端还可以向客户端发送信息。

第三次挥手:服务端向客户端发送一个FIN,去告诉客户端关闭应用。

第四次挥手:当客户端收到服务端的FIN时,发送一个ACK给服务器。其中ACK的值等于FIN+SEQ。

三. socket实例

demo1 同步实例

这里我抛出来一些简单的socket的demo,先贴出服务端的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
from socket import *
from time import ctime

HOST = ''
PORT = 11113
BUFSIZE = 4096
ADDR = (HOST, PORT)

tcpServer = socket(AF_INET, SOCK_STREAM)
tcpServer.bind(ADDR)
tcpServer.listen(10)

while 1:
print('waiting for connection...')
tcpClient, addr = tcpServer.accept()
print(addr)

while 1:
data = tcpClient.recv(BUFSIZE)
print(data.decode())

if not data:
print('---------')
break;

buf = '[' + ctime() + ']' + data.decode()
tcpClient.send(buf.encode())

tcpClient.close()
tcpServer.close()

服务端会一直监听11113端口,直到有客户端连接进来,会创建一个新的socket用于交换信息。
下面是客户端的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
from socket import *
import json

HOST = 'localhost'
PORT = 11113
BUFSIZE = 4096
ADDR = (HOST, PORT)

tcpClient = socket(AF_INET, SOCK_STREAM)
tcpClient.connect(ADDR)



while 1:
data = input('> ')

if not data:
break
tcpClient.send(data.encode())

data = tcpClient.recv(BUFSIZE)
print(data.decode())
if not data:
break

tcpClient.close()

这个demo基本上涵盖了socket通信的整个流程,但是demo中有一些缺点,因为socket.recv()是阻塞的,所以当服务端执行到recv()的时候,会一直等待,直到客户端发送消息才能够继续往下执行代码。这样的结构对于一些实时性要求比较高的场景很不友好。比如说,我们在用实时的视频流进行监控和一些异常计算,当有异常现象的时候,我们会与巡逻小车进行socket进行通信来处理异常,这就要求我们既要能够实时接收摄像头传来的视频流,又要接巡逻小车发来的指令信号,使用上述的demo作为框架就显得不那么合适了。

2. demo2 异步实例

有一种解决方法就是使用异步通信来解决,这样我们可以对传来的数据进行监听,当监听到有客户端传来请求时,我们会对传来的消息进行解析处理;当客户端没有传来请求时,服务端就会处理自己的事情。在python中可以使用select完成异步通信,下面有一个demo,这里我只抛出来客户端的demo,服务端的异步处理同客户端:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
from socket import *
import json
import time
import select

HOST = 'localhost'
PORT = 6688
BUFSIZE = 4096
ADDR = (HOST, PORT)

tcpClient = socket(AF_INET, SOCK_STREAM)

tcpClient.connect(ADDR)

input = [tcpClient]
# tcpClient.setblocking(False)
print("["+time.ctime()+"]" +"发现异常")
data_exchange = {'Position':0, 'CarArrived':False, 'ResetCarPosition':False, 'CarPatrol':False}
data = json.dumps(data_exchange)
tcpClient.send(data.encode())
tcpClient.settimeout(5)

while 1:
rs, ws, es = select.select(input, [], [], 1)
for indata in rs:
if indata == tcpClient:
data = tcpClient.recv(BUFSIZE)
if data:
car_recv = data.decode()
data_exchange = json.loads(car_recv)
if data_exchange['CarPatrol'] == True:
print("["+time.ctime()+"]" +"小车已经就绪,重置实验")
data_exchange = {'Position':0, 'CarArrived':False, 'ResetCarPosition':False, 'CarPatrol':False}
elif data_exchange['CarArrived'] == True:
print("["+time.ctime()+"]" +"小车已经到达异常点,开始处理异常")
time.sleep(3)
print("["+time.ctime()+"]" +"处理异常结束,小车归位")
data_exchange['ResetCarPosition'] = True
data = json.dumps(data_exchange)
tcpClient.send(data.encode())
print("1")


tcpClient.close()

参考文献

网络OSI七层模型、TCP/IP模型以及数据发送封装与解封装过程

简单理解Socket

socket中accept()函数的理解