Python Socket 编程
1、字典遍历问题
由于GIL
和内置数据结构的读写原子性,单独操作字典的某一项item
是安全的。但是遍历过程是线程不安全的,遍历中有可能被打断,其他线程如果对字典元素进行增加、弹出,都会影响字典的size
,就会抛出异常。所以在对字典操作过程中,一定要加锁。
1.1 线程遍历字典
import threading
import time
import random
import logging
import threading
FORMAT = "%(asctime)s %(threadName)10s %(thread)8d %(message)s"
logging.basicConfig(level=logging.INFO, format=FORMAT)
global_dict = {
}
lock = threading.Lock()
event = threading.Event()
def additem(d:dict):
count = 1
while not event.is_set():
d[count] = random.randint(100, 110)
count += 1
time.sleep(0.001)
def iterdict(d:dict):
while not event.is_set():
for k, v in d.items():
d[k] = random.randint(1, 10)
a = threading.Thread(target=additem, args=(global_dict, ), name='aaa')
b = threading.Thread(target=iterdict, args=(global_dict, ), name='bbb')
a.start()
b.start()
while True:
cmd = input('>>>>>>:').strip()
if cmd == 'quit':
event.set()
logging.info(global_dict)
break
else:
logging.info(threading.enumerate())
logging.info(list(global_dict.keys()))
# Exception in thread bbb:
# RuntimeError: dictionary changed size during iteration
1.2 加锁
import threading
import time
import random
import logging
import threading
FORMAT = "%(asctime)s %(threadName)10s %(thread)8d %(message)s"
logging.basicConfig(level=logging.INFO, format=FORMAT)
global_dict = {
}
lock = threading.Lock()
event = threading.Event()
def additem(d:dict):
count = 1
while not event.is_set():
with lock: # 不加锁的话,在遍历字典的时候长度变化会报错
# 读写操作是原子操作,不会被打断
d[count] = random.randint(100, 110)
count += 1
time.sleep(0.001)
def iterdict(d:dict):
while not event.is_set():
with lock:
for k, v in d.items():
# logging.info('{} {}'.format(k, v))
d[k] = random.randint(1, 10)
a = threading.Thread(target=additem, args=(global_dict, ))
b = threading.Thread(target=iterdict, args=(global_dict, ))
a.start()
b.start()
while True:
cmd = input('>>>>>>:').strip()
if cmd == 'quit':
event.set()
logging.info(global_dict)
break
else:
logging.info(threading.enumerate())
logging.info(list(global_dict.keys()))
2、TCP socket
2.1 概念
# 每一个socket连接,都会占用一个文件描述符 fd
import socket
# 创建socket对象
server = socket.socket()
# 绑定一个地址和端口的二元组
server.bind(('127.0.0.1', 9999))
# 开始监听,等待客户端连接到来
server.listen()
print(1, server)
# 接入一个到来的连接
# 默认阻塞,返回二元组
# 返回一个 新 的socket对象和客户端地址
newsocket, clientinfo = server.accept()
print(2, newsocket, clientinfo)
# 使用缓冲区获取数据
data = newsocket.recv(1024) # 阻塞,buffer空,data类型是bytes
print(3, clientinfo, data)
newsocket.send('{}'.format(data).encode())
print(6, newsocket.getsockname())
print(7, newsocket.getpeername())
newsocket.close()
# 等到另外一个新的连接
newsocket1, clientinfo1 = server.accept() # 默认阻塞,返回二元组
print(8, newsocket1, clientinfo1)
data1 = newsocket1.recv(1024) # 阻塞,buffer空
print(clientinfo1, data1)
newsocket1.send('{}'.format(data1).encode())
print(11, newsocket1.getsockname())
print(12, newsocket1.getpeername())
newsocket1.close()
server.close()
1 <socket.socket fd=372, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 9999)>
2 <socket.socket fd=388, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 9999), raddr=('127.0.0.1', 53252)> ('127.0.0.1', 53252)
3 ('127.0.0.1', 53252) b'new socket 53252'
6 ('127.0.0.1', 9999)
7 ('127.0.0.1', 53252)
8 <socket.socket fd=388, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 9999), raddr=('127.0.0.1', 53267)> ('127.0.0.1', 53267)
('127.0.0.1', 53267) b'I am the new socket, the port is 53267.'
11 ('127.0.0.1', 9999)
12 ('127.0.0.1', 53267)
Process finished with exit code 0
2.2 TCP-SERVER - 1
# TCP-server 端开发
# 一对多聊天
import socket
import datetime
import threading
import logging
FORMAT = "%(asctime)s %(threadName)10s %(thread)8d %(message)s"
logging.basicConfig(level=logging.INFO, format=FORMAT)
class ChatServer:
def __init__(self, ip='127.0.0.1', port=10002):
self.sock = socket.socket()
self.addr = ip, port
self.clients = {
} # 客户端
self.event = threading.Event()
self.lock = threading.Lock()
def start(self):
self.sock.bind(self.addr) # 绑定
self.sock.listen() # 监听
# accept会阻塞主线程,所以新开一个线程
# 字典读写是原子性的,但是遍历不是线程安全的
threading.Thread(target=self.accept).start()
def accept(self):
while not self.event.is_set():
sock, client = self.sock.accept() # 阻塞
with self.lock:
self.clients[client] = sock # 添加到客户端字典
# 准备接收数据,recv是阻塞的,开启新的线程
# 循环的接收新连接
threading.Thread(target=self.recv, args=(sock, client)).start(