socket
1 socket的定义
socket是一个抽象层,不属于osi七层协议或五层协议
socket仅仅是将应用层以下的层做了一个封装,把复杂的TCP/IP协议族隐藏在Socket接口后面.
但是本质上还是操作系统在执行封包解包的过程
`
socket就是套接字的音译
套接字家族分为两大类
*基于文件类型的套接字家族*
套接字家族的名字:AF_UNIX
unix一切皆文件,基于文件的套接字调用的就是底层的文件系统来取数据,两个套接字进程运行在同一机器,可以通过访问同一个文件系统间接完成通信
*基于网络类型的套接字家族*
套接字家族的名字:AF_INET
(还有AF_INET6被用于ipv6,还有一些其他的地址家族,不过,他们要么是只用于某个平台,要么就是已经被废弃,或者是很少被使用,或者是根本没有实现,所有地址家族中,AF_INET是使用最广泛的一个,python支持很多种地址家族,但是由于我们只关心网络编程,所以大部分时候我么只使用AF_INET)
2 socket的作用
实际应用中,只要是在我们需要进行数据的网络传输的时候就可以使用socket
`
服务器端先初始化Socket,然后与端口绑定(bind),对端口进行监听(listen),调用accept阻塞,等待客户端连接。在这时如果有个客户端初始化一个Socket,然后连接服务器(connect),如果连接成功,这时客户端与服务器端的连接就建立了。客户端发送数据请求,服务器端接收请求并处理请求,然后把回应数据发送给客户端,客户端读取数据,最后关闭连接,一次交互结束
3 socket的使用
3.1 基础语法
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
服务端套接字函数
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() 创建一个与该套接字相关的文件
3.2 基于TCP的套接字
tcp是基于链接的,必须先启动服务端,然后再启动客户端去链接服务端
tcp是流式协议
send与recv并不是一一对应的
send之后会先将数据放入缓存,然后进行封包传输,送到目标计算机的缓存内
recv会从自己的计算机缓存内读取数据,如果没有数据则认为还未传数据,继续等待
3.2.1 思路
tcp服务端
1 ss = socket() #创建服务器套接字
2 ss.bind() #把地址绑定到套接字
3 ss.listen() #监听链接
4 inf_loop: #服务器无限循环
5 cs = ss.accept() #接受客户端链接
6 comm_loop: #通讯循环
7 cs.recv()/cs.send() #对话(接收与发送)
8 cs.close() #关闭客户端套接字
9 ss.close() #关闭服务器套接字(可选)
tcp客户端
1 cs = socket() # 创建客户套接字
2 cs.connect() # 尝试连接服务器
3 comm_loop: # 通讯循环
4 cs.send()/cs.recv() # 对话(发送/接收)
5 cs.close() # 关闭客户套接字
3.2.2 第一版
服务端
import socket
ip_port=('127.0.0.1',9000) #电话卡
BUFSIZE=1024 #收发消息的长度
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #买手机
s.bind(ip_port) #手机插卡
s.listen(5) #设置手机同时通话数量
conn,addr=s.accept() #手机待机,等待电话来,接到电话时可以得到链接和对方手机号
# print(conn)
# print(addr)
print('接到来自%s的电话' %addr[0])
msg=conn.recv(BUFSIZE) #听消息,接收讯息,可以设置一次接收的信息长度
print(msg,type(msg))
conn.send(msg.upper()) #发消息,说话
conn.close() #挂电话
s.close() #手机关机
客户端
import socket
ip_port=('127.0.0.1',9000)
BUFSIZE=1024
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.connect_ex(ip_port) # 拨电话
s.send('linhaifeng nb'.encode('utf-8')) # 发消息,说话(只能发送字节bytes类型)
feedback=s.recv(BUFSIZE) # 收消息,听电话
print(feedback.decode('utf-8'))
s.close() # 挂电话
3.2.3 第二版
第一版无法多次传入数据,而且会出现bug: 发送空数据后,程序阻塞,无法继续进行
第二版通过if判断解决发送空数据问题,通过循环完成多次传输数据
服务端
import socket
# 1、买手机
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 流式协议=》tcp协议
# 2、绑定手机卡
phone.bind(('127.0.0.1', 8081)) # 0-65535, 1024以前的都被系统保留使用
# 3、开机
phone.listen(5) # 5指的是半连接池的大小
print('服务端启动完成,监听地址为:%s:%s' % ('127.0.0.1', 8080))
# 4、等待电话连接请求:拿到电话连接conn
conn, client_addr = phone.accept()
# print(conn)
print("客户端的ip和端口:", client_addr)
# 5、通信:收\发消息
data = conn.recv(1024) # 最大接收的数据量为1024Bytes,收到的是bytes类型
print("客户端发来的消息:", data.decode('utf-8'))
conn.send(data.upper())
# 6、关闭电话连接conn(必选的回收资源的操作)
conn.close()
# 7、关机(可选操作)
phone.close()
客户端
import socket
# 1、买手机
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 流式协议=》tcp协议
# 2、拨通服务端电话
phone.connect(('127.0.0.1', 8083))
# 3、通信
while True:
msg = input("输入要发送的消息>>>: ").strip() # msg=''直接发送会导致无法收到
if len(msg) == 0:
continue
phone.send(msg.encode('utf-8'))
print('======?')
data = phone.recv(1024)
print(data.decode('utf-8'))
# 4、关闭连接(必选的回收资源的操作)
phone.close()
3.2.4 第三版
第二版会出现BUG: 一旦客户端强制关闭,服务端会崩溃
(unix系统会进入死循环,重复执行,windows系统会报错(因为conn已不存在))
在第三版中,在unix系统中使用if判断服务端收到的数据是否为空,由于第二版设置了输入为空则不发送,所以正常情况下服务端不会收到空数据,为空则证明客户端强制关闭了,执行break退出循环,以此解决死循环问题.
在windows系统中使用异常捕捉解决报错问题
`
`
服务端
# 服务端应该满足的特点:
# 1、一直提供服务
# 2、并发地提供服务
import socket
# 1、买手机
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 流式协议=》tcp协议
# 2、绑定手机卡
phone.bind(('127.0.0.1', 8080)) # 0-65535, 1024以前的都被系统保留使用
# 3、开机
phone.listen(5) # 5指的是半连接池的大小
print('服务端启动完成,监听地址为:%s:%s' % ('127.0.0.1', 8080))
# 4、等待电话连接请求:拿到电话连接conn
# 加上链接循环
while True:
conn, client_addr = phone.accept()
# 5、通信:收\发消息
while True:
try:
data = conn.recv(1024) # 最大接收的数据量为1024Bytes,收到的是bytes类型
if len(data) == 0:
# 在unix系统洗,一旦data收到的是空
# 意味着是一种异常的行为:客户度非法断开了链接
break
print("客户端发来的消息:", data.decode('utf-8'))
conn.send(data.upper())
except Exception:
# 针对windows系统
break
# 6、关闭电话连接conn(必选的回收资源的操作)
conn.close()
# 7、关机(可选操作)
phone.close()
客户端
import socket
# 1、买手机
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 流式协议=》tcp协议
# 2、拨通服务端电话
phone.connect(('127.0.0.1', 8080))
# 3、通信
while True:
msg = input("输入要发送的消息>>>: ").strip() # msg=''
if len(msg) == 0: continue
phone.send(msg.encode('utf-8'))
print('============')
data = phone.recv(1024)
print(data.decode('utf-8'))
# 4、关闭连接(必选的回收资源的操作)
phone.close()
3.3 基于UDP的套接字
基于udp的套接字与基于TCP的套接字的不同在于UDP不用建立连接,直接进行传输,因此不需要有任何链接相关的操作
基于udp的套接字不会出现发送空数据就阻塞的状况,因为接受数据时数据内还有发送端的IP和PORT
基于udp的套接字也不会出现关闭客户端,导致服务端崩溃的问题,因为双方没有关联
`
`
客户端
import socket
client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # 流式协议=》tcp协议
while True:
msg = input('>>>: ').strip()
client.sendto(msg.encode('utf-8'), ('127.0.0.1', 8081))
res = client.recvfrom(1024)
print(res)
client.close()
服务端
import socket
server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # 数据报协议=》udp协议
server.bind(('127.0.0.1', 8081))
while True:
data, client_addr = server.recvfrom(1024)
server.sendto(data.upper(), client_addr)
server.close()