目录
SOCKET
一、基于TCP协议的socket套接字编程
1.1 什么是socket
socket是应用层和传输层之间的一个抽象层,我们经常把socket称为套接字,它是一组接口,把TCP/IP层的复杂操作抽象为几个简单的接口供应用层调用以实现进程在网络中通信。
在设计模式中,socket其实就是一个门面模式,它把复杂的TCP/IP协议隐藏在socket接口后面,对用户来说,一组简单的接口就是全部,让socket去组织数据,以符合指定的协议。我们无需深入理解tcp/udp协议,socket已经为我们封装好了,我们只需要遵循socket的规定去编程,写出的程序自然就是遵循tcp/udp标准的。
1.2 套接字分类
- 基于文件类型的套接字家族:AF_UNIX,两个套接字进程只能运行在同一台机器上,现在基本不用这个
- 基于网络类型的套接字家族:AF_INET,这是使用最广泛的一个,可以在不同机器上使用
1.3 套接字工作流程
先从服务器端说起,服务器端先初始化socket,然后与端口绑定(bind),对端口进行监听(listen),调用accept阻塞,等待客户端连接。在这时如果有个客户端初始化一个socket,然后连接服务器(connect),如果连接成功,这时客户端与服务器端的连接就建立了。客户端发送数据请求,服务器端接收请求并处理请求,然后把回应数据发送给客户端,客户端读取数据,最后关闭连接,一次交互结束。下面直接用代码举例:
- 基于TCP协议的简单套接字编程
# 简单的tcp通信服务端
# 以买手机打电话举例,socket.SOCK_STREAM表示建立tcp连接,socket.SOCK_DGRAM表示建立udp连接
# 导入socket模块
import socket
# 实例化一个socket对象(买了个手机)
soc = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
# 绑定ip地址(插电话卡),参数传元组:(ip,端口号)
# soc.bind(('127.0.0.1',8080)) # ip写127.0.0.1,只能自己访问
soc.bind(('192.168.11.176',8080)) # 如果写本机ip,局域网外部可以访问
# 监听(手机开机),这个5是半连接池的大小
soc.listen(5)
# 等待客户端连接(等待别人给我打电话)
print('start>>>')
conn,addr = soc.accept() # conn:通路(三次握手建立的双向连接),addr:(客户端的ip,端口)
# 通信:收/发消息
data = conn.recv(1024) # 一次最大接收1024字节数
print('来自客户端的数据>>>',data)
# 发送数据,数据必须是bytes格式
conn.send(data.upper()) # 把接收到的客户端的数据变成大写发送回去
# 关闭通路(挂断电话)
conn.close()
# 关闭连接(销毁手机)
soc.close()
# 简单的tcp通信客户端
import socket
# 创建一个socket对象
soc = socket.socket() # 不传参默认是tcp协议
# 连接服务端
soc.connect(('192.168.11.176',8005)) # 指定服务端ip和端口
# 通信:发/收消息
soc.send(b'zyl') # 发送数据,必须是bytes格式,也可以用soc.send('zyl'.encode('utf8'))
data = soc.recv(1024) # 接收数据
print('收到服务端的数据>>>',data)
# 关闭连接
soc.close()
- 加入连接和通信循环的基于TCP协议的套接字编程
# 服务端
import socket
# 生成一个socket对象
soc = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
# 绑定地址和端口号
soc.bind(('127.0.0.1',8005)) # 127.0.0.1只能自己访问
# 监听(半连接池的大小,不是连接数)
soc.listen(5)
# 等待客户端连接
while True: # 连接循环
print('等待客户端连接')
conn,addr = soc.accept() # 卡住,如果没有客户端连接,会一直卡在这,当有客户端连接,才继续往下走
print('有个客户端连接上了',addr)
while True: # 通信循环
try:
# windows如果客户端断开,会报错,所以加了try
# linux如果客户端断开,不会报错,会收到空,所以当data为空时,也break
# 等待接收,最大收取1024个字节
data = conn.recv(1024) # 会卡住,当客户端有数据过来,才会执行
if len(data) == 0: # 处理linux客户端断开,在window下这段代码根本不会执行(即便是客服端发了空,也不会走到这行代码)
break
print(data)
conn.send(data.upper())
except Exception:
break
# 关闭通路
conn.close()
# 关闭连接
soc.close()
# 客户端
import socket
# 创建一个socket对象
soc = socket.socket()
soc.connect(('127.0.0.1',8005))
while True: # 通信循环
in_s = input('请输入要发送的数据>>>').strip()
# 发送的数据必须是b格式,in_s.encode('utf-8') 把字符串编码成b格式
# 把b格式转成字符串
# ss=str(b'hello',encoding='utf-8')
# ss=b'hello'.decode('utf-8')
# #把字符串转成b格式
# by=bytes('hello',encoding='utf-8')
# by='hello'.encode('utf-8')
soc.send(in_s.encode('utf8')) # 发送数据给服务端
data = soc.recv(1024) # 接收服务端发送的数据
print('收到服务端数据>>>',data)
# 关闭连接
soc.close()
二、基于udp协议的套接字编程
这里就直接写加入通信循环后的服务端和客户端,因为udp协议不需要建立连接
# 服务端
import socket
server = socket.socket(type=socket.SOCK_DGRAM)
server.bind(('127.0.0.1',8005))
while True: # 通信循环
data,addr = server.recvfrom(1024) # udp协议无需等待连接和监听
print(data)
server.sendto(data.upper(),addr) # 返回处理好的数据给客户端
# 客户端
import socket
client = socket.socket(type=socket.SOCK_DGRAM)
while True: # 通信循环
msg = input('请输入要发送的数据>>>').strip()
# 直接发送数据
client.sendto(msg.encode('utf8'),('127.0.0.1',8005)) # 发送bytes格式的数据和地址
# 接收数据
data = client.recvfrom(1024)
print(data)
三、UDP协议和TCP协议的区别
3.1 udp协议的特点
- 不需要建立连接,不需要监听,可以直接发送数据,不需要回执,客户端和服务端谁端口都不影响发送,它只管发送,所以不可靠
- 可以发送空内容,没有粘包问题,但是会丢包,不管客户端或服务端是否收到,它只管发,所以不可靠
3.2 UDP和TCP的区别
TCP | UDP |
---|---|
可靠连接 | 不可靠连接 |
基于数据流的传输 | 基于数据报的传输 |
粘包 | 不粘包 |
不丢包 | 丢包 |