本文共 9141 字,大约阅读时间需要 30 分钟。
python中提供socket.py标准库,非常底层的接口库
socket是一种通用的网络编程接口,和网络层次没一一对应的关系AF表示Address Family,用于socket()第一个参数
socket编程,需要两端,一般来说需要一个服务端、一个客户端,服务端称为Server,客户端称为Client。这种编程模式也称为CS编程。
TCP server端开发:
# TCP server 端开发import socket# import timeserver = socket.socket() # TCP 连接 IPv4ip = '127.0.0.1' # 本机回环地址,永远指向本机port = 9999 # 建议使用1000以上; TCP 65536种状态server.bind((ip, port)) # address,此方法只能绑定一次server.listen() # 真正的显示出端口,监听不是阻塞函数# time.sleep(100)print(server)# print(server.accept()) # 默认阻塞,不懂千万不要修改new_socket, client_info = server.accept()print(new_socket)print('new_socket', client_info)while True: # new_socket.send(b'server ack') data = new_socket.recv(1024) # 缺省情况下是阻塞的 print(data) new_socket.send('server ack. data={}'.format(data.decode()))new_socket.close()# new2, client2 = server.accept() # 新的连接,之前的连接已经关闭,并且两次连接可能在不同的进程# print(new2)# print('new2', client2)# data = new2.recv(1024)# print(data)# new2.send('server new2 ack. data={}'.format(data.encode()))# new2.close()server.close()
实战——写一个群聊程序
需求分析 聊天工具是CS程序,C是每一个客户端client,S是服务器端server 服务器应该具有的功能:代码实现
# tcp server 端开发import socketimport threadingimport loggingfrom datetime import datetimeFORMAT = "%(threadName)s %(thread)d %(message)s"logging.basicConfig(format=FORMAT, level=logging.INFO)class ChatServer: def __init__(self, ip='127.0.0.1', port=9999): self.sock = socket.socket() self.address = ip, port self.event = threading.Event() self.lock = threading.Lock() self.clients = { } def start(self): self.sock.bind(self.address) self.sock.listen() threading.Thread(target=self.accept, name='accept').start() def accept(self): while not self.event.is_set(): try: new_sock, client_info = self.sock.accept() # 阻塞等待连接 except Exception as e: logging.error(e) with self.lock: self.clients[client_info] = new_sock threading.Thread(target=self.rec, name='rec', args=(new_sock, client_info)).start() def rec(self, sock, client): while not self.event.is_set(): try: data = sock.recv(1024) # 阻塞等待接收信息,接收信息也可能出现异常 except Exception as e: logging.error(e) data = b'' print(data.decode(encoding='cp936')) if data.strip() == b'quit' or data.strip() == b'': # 客户端主动断开连接 with self.lock: self.clients.pop(client) # 将断开连接的客户ip和端口从字典中移除,因为下文还要遍历字典 sock.close() # 此句比较耗时,可以放在锁的外面 break msg = "{:%Y/%m/%d %H:%M:%S} [{}:{}] - {}".format(datetime.now(), *client, data.decode(encoding='cp936')) exc = [] exs = [] with self.lock: for c, s in self.clients.items(): # 遍历的是时候不允许别人pop和add,所以加锁 try: s.send(msg.encode(encoding='cp936')) # 给所有的new_sock发送的信息。注意此句可能会出现异常,如网络断了 except Exception as e: logging.error(e) exc.append(c) exs.append(s) for c in exc: self.clients.pop(c) for s in exs: # 此句比较耗时,所以放在锁外面 s.close() def stop(self): self.event.set() with self.lock: values = list(self.clients.values()) self.clients.clear() # 这是一个好习惯 for s in values: s.close() self.sock.close()cs = ChatServer()cs.start()while True: cmd = input(">>>").strip() if cmd == 'quit': cs.stop() break logging.info(threading.enumerate()) logging.info(cs.clients)
socket常用方法
makefile
socket.makefile(mode=‘r’, buffering=None, *, encoding=None, error=None, newline=None) 创建一个与该套接字相关联的文件对象,将recv方法看做读方法,将send方法看做写方法。import sockets = socket.socket()s.bind(('127.0.0.1', 9999))s.listen()s1, info = s.accept()print(s1.getpeername()) # ('127.0.0.1', 55934)print(s1.getsockname()) # ('127.0.0.1', 9999)f = s1.makefile('rw')data = f.read(10) # 一次读取10个字节print(data)msg = 'server rec {}'.format(data)f.write(msg)f.flush()print(f, s1, sep='\n')f.close()s1.close()s.close()
makefile练习
使用makefile改写群聊类import datetimeimport threadingimport socketimport loggingFORMAT = "%(asctime)s %(threadName)s %(thread)d %(message)s"logging.basicConfig(level=logging.INFO, format=FORMAT)class ChatServer: def __init__(self, ip='127.0.0.1', port=9999): self.sock = socket.socket() self.address = ip, port self.event = threading.Event() self.clients = { } self.lock = threading.Lock() def start(self): self.sock.bind(self.address) self.sock.listen() threading.Thread(target=self.accept, name='accept').start() def accept(self): while not self.event.is_set(): new_sock, client_info = self.sock.accept() new_file = new_sock.makefile('rw') with self.lock: self.clients[client_info] = new_file, new_sock # 增加了item,所以必须加锁,多线程处理同一个资源 threading.Thread(target=self.rec, name='rec', args=(new_file, client_info)).start() def rec(self, f, client): while not self.event.is_set(): line = f.readline() # 阻塞等一行来,输入数据的时候要加换行符 print(line) if line.strip() == 'quit' or line.strip() == '': # line为字符串。不能再写成b''和b'quit'了 with self.lock: _, s = self.clients.pop(client) f.close() s.close() break msg = '{:%Y/%m/%d %H:%M:%S} [{}: {}] {}'.format(datetime.datetime.now(), *client, line) # 此处的line不用解码了,因为readline读取的是字符串 with self.lock: for ff, _ in self.clients.values(): # 注意ff不能与上面的f重复 ff.write(msg) ff.flush() # def rec(self, f, client): # while not self.event.is_set(): # try: # line = f.readline() # except Exception as e: # logging.error(e) # line = 'quit' # msg = line.strip() # if msg == 'quit' or msg == '': # with self.lock: # _, sock = self.clients.pop(client) # f.close() # sock.close() # logging.info('{} quits.'.format(client)) # break # msg = '{:%Y/%m/%d %H:%M:%S} [{}: {}] {}'.format(datetime.datetime.now(), *client, line) # logging.info(msg) # with self.lock: # for ff, _ in self.clients.values(): # ff.write() # ff.flush() def stop(self): self.event.set() # keys = [] with self.lock: for f, s in self.clients.values(): f.close() s.close() self.sock.close()def main(): cs = ChatServer() cs.start() while True: cmd = input(">>>").strip() if cmd == 'quit': cs.stop() threading.Event().wait(3) break logging.info(threading.enumerate()) logging.info(cs.clients)if __name__ == '__main__': main()
import socketclient = socket.socket()ip_address = ('127.0.0.1', 9999)client.connect(ip_address) # 直接连接服务器client.send(b'abc\n')data = client.recv(1024) # 阻塞等待client.close()
import socketimport threadingimport datetimeimport loggingFORMAT = "%(threadName)s %(thread)d %(message)s"logging.basicConfig(format=FORMAT, level=logging.INFO)class ChatClient: def __init__(self, ip='127.0.0.1', port=9999): self.client = socket.socket() self.address = ip, port self.event = threading.Event() def start(self): self.client.connect(self.address) self.send("I'm ready.") threading.Thread(target=self.rec, name='rec').start() def rec(self): while not self.event.is_set(): try: data = self.client.recv(1024) # 此句可能会出现异常,如网络中断 except Exception as e: logging.error(e) break msg = "{:%Y/%m/%d %H:%M:%S} [{}:{}] {}".format(datetime.datetime.now(), *self.address, data) logging.info(msg) def send(self, msg: str): data = "{}\n".format(msg.strip()).encode() self.client.send(data) def stop(self): self.client.close() self.event.wait(3) self.event.set() logging.info('Client stops')cc = ChatClient()cc.start()while True: cmd = input(">>>").strip() if cmd == 'quit': cc.stop() break cc.send(cmd)
转载地址:http://opfvi.baihongyu.com/