Python中提供socket.py标准库,非常底层的接口库。
Socket是一种通用的网络编程接口,和网络层次没有一一对应的关系。
构建socket对象
socket = socket.socket(family=AF_INET, type=SOCK_STREAM, proto=0, fileno=None)
- family : 协议族
名称 | 含义 |
---|---|
AF_INEET | IPV4(默认值) |
AF_INET6 | IPV6 |
AF_UNIX | Unix Domain Socket, windows没有 |
- type: socket类型
名称 | 含义 |
---|---|
SOCK_STREAM | 面向连接的流套接字, 即TCP协议(默认值) |
SOCKET_DGRAM | 无连接的数据报文套接字。即UDP协议 |
TCP编程
socket编程需要两端,一般来说,需要一个服务端(Server),一个客户端(Client)。
这种编程模式也称为CS编程
socket对象常用方法:
方法 | 含义 |
---|---|
socket.getpeername | 返回套接字的远程地址,返回值是raddr |
socket.getsocketname | 返回套接字自己的地址,返回值为laddr |
TCP服务端编程
- 创建socket对象
- 使用bind方法,绑定IP地址,Address和端口Port
- 使用listen开始监听,在上面以绑定的地址上
- 使用accept开始等待连接进来,获取用于传送数据的socket对象和addr。注意使用accept会发生阻塞,惯用放在新的线程里面
- 接收数据recv, buffsize可以选用1024,接收的字节流bytes
- 发送数据send,发送的也是字节流bytes
构建一个TCP协议的远古版本群聊服务端
import datetime
import socket
import threading
class ChatSocket:
def __init__(self, ip="127.0.0.1", port=9999): # 本地localhost也可以用""或者"localhost"表示
self.addr = (ip, port)
self.socket = socket.socket() # 创建socket对象
self.clients = {} # 创建客户端词典
self.event = threading.Event()
self.lock = threading.Lock() # 设置一把锁,为了遍历客户端的字典的时候保证线程安全
def start(self):
self.socket.bind(self.addr) # 绑定端口
self.socket.listen(2) # 开始监听,监听客户端无上限
threading.Thread(target=self.accept).start() # 如果不创建此线程,下面的等待连接accept会阻塞当前的主线程,导致无法执行最后的stop
def accept(self):
while not self.event.is_set(): # 标志位的设置是为了stop时,不再 等待新线程的接入
new_socket, client_info = self.socket.accept() # 等待连接,阻塞,多人连接
with self.lock:
self.clients[client_info] = new_socket
print(client_info, "已连接")
t = threading.Thread(target=self.recv_send, args=(new_socket, client_info)) # 由于recv会发生阻塞行为
t.start()
def recv_send(self, new_socket, clint_info):
while not self.event.is_set():
data = new_socket.recv(1024) # 设置一次最大读取数据上限是1024字节,阻塞,如果客户端主动断开连接,会收到空的字符串
print(data)
if data.decode().strip() == "quit" or data.decode().strip() == "": # 客户端上的退出按钮会返回一个空字符串或者发送quit退出
print("用户", clint_info[1], "quit")
with self.lock:
self.clients.pop(clint_info)
new_socket.close()
break
msg = "用户{} {}说: {}".format(clint_info[1], datetime.datetime.now(), data.decode(encoding="gbk"))
print(msg)
with self.lock:
for s in self.clients.values(): # 群聊的性质就是将从client接收的信息,转发给当前所有连接到server的client客户端,是一种很,标识符的选择一定要慎重
s.send(msg.encode(encoding="gbk"))
def stop(self):
self.event.set() # 设为True
with self.lock:
for s in self.clients.values():
s.close()
self.socket.close()
cs = ChatSocket()
cs.start()
while True:
cmd = input(">>").strip()
if cmd == "quit":
cs.stop()
threading.Event().wait(3)
break
TCP客户端
再构建一个客户端
客户端连接步骤:
- 创建Socket对象
- 连接到远端服务端的IP和port端口, connect方法
- 传输数据
- send发送数据
- recv接收数据, 会阻塞
- 关闭连接,释放资源
import socket
import threading
import logging
fmt = "%(asctime)s %(threadName)s %(thread)s %(message)s"
logging.basicConfig(format=fmt, level=logging.INFO)
class Client:
def __int__(self, ip="127.0.0.1", port=9999):
self.sock = socket.socket() # 建立socket对象
self.addr = ip, port
self.event = threading.Event()
def start(self):
self.<