计算机网络基础
套接字
基于tcp协议的套接字
一个生活中的场景。你要打电话给一个朋友,先拨号,朋友听到电话铃声后提起电话,这时你和你的朋友就建立起了连接,就可以讲话了。等交流结束,挂断电话结束此次交谈。 生活中的场景就解释了这工作原理。
案例
用代码的形式进行通信:
模拟客户端:
import socket
# 1 、买手机
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 2、拨通电话
phone.connect(('127.0.0.1', 8080))
# 3、通信
phone.send('hello egon 哈哈哈'.encode('utf-8')) #是以Bytes类型发送的信息
data = phone.recv(1024)
print(data.decode('utf-8'))
# 4、关闭链接
phone.close()
'''
服务端发送的消息: HELLO EGON 哈哈哈
'''
模拟服务端:
import socket
# 1、买手机
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 流式协议=》tcp协议
# 2、绑定手机卡
phone.bind(('127.0.0.1', 8080)) #绑定ip+端口 服务端的ip是固定不变的
# 3、开机
phone.listen(5) # 5指的是半连接池的大小
# 4、等待电话链接请求
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、关机(可选操作)s
phone.close()
'''
<socket.socket fd=340, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0,
laddr=('127.0.0.1', 8080), raddr=('127.0.0.1', 50425)>
客户端的ip和端口: ('127.0.0.1', 50425)
客户端发来的消息: hello egon 哈哈哈
'''
如果将客户端发送的信息定义为用户输入,只需加入input(),如果想让通信循环,也只需加入while语句,当用户输入quit时,退出通信
客户端:
while True:
msg = input("请输入你要发送的信息:").strip()
phone.send(msg.encode('utf-8'))
if msg == 'quit':
break
data = phone.recv(1024)
print('服务端发送的消息:', data.decode('utf-8'))
服务端:
while True:
data = conn.recv(1024) # 最大接收的数据量为1024Bytes,收到是bytes类型
if data.decode('utf-8') == 'quit':
break
print('客户端发来的消息:', data.decode('utf-8'))
conn.send(data.upper())
但其实其中是有两处BUG的:
第一处BUG:
当客户端输入“ ”空格时,服务端不会返回消息,而是会处于一直在接收信息的状态。
要理解其中的原因,要先了解socket对于信息的收发原理
当客户端进行发送消息时,不会直接发送到服务端进行响应,也就是说客户端的send()发生,服务端的recv( )也不会第一时间就响应,而是客户端send()发送数据给自身机器的内存中,再由操作系统,通过网卡等物理设施,将信息传送给服务端的物理设施,物理设施再将信息存入服务端的内存,服务端的recv()再进行接送数据。
所以在上述的bug中,客户端可以进行发送空格,但是在服务端的内存中,并没有如何的数据可以让服务端的recv接收,也就导致了客户端无法收到服务端的回应。
在实际应用中,空格也是不允许被发送的。所以解决方法就是对用户的输入进行判断,如果是空格就continue,让用户重新输入。
msg = input("请输入你要发送的信息:").strip()
if len(msg) == 0:
continue
第二处BUG:当客户端与服务端进行通信时,客户端突然结束进程,服务端就会出错:
(window平台)
Traceback (most recent call last):
File "D:\python服务端.py", line 19, in <module>
data = conn.recv(1024) # 最大接收的数据量为1024Bytes,收到是bytes类型
ConnectionResetError: [WinError 10054] 远程主机强迫关闭了一个现有的连接。
如果是unix平台,服务端会出现死循环,一旦data收到空,就以为是一种异常行为:客户非法断开了链接。
if len(data) == 0:
break
针对window:捕捉异常处理
while True:
try:
data = conn.recv(1024) # 最大接收的数据量为1024Bytes,收到是bytes类型
if len(data) == 0:
break
if data.decode('utf-8') == 'quit':
break
print('客户端发来的消息:', data.decode('utf-8'))
conn.send(data.upper())
except Exception:
break
此时,服务端不会报错,但是还是有优化的地方:当客户端断开链接时,服务端应该继续进行等待接收的状态,而不是直接关闭,这就牵扯到了链接循环问题。
套娃循环:
while True:
conn, client_addr = phone.accept()
print(conn)
print('客户端的ip和端口:', client_addr)
while True:
try:
data = conn.recv(1024)
if len(data) == 0:
break
if data.decode('utf-8') == 'quit':
break
print('客户端发来的消息:', data.decode('utf-8'))
conn.send(data.upper())
except Exception:
break
conn.close()
基于udp协议的套接字通信
udp是无链接的,先启动哪一端都不会报错
udp服务端
1 ss = socket() #创建一个服务器的套接字
2 ss.bind() #绑定服务器套接字
3 inf_loop: #服务器无限循环
4 cs = ss.recvfrom()/ss.sendto() # 对话(接收与发送)
5 ss.close() # 关闭服务器套接字
udp客户端
cs = socket() # 创建客户套接字
comm_loop: # 通讯循环
cs.sendto()/cs.recvfrom() # 对话(发送/接收)
cs.close() # 关闭客户套接字
案例
客户端
import socket
client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
while True:
msg = input('请输入信息:').strip()
if msg == 'quit':
break
client.sendto(msg.encode('utf-8'), ('127.0.0.1', 8080))
data, server_addr = client.recvfrom(1024)
print(data.decode('utf-8'))
client.close()
服务端
# 基于udp协议
import socket
server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # 数据报协议=>udp协议
server.bind(('127.0.0.1', 8080))
while True:
data, client_addr = server.recvfrom(1024)
if data.decode('utf-8') == 'quit':
break
print('客户端传递的信息:', data.decode('utf-8'))
server.sendto(data.upper(), client_addr)
server.close()