网络编程

mac2025-04-13  12

Socket介绍

协议族

AF表示Address Family,用于socket()第一个参数

名称含义AF_INETIPV4AF_INET6IPV6AF_UNIXUnix Domain Socket, windows没有

Socket类型

名称含义SOCK_STREAM面向连接的流套接字。默认值,TCP协议SOCK_DGRAM无连接的数据报文套接字。UDP协议

TCP编程

Socket编程,需要两端,一般来说需要一个服务端、一个客户端,服务端称为Server,客户端称为Client。这种编程模式也称为CS编程。

TCP服务端编程

服务器端编程步骤

创建Socket对象绑定IP地址Address和端口Port。bind()方法 IPV4地址为一个二元组(‘IP地址字符串’, port)开始监听,将在指定的IP端口上监听。listen()方法获取用于传送数据的Socket对象 socket.accept() -> (socket object, address info) accept方法阻塞等待客户端建立连接,返回一个新的Socket对象和客户端地址的二元组 地址是远程客户端的地址,IPV4它是一个二元组(clientadddr, port) 接收数据 recv(bufsize[, flags])使用缓冲区接收数据发送数据 send(bytes)发送数据 import socket s = socket.socket() # 创建socket对象 s.bind(('127.0.0.1', 9988)) # 绑定IP地址和端口 s.listen() # 开始监听,等待客户端连接到来 # 接入一个到来的连接 s1, info = s.accept() # 阻塞直到和客户端成功建立连接,返回一个新的socket对象和客户端地址 print(s1, info) # s1返回的socket对象,info客户端地址字符串,端口组成的二元组 data = s1.recv(1024) # 阻塞 print(info, data) s1.send(b'it is s1 send test!!!') s1.close() s2, info = s.accept() data = s2.recv(1024) print(info, data) s2.send(b'it is s2 send test!!!') s2.close() print('will close!') s.close() <socket.socket fd=1324, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 9988), raddr=('127.0.0.1', 10217)> ('127.0.0.1', 10217) ('127.0.0.1', 10217) b'client1 send message' ('127.0.0.1', 10225) b'client2 send message' will close!

查看监听端口

Windows命令

netstat -anp tcp | findstr 9988

linux命令

netstate -tanl | grep 9988 ss -tanl | grep 9988

群聊程序

需求分析

聊天工具是CS程序,C是每一个客户端client,S是服务器端server。

服务器应该具有的功能:

启动服务,包括绑定地址和端口,并监听。建立连接,能和多个客户端建立连接接收不同用户的信息分发,将接收的某个用户的信息转发到已连接的所有客户端停止服务记录连接的客户端

代码实现

# 服务端对应一个类 class ChatServer: def __init__(self, ip, port): # 启动服务 self.sock = socket.socket() self.addr = (ip, port) def start(self): # 启动监听 pass def accept(self): # 多客户端连接 pass def recv(self): # 接收客户端数据 pass def stop(self): # 停止服务 pass import socket import logging import threading import datetime Format = '%(asctime)s - %(threadName)s - %(message)s' logging.basicConfig(format=Format, level=logging.INFO) class ChatServer: def __init__(self, ip='127.0.0.1', port=9988): # 启动服务 self.sock = socket.socket() self.addr = (ip, port) self.clients = {} # 客户端(字典, 以client为键,返回的socket对象为值) def start(self): # 启动监听 self.sock.bind(self.addr) # 绑定 self.sock.listen() # accept会阻塞主线程,新开一个线程 threading.Thread(target=self.accept).start() def accept(self): # 多客户端连接 while True: sock, client = self.sock.accept() # 阻塞 self.clients[client] = sock # 准备接收数据,recv是阻塞的,开启新的线程 threading.Thread(target=self.recv, args=(sock, client)).start() def recv(self, sock:socket.socket, client): # 接收客户端数据 while True: data = sock.recv(1024) msg = '{:%Y.%m.%d %H:%M:%S} {}:{}-->{}\n'.format(datetime.datetime.now(), *client, data.decode()) logging.info(msg) msg = msg.encode() for s in self.clients.values(): # 群发信息 s.send(msg) def stop(self): # 停止服务 for s in self.clients.values(): s.close() self.sock.close() cs = ChatServer() cs.start() 2019-10-12 22:15:28,617 - Thread-7 - 2019.10.12 22:15:28 127.0.0.1:12693-->234 2019-10-12 22:16:06,117 - Thread-8 - 2019.10.12 22:16:06 127.0.0.1:12705-->12334545 2019-10-12 22:16:17,581 - Thread-7 - 2019.10.12 22:16:17 127.0.0.1:12693-->123222222222 2019-10-12 22:16:27,022 - Thread-7 - 2019.10.12 22:16:27 127.0.0.1:12693--> Exception in thread Thread-7: Traceback (most recent call last): File "c:\programdata\miniconda3\lib\threading.py", line 916, in _bootstrap_inner self.run() File "c:\programdata\miniconda3\lib\threading.py", line 864, in run self._target(*self._args, **self._kwargs) File "<ipython-input-1-0f4ba791ec58>", line 31, in recv data = sock.recv(1024) ConnectionAbortedError: [WinError 10053] 你的主机中的软件中止了一个已建立的连接。 2019-10-12 22:16:46,551 - Thread-8 - 2019.10.12 22:16:46 127.0.0.1:12705-->quit Exception in thread Thread-8: Traceback (most recent call last): File "c:\programdata\miniconda3\lib\threading.py", line 916, in _bootstrap_inner self.run() File "c:\programdata\miniconda3\lib\threading.py", line 864, in run self._target(*self._args, **self._kwargs) File "<ipython-input-1-0f4ba791ec58>", line 36, in recv s.send(msg) ConnectionAbortedError: [WinError 10053] 你的主机中的软件中止了一个已建立的连接。 import socket import logging import threading import datetime Format = '%(asctime)s - %(threadName)s - %(message)s' logging.basicConfig(format=Format, level=logging.INFO) class ChatServer: def __init__(self, ip='127.0.0.1', port=9977): self.sock = socket.socket() self.addr = ip, port self.clients = {} self.event = threading.Event() def start(self): self.sock.bind(self.addr) self.sock.listen() threading.Thread(target=self.accept).start() def accept(self): while not self.event.is_set(): sock, client = self.sock.accept() self.clients[client] = sock threading.Thread(target=self.recv, args=(sock, client)).start() def recv(self, sock:socket.socket, client): while not self.event.is_set(): data = sock.recv(1024) msg = '{:%Y.%m.%d %H:%M:%S} {}:{}-->{}\n'.format(datetime.datetime.now(), *client, data.decode()) logging.info(msg) msg = msg.encode() for s in self.clients.values(): s.send(msg) def stop(self): self.event.set() for s in self.clients.values(): s.close() self.sock.close() cs = ChatServer() cs.start() while True: cmd = input('>>>').strip() if cmd == 'quit' or cmd == '': cs.stop() threading.Event().wait(3) break File "<ipython-input-2-23af79808cf7>", line 35 for s in self.clients.values(): ^ SyntaxError: unexpected EOF while parsing

客户端主动断开带来的问题

服务端知道自己何时断开,如果客户端断开,服务器不知道。(客户端主动断开,服务端recv会得到一个空串) 所以,好的做法是,客户端断开发出特殊消息通知服务器端断开连接。但是,如果客户端主动断开,服务端主动发送一个空消息,超时返回异常,捕获异常并清理连接。 即使为客户端提供了断开命令,也不能保证客户端使用它断开连接。但是还是要增加这个退出功能。

注意:

由于GIL和内置数据结构的读写原子性,单独操作字典的某一项item是安全的。但是遍历过程是线程不安全的,遍历中有可能被打断,其他线程如果对字典元素进行增加、弹出,都会影响字典的size,就会抛出异常。所以还是要加锁Lock。

import logging import threading import datetime import socket Format = '%(asctime)s - %(threadName)s - %(message)s' logging.basicConfig(format=Format, level=logging.INFO) class ChatServer: def __init__(self, ip='127.0.0.1', port=9911) 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() 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 threading.Thread(target=self.recv, args=(sock, client)).start() def recv(self, sock:socket.socket, client): while not self.event.is_set(): data = sock.recv(1024) msg = data.decode().strip() if msg == 'quit' or msg == '': with self.lock: self.clients.pop(client) sock.close() logging.info('{} quits}'.format(client)) break msg = '{:%Y.%m.%d %H:%M:%S} {}:{}-->{}\n'.format(datetime.datetime.now(), *client, data.decode()) logging.info(msg) msg = msg.encode() with self.lock: for s in self.clients.values(): s.send(msg) def stop(self): self.event.set() with self.lock: for s in self.clients.values(): s.close() self.sock.close() cs = ChatServer() cs.start() while True: cmd = input('>>>').strip() if cmd == 'quit' or cmd == '': cs.stop() threading.Event().wait(3) break logging.info(threading,enumerate()) # 用来观察断开后线程的变化 logging.info(cs.clients)

TCP客户端编程

客户端编程步骤

创建socket对象连接到远端服务端的ip和port,connect()方法传输数据 使用send、recv方法发送、接收数据 关闭连接,释放资源 import socket client = socket.socket() addr = ('127.0.0.1', 9999) client.connect(addr) client.send(b'123123123') data = client.recv(1024) print(data) client.close()

客户端类

import socket import threading import logging import datetime Format = '%(asctime)s - %(threadName)s - %(message)s' logging.basicConfig(format=Format, level=logging.INFO) class ChatClient: def __init__(self, ip='127.0.0.1', port=9911): self.sock = socket.socket() self.addr = ip, port self.event = threading.Event() def start(self): self.sock.connect(self.addr) self.sock.send('i am ready') threading.Thread(target=self.recv, name='recv').start() def recv(self): while not self.event.is_set(): try: data = self.sock.recv(1024) except Exception as e: logging.error(e) break msg = '{:%Y.%m.%d %H:%M:%S} {}:{}-->{}\n'.format(datetime.datetime.now(), *client, data.decode()) logging.info(msg) def send(self, msg:str): data = '{}\n'.format(msg.strip()).encode() self.sock.send(data) def stop(self): self.sock.close() self.event.wait(3) self.event.set() logging.info('Client stops') def main(): cc = ChatClient() cc.start() while True: cmd = input('>>>') if cmd.strip() == 'quit': cc.stop() break File "<ipython-input-3-7182a4a85f47>", line 17 def start(self): ^ SyntaxError: unexpected EOF while parsing import socket import logging ip = '127.0.0.1' port = 9966 Format = '%(asctime)s - %(threadName)s - %(message)s' logging.basicConfig(format=Format, level=logging.INFO) addr = ip, port s = socket.socket() s.bind(addr) s.listen() sock, info = s.accept() data = sock.recv(1024) logging.info(data) 2019-10-13 17:22:12,913 - MainThread - b'123qwe'
最新回复(0)