Socket套接字、基于TCP的套接字程序、基于UDP的套接字程序、TCP协议与UDP协议的区别
Socket套接字编程
为何学习socket套接字一定要先学习互联网协议:
1、首先:要想开发一款自己的C/S架构软件,就必须掌握socket编程
2、其次:C/S架构的软件(软件属于应用层)是基于网络进行通信的
3、然后:网络的核心即一堆协议,协议即标准,你想开发一款基于网络通信的软件,就必须遵循这些标准。
socket层
从上图中,我们没有看到socket的影子,它在哪里呢?用图说话一目了然
socket套接字是什么
Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。
所以,无需深入理解tcp/udp协议,socket已经封装好了,只需要遵循socket的规定去编程,写出的程序自然就是遵循tcp/udp标准的。
基于文件类型的套接字家族的名字:AF_UNIX
基于网络类型的套接字家族的名字:AF_INET
套接字工作流程
- 基于TCP套接字通信的流程图:
先从服务器端说起。服务器端先初始化Socket,然后与端口绑定(bind),对端口进行监听(listen),调accept阻塞,等待客户端连接。在这时如果有个客户端初始化一个Socket,然后连接服务器(connect),如果连接成功,这时客户端与服务器端的连接就建立了。客户端发送数据请求,服务器端接收请求并处理请求,然后把回应数据发送给客户端,客户端读取数据,最后关闭连接,一次交互结束
socket()模块的用法:
import socket
socket.socket(socket_family,socket_type,protocal=0)
"""
socket_family 可以是 AF_UNIX 或 AF_INET。
socket_type 可以是 SOCK_STREAM 或 SOCK_DGRAM。protocol 一般不填,默认值为 0。
"""
# 获取tcp/ip套接字
tcpSock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 获取udp/ip套接字
udpSock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
"""
由于 socket 模块中有太多的属性。我们在这里破例使用了'from module import *'语句。
使用 'from socket import *',我们就把 socket 模块里的所有属性都带到我们的命名空
间里了,这样能大幅减短我们的代码。例如tcpSock = socket(AF_INET, SOCK_STREAM)
"""
socket套接字函数
1、服务端套接字函数
s.bind() # 绑定(主机,端口号)到套接字
s.listen() # 开始TCP监听
s.accept() # 被动接受TCP客户的连接,(阻塞式)等待连接的到来
2、客户端套接字函数
s.connect() # 主动初始化TCP服务器连接
s.connect_ex() # connect()函数的扩展版本,出错时返回出错码,而不是抛出异常
3、公共用途的套接字函数
s.recv() # 接收TCP数据
s.send() # 发送TCP数据(send在待发送数据量大于己端缓存区剩余空间时,数据丢失,不会发完)
s.sendall() # 发送完整的TCP数据(本质就是循环调用send,sendall在待发送数据量大于己端缓存区剩余空间时,数据不丢失,循环调用send直到发完)
s.recvfrom() # 接收UDP数据
s.sendto() # 发送UDP数据
s.getpeername() # 连接到当前套接字的远端的地址
s.getsockname() # 当前套接字的地址
s.getsockopt() # 返回指定套接字的参数
s.setsockopt() # 设置指定套接字的参数
s.close() # 关闭套接字
4、面向锁的套接字方法
s.setblocking() # 设置套接字的阻塞与非阻塞模式
s.settimeout() # 设置阻塞套接字操作的超时时间
s.gettimeout() # 得到阻塞套接字操作的超时时间
5、面向文件的套接字的函数
s.fileno() # 套接字的文件描述符
s.makefile() # 创建一个与该套接字相关的文件
基于TCP的套接字程序
tcp是基于链接的,必须先启动服务端,然后再启动客户端去链接服务端
tcp服务端
server = socket() # 创建服务器套接字
server.bind() # 把地址绑定到套接字
server.listen() # 监听链接
inf_loop: # 服务器无限循环
cs = server.accept() # 接受客户端链接
comm_loop: # 通讯循环
cs.recv()/cs.send() # 对话(接收与发送)
cs.close() # 关闭客户端套接字
server.close() # 关闭服务器套接字(可选)
tcp客户端
client = socket() # 创建客户套接字
client.connect() # 尝试连接服务器
comm_loop: # 通讯循环
client.send()/client.recv() # 对话(发送/接收)
client.close() # 关闭客户套接字
基于TCP协议的套接字通信
socket通信流程与打电话流程类似,以打电话为例来实现一个简版的套接字通信
## 服务端.py文件
import socket
# 1、买手机
# server = socket.socket(socket.AF_INET,socket.SOCK_STREAM) # 流式协议
server = socket.socket() # 默认就是基于网络的TCP传输协议,同上
# 2、绑定电话卡(绑定ip和port)
server.bind(('127.0.0.1',8080)) # 回环地址ip
# 3、开机(过渡)
server.listen(5) # 半连接池
# 4、接收链接请求(监听)
conn, client_addr = server.accept() # server.accept背后在做tcp的三次握手,有返回值
# 返回值赋值给conn对象(代表tcp三次握手的封装成果)和client_addr(客户端的ip + port(端口))
# 5、接收客户端发送的消息(听别人说话)
data = conn.recv(1024) # 最大接收的字节数,data(bytes类型)接收的是网络发过来的数据
print(data)
# 6、给客户端回复消息(给别人回话)
conn.send(data.upper()) # 以大写格式回复消息
# 7、挂电话
conn.close()
# 8、关机
server.close()
## 客户端.py文件
import socket
# 1、买手机
client = socket.socket()
# 2、打电话(拨号)
client.connect(('127.0.0.1',8080)) # 回环地址ip
# 3、发\收数据
client.send("hello".encode('utf-8')) # 发消息(只能发送字节类型)
data = client.recv(1024) # 收消息
print(data.decode('utf-8')) # 服务端发来的数据,收到是大写的bytes,用utf-8解码
# 4、关机
client.close()
'''
补充知识:回环地址IP
IP地址127.0.0.1 代表本机IP地址,等价于localhost⽤ http://127.0.0.1 就可以测试本机中配置的Web服务器。
127.0.0.1,通常被称为本地回环地址(Loop back address),不属于任何一个有类别地址类。它代表设备的本地虚拟接
口,所以默认被看作是永远不会宕掉的接口。在windows操作系统中也有相似的定义,所以通常在不安装网卡前就可以ping通
这个本地回环地址。一般都会用来检查本地网络协议、基本数据接口等是否正常的
'''
通信循环及代码优化
1.客户端校验消息不能为空
2.服务端添加兼容性代码(mac linux)
3.服务端重启频繁报端口占用错误
'''
在重启服务端时可能会遇到
OSError:[Errno 48] Address already in use # 地址已经在使用中
这个是由于服务端仍然存在四次挥手的time_wait状态在占用地址
# 在绑定之前加入一条socket配置,重用ip和端口
server=socket(AF_INET,SOCK_STREAM)
server.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) #就是它,在bind前加
server.bind(('127.0.0.1',8080))
'''
4.客户端异常关闭服务端报错的问题
异常捕获
5.服务端链接循环
6.半连接池
设置可以等待的客户端数量
加上通信循环与链接循环,即异常处理
## 服务端.py文件
import socket
# 1、买手机
server = socket.socket(socket.AF_INET,socket.SOCK_STREAM) # 流式协议
# server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) 关于端口占用的修改
# 2、绑定电话卡
server.bind(('127.0.0.1',8080)) # 回环地址ip
# 3、开机
server.listen(5) # 半连接池
# 4、接收链接请求
while True: # 链接循环,可以不停的接电话
conn, client_addr = server.accept()
print(client_addr)
# 5、收发消息
while True: # 通信循环,可以不断的通信,收发消息
try: # 应对Windows系统的异常
data = conn.recv(1024)
if len(data) == 0: # 应对linux系统的异常处理,正常情况data不会等于0
break
print(data)
conn.send(data.upper())
except Exception: # 应对Windows系统的异常
break # 无论应对哪个异常都应该将循环结束掉
# 6、挂电话
conn.close()
# 7、关机
server.close()
## 客户端.py文件
import socket
# 1、买手机
client = socket.socket(socket.AF_INET,socket.SOCK_STREAM) # 流式协议
# 2、打电话
client.connect(('127.0.0.1',8080)) # 回环地址ip
# 3、发\收数据
while True: # 加while循环
msg = input('>>>>:').strip()
if len(msg) == 0: # 解决输入空的bug
continue
client.send(msg.encode('utf-8')) # 发消息
data = client.recv(1024) # 收消息
print(data.decode('utf-8'))
# 4、关闭
client.close()
基于UDP协议的套接字程序
- udp是无链接的,先启动哪一端都不会报错
udp服务端
server = socket() # 创建一个服务器的套接字
server.bind() # 绑定服务器套接字
while True: # 服务器无限循环
client = server.recvfrom()/server.sendto() # 对话(接收与发送)
server.close() # 关闭服务器套接字
udp客户端
client = socket() # 创建客户套接字
while True: # 通讯循环
client.sendto()/client.recvfrom() # 对话(发送/接收)
client.close() # 关闭客户套接字
基于UDP协议的套接字通信
# 服务端.py文件
from socket import *
server = socket(AF_INET,SOCK_DGRAM) # 数据报协议
server.bind(('127.0.0.1',8081))
while True :
data,client_addr = server.recvfrom(1024)
server.sendto(data.upper(),client_addr)
server.close()
# 客户端.py文件 (支持并发但比较狭窄)
from socket import *
client = socket(AF_INET, SOCK_DGRAM)
while True :
msg = input('请输入您要发送的消息>>>:').strip()
client.sendto(msg.encode('utf-8'),('127.0.0.1',8081))
data,server_addr = client.recvfrom(1024)
print(data.decode('utf-8'))
client.close()
TCP协议与UDP协议的区别
- 1.TCP(transport control protocol,传输控制协议)是面向连接的,面向流的,提供高可靠性服务。收发两端(客户端和服务器端)都要有一一成对的socket,因此,发送端为了将多个发往接收端的包,更有效的发到对方,使用了优化方法(Nagle算法),将多次间隔较小且数据量小的数据,合并成一个大的数据块,然后进行封包。这样,接收端,就难于分辨出来了,必须提供科学的拆包机制。 即面向流的通信是无消息保护边界的。
- 2.UDP(user datagram protocol,用户数据报协议)是无连接的,面向消息的,提供高效率服务。不会使用块的合并优化算法,, 由于UDP支持的是一对多的模式,所以接收端的skbuff(套接字缓冲区)采用了链式结构来记录每一个到达的UDP包,在每个UDP包中就有了消息头(消息来源地址,端口等信息),这样,对于接收端来说,就容易进行区分处理了。 即面向消息的通信是有消息保护边界的。
- 3.TCP是基于数据流的,于是收发的消息不能为空,这就需要在客户端和服务端都添加空消息的处理机制,防止程序卡住,而udp是基于数据报的,即便是你输入的是空内容(直接回车),那也不是空消息,udp协议会帮你封装上消息头。
UDP的recvfrom是阻塞的,一个recvfrom(x)必须对唯一一个sendinto(y),收完了x个字节的数据就算完成,若是y>x数据就丢失,这意味着udp根本不会粘包,但是会丢数据,不可靠
TCP的协议数据不会丢,没有收完包,下次接收,会继续上次继续接收,己端总是在收到ack时才会清除缓冲区内容。数据是可靠的,但是会粘包。