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(
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值