3. Socket编程¶
socket编程相关的模块是socket,所以在使用socket编程的时候,需要导入socket模块
import socket
3.1. socket编程概述¶
socket编程属于传输层编程,主要研究的是两台机器如何发送基本字符或者字节流信息
,或者可以理解成,socket编程研究的是如何一个字节一个字节的把信息传输给对方,
而此时双方并不关心每一个字节具体的含义,反正我发过去就完了。
而一旦发送完毕,你发送的内容到底是啥意思,发送者的意图等,属于HTTP协议的范围,HTTP协议属于应用层协议,默认两台机器能顺利发送完整有序的字符信息。
一个不太恰当的例子,socket编程相当于快递公司,它只负责把货物安全按时发到制定人员 ,如果你发送的是一堆货物,则货物可能分不同的批次车次运输到对方,而具体如何运输, 发货方其实没必要知道。 但一旦发送到对方手中,货物的使用方式,作用和价值等等,属于 取决于收货方,跟快递公司无关,此处收货方发货收货双方可以认为是HTTP协议。
TCP/IP协议族
TCP(传输控制协议,TransmissionControlProtocal)
基于链接的,每次传输数据都要建立链接
建立链接需要消耗资源,带宽等,需要消耗时间
传输数据安全,可靠,数据传输有序
UDP(用户数据报协议,UserDatagramProtocal)
无连接
安全性差,数据接受无顺序
传输消耗带宽小,没有专门链接需要消耗资源
可以把TCP理解成挂号信,我们的信用卡法律文书等重要文件,如果邮寄需要发送挂号信, 此时信件的邮寄过程中实时信息更新,可查可追溯,所以安全。
UDP则相当于普通信件,我们邮寄平信的时候,只要把信件交给快递员就好了,至于信件能不能送到,何时送到, 没有保障,也不能查看。
socket:套接字,在网络里边,特指通过ip地址和端口进行通信的一种机制
IP地址: 用来唯一标示网络中的设备地址,通过IP地址可以找到网络中任何一台设备
端口:
进行应用程序身份鉴定的一个数字,一台机器可能有很多程序,每台程序进行网络 通信都要有自己的特定的端口号
理论上数值是0-65535之间
一般1000以下不推荐开发者自行使用
3.2. UDP编程¶
在开始编程之前我们需要先看几个概念:
无连接:
UDP是没有链接的编程,即发送数据只要有IP和端口号就发送,至于对方能不能接受, 端口和IP是否正确不在考虑范围之内。客户端(Client) 和 服务器端(Server):
我们以前的编程都是一个程序,因为我们需要模拟通信过程,也就是需要有 至少两方参与,我们习惯把发起通信的一方称为客户端(Client),把另一方 称为服务器端(Server)。
在我们进行socket编程的时候,要想让程序跑起来,一般我们需要写两个程序, 然后分别运行。Server端流程
建立socket,socket是负责具体通信的一个实例a
# 建立socket,负责跟对方进行通信 # socket初始化需要两个参数 # 1. AF_INET:指定socket编程使用IPv4协议族 # 2. SOCK_DGRAM:指定通信方式为UDP方式 sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
绑定,为创建的socket指派固定的端口和ip地址
# 绑定ip和端口 # 127.0.0.1 这个地址代表的是机器本身 # 7777:端口,负责指定服务器上的对应的应用 # 端口随机指定,一般大于1000小于65535 # 所谓端口地址,是一个有字符串和整数组成的元组 # 注意bind函数的参数只有一个tuple sock.bind(("127.0.0.1", 7777))
接受对方发送内容
# 接收数据 # 如果对方不发送或者还没发送数据,则服务器端只能等待 # recvfrom接收的返回值是一个元组形式,前一个选项是数据,后一个选项是对方的地址 # 参数的含义是缓冲区的大小 data, addr = sock.recvfrom(500) # 发送的数据信息只能是bytes格式 # 要想让信息显示出来,一般需要进行解码操作 text = data.decode() print(type(text))
给对方发送反馈,此步骤为非必须步骤
# 需要给client发送反馈信息,此步骤为非必须项 rsp = "I have received your msg" # 发送的数据格式要求是一个字节串 # 由字符串得到字节串需要进行编码操作 data = rsp.encode() # 发送数据给对方 sock.sendto(data, addr)
Client端流程
建立通信的socket实例
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
发送内容到指定服务器
text = "I love wangxiaojing" # 发送的数据只能是bytes格式 # str格式数据要想转换成bytes格式需要编码 data = text.encode() # 特别注意地址信息的格式 sock.sendto(data, ("127.0.0.1", 7777))
接受服务器给定的反馈内容,如果期待对方回复的话。
# 如果对方没有反馈,此函数会一直等待 # data是一个bytes格式的内容 data, addr = sock.recvfrom(1000) # bytes格式内容转换成字符串需要进行解码 text = data.decode() print(text)
整个通信过程如下图所示:
关于上述代码,有几个说明需要再提一下:
源代码可以刘大拿个人博客下载
源代码案例01是服务器,02是客户端
启动的时候需要先启动服务端程序,这样客户端发送数据才不会报错
传输的数据是bytes格式,发送端需要先编码,由str转换成bytes,接收端 需要接收后解码,由bytes格式转换成str
原则上服务器程序要求永久运行,需要用死循环实现,具体实现参考源代码案例03
3.3. TCP编程¶
TCP编程是面向链接的传输,即每次传输之前需要先建立一个链接。
同样在编写程序的时候,客户端和服务器端两个程序需要编写。大致过程和方法
跟UDP编程相似,只不过:
增加了传输的时候建立/关闭链接的过程
建立socket实例的时候使用参数不同
接收/发送函数名称不同
Server端的编写流程
建立socket负责具体通信,这个socket其实只负责接受对方的请求,真正通信的是链接后从新建立的socket
# 1. 建立socket负责具体通信,这个socket其实只负责接受对方的请求,真正通信的是链接后从新建立的socket # 需要用到两个参数 # AF_INET: 含义同udp一致 # SOCK_STREAM: 表明是使用的tcp进行通信 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
绑定端口和地址
# 2. 绑定端口和地址 # 此地址信息是一个元祖类型内容,元祖分两部分,第一部分为字符串,代表ip,第二部分为端口,是一个整数,推荐大于10000 addr = ("127.0.0.1", 8998) sock.bind(addr)
监听接入的访问socket,此过程可以理解成是等待客户端发起连接
# 3. 监听接入的访问socket sock.listen()
接受访问的socket,可以理解接受访问即建立了一个通讯的链接通路
# 4. 接受访问的socket,可以理解接受访问即建立了一个通讯的链接通路 # accept返回的元祖第一个元素赋值给skt,第二个赋值给addr skt,addr = sock.accept()
接受对方的发送内容,利用接收到的socket接收内容,同UDP一样,发送的信息 只能是bytes格式,所以需要进行解码编码操作。
# 5. 接受对方的发送内容,利用接收到的socket接收内容 # 500代表接收使用的buffersize #msg = skt.receive(500) msg = skt.recv(500) # 接受到的是bytes格式内容 # 想得到str格式的,需要进行解码 msg = msg.decode()
如果有必要,给对方发送反馈信息。需要注意的是,此时的发送反馈不需要填写 客户端地址,因为发送反馈是按照已经建立好的链接发送的,即已经知道对方的地址。
# 6. 如果有必要,给对方发送反馈信息 skt.send(rst.encode())
关闭链接通路
# 7. 关闭链接通路 skt.close()
参看案例代码04
Client端流程
建立通信socket
# 1. 建立通信socket sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
链接对方,请求跟对方建立通路。实际使用当中,需要确保连接建立 成功后才能发送数据。
# 2. 链接对方,请求跟对方建立通路 addr = ("127.0.0.1", 8998) sock.connect(addr)
发送内容到对方服务器
# 3. 发送内容到对方服务器 msg = "I love wangxiaojing" sock.send(msg.encode())
接受对方的反馈,当然前提是确保对方有反馈发回,否则 会无限期等待。
# 4. 接受对方的反馈 rst = sock.recv(500) print(rst.decode())
关闭链接通路
# 5. 关闭链接通路 sock.close()
参看案例代码05
整个通信过程如下图所示:
socket编程相对比较简单,在我们很多不太需要表示层参与的地方,比如物联网中,经常 对一些简单消息直接用socket发送即可,因为信息数量少,有时候可能并不是太重要,直接采用 UDP发送节省资源。 UDP编程还有一个常用的应用场景就是即时通信,即时通信需要长时间 高频炉发送数据,但相对来讲信息重要性相对较低,此时采用UDP比较合适。