Python网络编程
Socker套接字
Socket又称套接字,是进程间通信的一种方式,网络化的应用程序在开始任何通讯之前都必须先创建套接字。我们每天浏览网页,聊天,收发Email等都是基于Socket来完成通信的
Python中的网络编程
Python中提供了socket模块。模块中的socket()
函数被用来创建套接字。套接字也有自己的一套函数来提供基于套接字的网络通信。
创建socker
使用socket.socket()函数来创建套接字,语法如下:
socket(family, type, proto, fileno=None)
- family: 套接字家族,可以是AF_UNIX或者AF_INET
- type: 套接字类型,可以是面向连接的(TPC协议)或者是非连接的(UDP协议),分为SOCK_STREAM或SOCK_DGRAM。
- proto: 所用的协议,一般不填默认为0
套接字对象常用(内建)方法
服务器端套接字函数:
函数 | 描述 |
---|---|
s.bind() | 绑定地址(主机号,端口号对)到套接字 |
s.listen(backlog) | 开启TCP监听,backlog参数用来指定最多允许多少个客户端连接到服务器,它的值至少为1。 |
s.accept() | 被动接受TCP客户端连接,等待连接的到来 |
客户端套接字函数:
函数 | 描述 |
---|---|
s.connect() | 主动初始化TCP服务器连接 |
s.connect_ex() | connect()函数的的扩展版本,出错时返回出错码,而不是抛出异常 |
公共用途的套接字函数:
函数 | 描述 |
---|---|
s.recv(bufsize) | 接收TCP数据,数据以bytes类型返回,bufsize为要接收的最大数据量 |
s.send() | 发送TCP数据 |
s.sendall() | 完整发送TCP数据 |
s.recvfrom() | 接收UDP数据 |
s.sendto() | 发送UDP数据 |
s.close() | 关闭套接字 |
s.getpeername() | 返回连接套接字的远程地址,返回值通常是元组 |
s.getsockname() | 返回套接字自己的地址,通常是一个元组 |
s.setsockopt(level, optname, value) | 设置给定套接字选项的值,一般用来设置端口号复用 |
s.getsockopt() | 返回套接字选项的值 |
s.settimeout(timeout) | 设置套接字操作的超时期,timeout是一个浮点数,单位是秒,值为None表示没有超时期。 |
s.gettimeout() | 返回当前超时期的值,单位是秒,如果没有设置超时期,则返回None |
s.fileno() | 返回套接字的文件描述符 |
s.setblocking(flag) | 如果flag为0,则将套接字设为非阻塞模式,否则将套接字设置为阻塞模式 |
s.makefile() | 创建一个与该套接字相关联的文件 |
基于TCP协议的socket通信
TCP通信时,一定要先建立相关的连接,才能发送数据。
客户端发送消息,服务端接收消息:
服务端:server.py
import socket
# 1.创建socket对象
sk = socket.socket()
# 2.绑定ip和端口
sk.bind(("127.0.0.1", 9000))
# 3.开启监听
sk.listen()
# 4.建立三次握手
conn, addr = sk.accept()
# 5.收发数据,单位是字节,代表一次最多接受多少数据
res = conn.recv(1024)
print(res.decode("utf-8"))
# 6.四次挥手
conn.close()
# 7.释放端口
sk.close()
客户端:client.py
import socket
# 1.创建socket对象
sk = socket.socket()
# 2.与服务端进行连接
sk.connect(("127.0.0.1", 9000))
# 3.发送数据,二进制的字节流
sk.send("今天是周末啊".encode("utf-8"))
# 4.关闭连接
sk.close()
说明:当客户端和服务端建立连接后,服务端程序退出后端口号不会立即释放。
解决方法:
-
更改服务端端口号
-
设置端口号复用,也就是说让服务端程序退出后端口号立即释放,设置如下:
sk.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
TCP网络应用程序的注意事项:
- 当TCP客户端程序想要和TCP服务端程序进行通信的时候必须要先建立连接。
- TCP客户端程序一般不需要绑定端口号,因为客户端是主动发起建立连接的。
- TCP服务端程序必须绑定端口号,否则客户端找不到这个TCP服务端程序。
- 关闭accept返回的套接字意味着和这个客户端已经通信完毕。
- 关闭listen后的套接字意味着服务端的套接字关闭了,会导致新的客户端不能连接服务端,但是之前已经连接成功的客户端还能正常通信。
基于TCP协议的socket循环通信
server.py
import socket
# 1.创建socket对象
sk = socket.socket()
# 让当前端口重复绑定多个程序(仅在测试阶段使用)
sk.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
# 2.在网络中注册主机(绑定ip和端口号)
sk.bind(("127.0.0.1", 9000))
# 3.开启监听
sk.listen()
while True:
# 4.建立三次握手
conn, addr = sk.accept()
# 5.收发数据
while True:
try:
res = conn.recv(1024)
if res.lower() == b"q":
print("客户端退出聊天了")
break
print(f"来自客户端的消息:{res.decode('utf-8')}")
to_client_value = input("请输入服务端要给客户端发送的消息:").encode("utf-8")
conn.send(to_client_value)
except ConnectionResetError:
print("客户端连接中断")
break
# 6.四次挥手
conn.close()
# 7.释放连接
sk.close()
client.py
import socket
# 1.创建socket对象
sk = socket.socket()
# 2.连接服务器
sk.connect(("127.0.0.1", 9000))
# 3.收发数据
while True:
to_server_data = input("请输入您要发送的数据:").strip().encode("utf-8")
# 如果客户端发送空的内容,服务都就会阻塞,所以无论哪一端,都不能发送空的消息
if not to_server_data:
print("发送内容不能为空")
continue
sk.send(to_server_data)
if to_server_data.lower() == b"q":
break
res = sk.recv(1024)
print(f"来自服务端的消息:{res.decode()}")
# 4.关闭连接
sk.close()
粘包现象
使用tcp协议在发送数据时,会出现粘包现象。
在客户端和服务器端会有一个数据缓冲区,缓冲区用来临时保存数据,为了能够完整的接收到数据,因此缓冲区会设置的比较大,在收发数据较频繁时,由于tcp传输消息的无边界,不清楚应该截取多少长度,导致客户端和服务端有可能把多条数据当成是一条数据进行截取,造成粘包。
使用strcut模块解决粘包
- pack()函数:可以把任意长度的数字转化成具有4个字节的固定长度字节流。
- unpack()函数:把4个字节的值恢复成原本的数字,返回的是元组。
server.py
import socket
import struct
sk = socket.socket()
sk.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sk.bind(("127.0.0.1", 9000))
sk.listen(3)
conn, addr = sk.accept()
msg = input("请输入要发送的数据:").encode()
# 先把长度发过去
res = struct.pack("i", len(msg))
conn.send(res)
# 发送第二个数据
conn.send(msg)
# 发送第三个数据
conn.send("hello".encode())
conn.close()
sk.close()
client.py
import socket
import struct
import time
sk = socket.socket()
sk.connect(("127.0.0.1", 9000))
time.sleep(0.1)
# 接收第一次发送过来的数据(数据的大小)
n = sk.recv(4)
tup = struct.unpack("i", n)
n = tup[0]
# 接收第二次真正的数据
res1 = sk.recv(n)
# 接收第三次真正的数据
print(res1.decode())
res2 = sk.recv(1024)
print(res2.decode())
# 关闭连接
sk.close()
基于UDP协议的socket通信
基于udp协议的socket通信无须建立连接,先开启服务端或者客户端都可以。
udp_server.py
import socket
server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
server.bind(("127.0.0.1", 9000))
while True:
from_client_data = server.recvfrom(1024) # 阻塞,等待来消息
print(from_client_data)
print(f"来自客户端{from_client_data[1]}: {from_client_data[0].decode('utf-8')}")
send_client_data = input("请输入要发送给客户端的消息:")
server.sendto(send_client_data.encode("utf-8"), from_client_data[1])
udp_client.py
import socket
client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
while True:
send_server_data = input("请输入要发送给服务端的消息:").strip()
client.sendto(send_server_data.encode("utf-8"), ("127.0.0.1", 9000)) #
data, addr = client.recvfrom(1024)
print(f"来自服务端{addr}的消息: {data.decode('utf-8')}")