【一】什么是socket
- Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。
- 在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面
- 对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。
- 所以,我们无需深入理解tcp/udp协议,socket已经为我们封装好了,我们只需要遵循socket的规定去编程,写出的程序自然就是遵循tcp/udp标准的。
- 也有人将socket说成ip+port
- ip是用来标识互联网中的一台主机的位置
- 而port是用来标识这台机器上的一个应用程序
- ip地址是配置到网卡上的
- 而port是应用程序开启的
- ip与port的绑定就标识了互联网中独一无二的一个应用程序
- 而程序的pid是同一台机器上不同进程或者线程的标识
【二】套接字发展史及分类
- 套接字起源于 20 世纪 70 年代加利福尼亚大学伯克利分校版本的 Unix,即人们所说的 BSD Unix。
- 因此,有时人们也把套接字称为“伯克利套接字”或“BSD 套接字”。
- 一开始,套接字被设计用在同 一台主机上多个应用程序之间的通讯。
- 这也被称进程间通讯,或 IPC。套接字有两种,分别是基于文件型的和基于网络型的。
【1】基于文件类型的套接字家族
- 套接字家族的名字:AF_UNIX
- unix一切皆文件
- 基于文件的套接字调用的就是底层的文件系统来取数据
- 两个套接字进程运行在同一机器
- 可以通过访问同一个文件系统间接完成通信
【2】基于网络类型的套接字家族
- 套接字家族的名字:AF_INET
- 还有AF_INET6被用于ipv6,还有一些其他的地址家族:
- 所有地址家族中,AF_INET是使用最广泛的一个
- python支持很多种地址家族,但是由于我们只关心网络编程,所以大部分时候我么只使用AF_INET
【三】套接字工作流程
- 一个生活中的场景。
- 你要打电话给一个朋友,先拨号,朋友听到电话铃声后提起电话,这时你和你的朋友就建立起了连接,就可以讲话了。等交流结束,挂断电话结束此次交谈。
【1】流程分析(文字)
- 服务端:
- 服务器端先初始化socket对象
- 然后与本地端口绑定(bind),对端口进行监听(listen)
- 调用accept阻塞,等待客户端连接
- 服务端send发送数据请求,客户端接收请求并处理请求
- 然后客户端把回应数据发送给服务端,服务端接收并读取数据
- 最后关闭连接(close),一次交互结束
- 客户端:
- 客户端初始化一个socket对象
- 然后与本地端口绑定(bind)
- 然后连接服务器(connect)
- 客户端接收并读取数据
- 客户端send发送数据请求,服务器端接收请求并处理请求
- 最后关闭连接(close),一次交互结束
【2】流程分析(代码)
import socket
server_socket = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
ip = '127.0.0.6'
port = 8080
server_socket.bind((ip, port))
server_socket.listen()
conn, addr = server_socket.accept()
data = ''
conn.send(data.encode())
data = conn.recv(1024)
print(data.decode())
conn.close()
server_socket.close()
import socket
client_socket = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
ip = '127.0.0.6'
port = 8080
client_socket.connect((ip, port))
data = client_socket.recv(1024)
print(data.decode())
data = ''
client_socket.send(data.encode())
client_socket.close()
【3】套接字的相关函数
s.bind() 绑定(主机,端口号)到套接字
s.listen() 开始TCP监听
s.accept() 被动接受TCP客户的连接,(阻塞式)等待连接的到来
s.connect() 主动初始化TCP服务器连接
s.connect_ex() connect()函数的扩展版本,出错时返回出错码,而不是抛出异常
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() 关闭套接字
s.setblocking() 设置套接字的阻塞与非阻塞模式
s.settimeout() 设置阻塞套接字操作的超时时间
s.gettimeout() 得到阻塞套接字操作的超时时间
s.fileno() 套接字的文件描述符
s.makefile() 创建一个与该套接字相关的文件
【四】三次握手和四次挥手
【1】三次握手

【2】四次挥手

【五】基于TCP的套接字
import socket
server_socket = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
ip = '127.0.0.7'
port = 8080
server_socket.bind((ip, port))
server_socket.listen(5)
conn, addr = server_socket.accept()
to_client_data = '我是来自服务端的数据'
conn.send(to_client_data.encode())
from_client_data = conn.recv(1024)
print(from_client_data.decode())
conn.close()
server_socket.close()
import socket
client_socket = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
ip = '127.0.0.7'
port = 8080
client_socket.connect((ip, port))
from_server_data = client_socket.recv(1024)
print(from_server_data.decode())
to_server_data = '我是来自客户端的数据'
client_socket.send(to_server_data.encode())
client_socket.close()
【六】基于UDP的套接字
from conf import settings
import socket
server_socket = socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM)
server_socket.bind(settings.ADDR)
from_client_data, addr = server_socket.recvfrom(1024)
print(from_client_data.decode())
print(f"addr;{addr}")
to_client_data = '我是来自服务端的数据'
server_socket.sendto(to_client_data.encode(), addr)
server_socket.close()
from conf import settings
import socket
client_socket = socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM)
to_server_data = '我是来自客户端的数据'
client_socket.sendto(to_server_data.encode(), settings.ADDR)
print(client_socket)
from_server_data, addr = client_socket.recvfrom(1024)
print(from_server_data.decode())
print(f"addr:{addr}")
client_socket.close()
【七】TCP协议模型
【1】一代
from conf import settings
import socket
server_socket = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
server_socket.bind(settings.ADDR)
server_socket.listen(5)
conn, addr = server_socket.accept()
print(conn)
from_client_data = conn.recv(1024)
print(from_client_data.decode())
print(f"addr;{addr}")
to_client_data = '我是来自服务端的数据'
conn.send(to_client_data.encode())
conn.close()
server_socket.close()
import socket
from conf import settings
client_socket = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
client_socket.connect(settings.ADDR)
to_server_data = '我是来自客户端的数据'
client_socket.send(to_server_data.encode())
print(client_socket)
from_server_data = client_socket.recv(1024)
print(from_server_data.decode())
client_socket.close()
【2】二代(只能发一次信息)
from conf import settings
import socket
server_socket = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
server_socket.bind(settings.ADDR)
server_socket.listen(5)
while True:
conn, addr = server_socket.accept()
from_client_data = conn.recv(1024)
print(f"这是来自客户端的数据;{from_client_data.decode()}")
to_client_data = input("请输入发送给客户端的数据:").strip()
conn.send(to_client_data.encode())
conn.close()
server_socket.close()
import socket
from conf import settings
client_socket = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
client_socket.connect(settings.ADDR)
while True:
to_server_data = input("请输入发送给服务端的数据:").strip()
client_socket.send(to_server_data.encode())
from_server_data = client_socket.recv(1024)
print(F"这是来自服务端的数据:{from_server_data.decode()}")
client_socket.close()
【3】三代(可以多次发送信息)
from conf import settings
import socket
server_socket = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
server_socket.bind(settings.ADDR)
server_socket.listen(5)
conn, addr = server_socket.accept()
while True:
from_client_data = conn.recv(1024)
print(f"这是来自客户端的数据;{from_client_data.decode()}")
to_client_data = input("请输入发送给客户端的数据:").strip()
conn.send(to_client_data.encode())
conn.close()
server_socket.close()
import socket
from conf import settings
client_socket = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
client_socket.connect(settings.ADDR)
while True:
to_server_data = input("请输入发送给服务端的数据:").strip()
client_socket.send(to_server_data.encode())
from_server_data = client_socket.recv(1024)
print(F"这是来自服务端的数据:{from_server_data.decode()}")
client_socket.close()
【4】四代(断开连接,信息为空)
from conf import settings
import socket
server_socket = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
server_socket.bind(settings.ADDR)
server_socket.listen(5)
conn, addr = server_socket.accept()
while True:
from_client_data = conn.recv(1024)
print(f"这是来自客户端的数据;{from_client_data.decode()}")
to_client_data = input("请输入发送给客户端的数据:").strip()
conn.send(to_client_data.encode())
conn.close()
server_socket.close()
import socket
from conf import settings
client_socket = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
client_socket.connect(settings.ADDR)
while True:
to_server_data = input("请输入发送给服务端的数据:").strip()
if not to_server_data:
print("输入的数据不能为空!")
continue
if to_server_data == "q":
print("当前连接已断开!")
break
client_socket.send(to_server_data.encode())
from_server_data = client_socket.recv(1024)
print(F"这是来自服务端的数据:{from_server_data.decode()}")
client_socket.close()
【5】五代(检测用户信息为空)
from conf import settings
import socket
server_socket = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR,1)
server_socket.bind(settings.ADDR)
server_socket.listen(5)
conn, addr = server_socket.accept()
while True:
try:
from_client_data = conn.recv(1024)
if not from_client_data:
break
print(f"这是来自客户端的数据;{from_client_data.decode()}")
while True:
to_client_data = input("请输入发送给客户端的数据:").strip()
if not to_client_data:
print("输入的数据不能为空!")
continue
if to_client_data == "q":
print("当前连接已断开!")
conn.send(to_client_data.encode())
break
except Exception as e:
break
conn.close()
server_socket.close()
import socket
from conf import settings
client_socket = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
client_socket.connect(settings.ADDR)
while True:
to_server_data = input("请输入发送给服务端的数据:").strip()
if not to_server_data:
print("输入的数据不能为空!")
continue
if to_server_data == "q":
print("当前连接已断开!")
break
client_socket.send(to_server_data.encode())
from_server_data = client_socket.recv(1024)
if from_server_data == 'q':
break
print(F"这是来自服务端的数据:{from_server_data.decode()}")
client_socket.close()
【八】UDP协议模型
【1】空数据的处理
- TCP协议是水流式协议:传入的数据不能为空,因为水是一直流的,在传输过程中不会对数据进行操作
- UDP协议是数据报协议:传入的数据可为空,在传输过程中UDP会对数据进行内部的拼接和处理
【2】断开链接的影响
- TCP协议是水流式协议:在建立链接过程中,服务端和客户端的链接是一直存在的,断开一方都会对另一方造成影响
- UDP协议是数据报协议:在建立链接过程中,是通过解析对方数据中的ip和端口,再向另一方返回数据的,所以一方发生问题并不会影响到另一方
【3】代码实现
from conf import settings
import socket
server_socket = socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM)
server_socket.bind(settings.ADDR)
from_client_data, addr = server_socket.recvfrom(1024)
print(f'来自客户端的消息:{from_client_data.decode()}')
print(f"addr;{addr}")
while True:
to_client_data = input('请输入消息:')
server_socket.sendto(to_client_data.encode(), addr)
server_socket.close()
from conf import settings
import socket
client_socket = socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM)
while True:
to_client_data = input("请输入消息:")
client_socket.sendto(to_client_data.encode(), settings.ADDR)
from_server_data, addr = client_socket.recvfrom(1024)
print(f'来自服务端的消息:{from_server_data.decode()}')
print(f"addr:{addr}")
client_socket.close()
【九】端口冲突问题
【1】问题所在
- 有时候重启服务端后会出现报错:[Errno 48] Address already in use
- 这个是由于你的服务端仍然存在四次挥手的time_wait状态在占用地址
【2】解决办法
(1)方法一
socket = socket(AF_INET,SOCK_STREAM)
socket.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
socket.bind(('127.0.0.1',8080))
(2)方法二
- 发现系统存在大量TIME_WAIT状态的连接,通过调整linux内核参数解决
vi /etc/sysctl.conf
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_tw_recycle = 1
net.ipv4.tcp_fin_timeout = 30
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_tw_recycle = 1
net.ipv4.tcp_fin_timeout
- 然后执行 /sbin/sysctl -p 让参数生效。
/sbin/sysctl -p
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· 字符编码:从基础到乱码解决
· SpringCloud带你走进微服务的世界