Python-TCP编程
1、Socket介绍:
socket 套接字
Python中提供socket.py标准库,非常底层的接口库。
Socket 是一种通用的网络编程接口,和网络底层没有一一对应的关系
协议族:
AF:address family, 用于socket()第一个参数
Socket 类型
2、TCP编程:
Socket编程,需要两端,一般来说,需要一个服务器端,一个客户端,服务器端为server,客户端为client
2.1、TCP服务器端编程:
服务器端编程步骤:
-
- 创建Socket对象
- 绑定IP 地址 address, 和端口 port,bind() 方法,IPv4地址为一个二元组(‘IP地址字符串’,port)
- 开始监听,将在指定的ip 端口上监听,listen()方法
- 获取用于传递数据的socket 对象
socket.accept() ---> (socket.object, address info)
accept 方法 阻塞等待客户端建立连接,返回一个新的额socket对象和客户端地址的二元组
地址是远程客户端的地址,IPV4 中他是一个二元组(clientaddr,port)
-
-
-
- 接受数据:recv( bufsize [, flags])使用了缓冲区接受数据
- 发送数据:send(bytes)发送数据
-
-
只有一个client 请求的 样例:
1 import socket 2 3 # 1、创建套接字 4 socket = socket.socket() 5 6 # 2、绑定ip 和 port 7 ip = '127.0.0.1' 8 port = 9999 9 laddr = (ip, port) 10 11 socket.bind(laddr) 12 13 # 3、监听 套接字 14 socket.listen() 15 16 # 4\、等待接受 客户端数据,并生产新的socket,用于数据交互 17 data = socket.accept() 18 newsocket, raddr = data 19 20 # 5、接受数据 21 22 msg = newsocket.recv() # 收到的是字节类型 23 24 # 6、发送数据 25 newsocket.send(msg) # 发送也得是字节类型 26 27 # 7、当前socket 通信结束,可以关闭 28 newsocket.close() 29 30 # 8、关闭资源 ,这里关闭的是 创建连接时的 socket 31 socket.close()
newsocket:
<socket.socket fd=212, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 9999), raddr=('127.0.0.1', 50287)>
raddr:远程套接字
('127.0.0.1', 50287)
recv(1024):缓冲区大小一般是1024 的整数倍
socket.accept():阻塞直到 和客户端 成功建立连接,返回一个socket对象和客户端地址。
newsocket.recv(1024):连接成功后,没有收到数据,会阻塞在这里。
注:
TCP的服务端编程:
端口是给进程的,线程共享资源。
通过监听来获得端口,对端口进行暴露 ,同时监听
一个socket 是为了连接,创建新的socket进行资源通信
win cmd:
netstat -anp tcp | findstr 9999 (win的管道)
-b:显示进程
Linux:
一般使用ss
测试:
1、服务器端,主线程是用来做其他工作,所以,不能让accept占据主线程,处于阻塞状态
2、每个客户端应该都在自己的线程中工作,否则会recv阻塞,影响别人
3、为了方便的将信息通知给所有的客户端,所以某个人发出的信息,必须都发给其他人一份,所以这里通过字典,将所有newsocket收集起来
4、提供退出机制,因为使用 sindows上的调试工具,直接退出,会返回一个 b'',所以这里使用 b'quit' 或 b''退出客户端
5、主线程(server),关闭的时候,线程也依次结束,避免占用资源。
6、可以提供日志功能,将日志输出到文件
1 import socket 2 import logging 3 import threading 4 5 FORMAT = '%(asctime)s %(thread)s %(threadName)s %(message)s' 6 logging.basicConfig(format=FORMAT, level=logging.INFO) 7 8 class ChatServer: 9 def __init__(self, ip='127.0.0.1', port=9999): 10 self.socket = socket.socket() 11 self.laddr = (ip, port) 12 self.event = threading.Event() 13 self.clients = {} 14 15 def start(self): 16 self.socket.bind(self.laddr) 17 self.socket.listen() 18 threading.Thread(target=self.accept, name='accept').start() 19 20 def accept(self): 21 while not self.event.is_set(): 22 newsocket, raddr = self.socket.accept() 23 self.clients[raddr] = newsocket 24 threading.Thread(target=self.recv,args=(newsocket, raddr)).start() 25 26 def recv(self, s, raddr): 27 while not self.event.is_set(): 28 data = s.recv(1024) 29 logging.info(data) 30 31 if data.strip() == b'quit' or data == b'': 32 self.clients.pop(raddr) 33 s.close() 34 break 35 for s in self.clients.values(): 36 logging.info(data) 37 msg = 'msgs is that {}'.format(data.decode()) 38 s.send(msg.encode()) 39 40 41 def stop(self): 42 for s in self.clients.values(): 43 s.close() 44 self.socket.close() 45 self.event.set() 46 47 48 cs = ChatServer() 49 cs.start() 50 51 while True: 52 cmd = input('>>') 53 if cmd.strip() == 'quit': 54 cs.stop() 55 break 56 logging.info(threading.enumerate())
socket常用方法:
2.2、MakeFile:
socket.makefile(mode='r', buffering=None, * , encoding=None, error=None, newline=None)
创建一个与该套接子相关联的文件对象,将recv 方法看做读方法,将send 方法看做是写方法。
测试:显示读和写
1 import socket 2 sock = socket.socket() 3 4 ip = '127.0.0.1' 5 port = 9999 6 7 addr = (ip, port) 8 9 sock.bind(addr) 10 sock.listen() 11 12 13 s, _ = sock.accept() 14 print(s) 15 16 # 这儿权限只能 r w a 不能 出现 + 17 # 创建一个 类文件对象 18 f = s.makefile(mode='rw') 19 20 line = f.read(10) # recv 21 print(line ) 22 23 f.write(' -----{}-----'.format(line)) 24 f.flush()# 写完之后 flush一下
1 import socket 2 sock = socket.socket() 3 4 ip = '127.0.0.1' 5 port = 9999 6 7 addr = (ip, port) 8 9 sock.bind(addr) 10 sock.listen() 11 12 13 s, _ = sock.accept() 14 print(s) 15 16 # 这儿权限只能 r w a 不能 出现 + 17 # 创建一个 类文件对象 18 f = s.makefile(mode='rw') 19 20 line = f.readline(10) # recv 21 print(line ) 22 23 f.write(' -----{}-----'.format(line)) 24 f.flush()# 写完之后 flush一下
readline 是遇到回车换行才 输出,read 是 够了字符数,输出
s : <socket.socket fd=212, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 9999), raddr=('127.0.0.1', 52129)>
f : <_io.TextIOWrapper mode='rw' encoding='cp936'>
测试:将群聊服务器端 改为makefile
1 import socket 2 import logging 3 import threading 4 5 FORMAT = '%(asctime)s %(thread)s %(threadName)s %(message)s' 6 logging.basicConfig(format=FORMAT, level=logging.INFO) 7 8 class ChatServer: 9 def __init__(self, ip='127.0.0.1', port=9999): 10 self.socket = socket.socket() 11 self.laddr = (ip, port) 12 self.event = threading.Event() 13 self.clients = {} 14 15 def start(self): 16 self.socket.bind(self.laddr) 17 self.socket.listen() 18 threading.Thread(target=self.accept, name='accept').start() 19 20 def accept(self): 21 while not self.event.is_set(): 22 newsocket, raddr = self.socket.accept() 23 24 f = newsocket.makefile('rw') 25 26 self.clients[raddr] = f 27 threading.Thread(target=self.recv,args=(newsocket, f, raddr)).start() 28 29 def recv(self, s,f, raddr): 30 while not self.event.is_set(): 31 data = f.readline(10) 32 logging.info(data) 33 34 if data.strip() == 'quit' or data == b'': 35 self.clients.pop(raddr) 36 f.close() # 只关文件描述符,可能完全断不开,所以把socket也关一下 37 s.close() 38 break 39 for f in self.clients.values(): 40 logging.info(data) 41 f.write(data) 42 f.flush() 43 44 45 def stop(self): 46 for s in self.clients.values(): 47 s.close() 48 self.socket.close() 49 self.event.set() 50 51 52 cs = ChatServer() 53 cs.start() 54 55 while True: 56 cmd = input('>>') 57 if cmd.strip() == 'quit': 58 cs.stop() 59 break 60 logging.info(threading.enumerate())
2.2、TCP客户端编程
客户端编程步骤:
-
- 创建socket对象
- 连接到远程服务端的IP 和port ,connect()方法
- 传输数据
- 使用send, recv方法,发送数据,接受数据
关闭连接,释放资源
2.3、一个群聊程序:注意:群聊,所以,不发信息,也能recv信息,所以recv放到一个线程中,一直执行
服务器端程序:
1 import socket 2 import logging 3 import threading 4 5 FORMAT = '%(asctime)s %(thread)s %(threadName)s %(message)s' 6 logging.basicConfig(format=FORMAT, level=logging.INFO) 7 8 class ChatServer: 9 def __init__(self, ip='127.0.0.1', port=9999): 10 self.socket = socket.socket() 11 self.laddr = (ip, port) 12 self.event = threading.Event() 13 self.clients = {} 14 15 def start(self): 16 self.socket.bind(self.laddr) 17 self.socket.listen() 18 threading.Thread(target=self.accept, name='accept').start() 19 20 def accept(self): 21 while not self.event.is_set(): 22 newsocket, raddr = self.socket.accept() 23 24 f = newsocket.makefile('rw') 25 26 self.clients[raddr] = f 27 threading.Thread(target=self.recv,args=(newsocket, f, raddr)).start() 28 29 def recv(self, s,f, raddr): 30 while not self.event.is_set(): 31 data = f.readline(10) 32 logging.info(data) 33 34 if data.strip() == 'quit' or data == b'': 35 self.clients.pop(raddr) 36 f.close() # 只关文件描述符,可能完全断不开,所以把socket也关一下 37 # s.close() 38 break 39 for f in self.clients.values(): 40 logging.info(data) 41 f.write(data) 42 f.flush() 43 44 45 def stop(self): 46 for s in self.clients.values(): 47 s.close() 48 self.socket.close() 49 self.event.set() 50 51 52 cs = ChatServer() 53 cs.start() 54 55 while True: 56 cmd = input('>>') 57 if cmd.strip() == 'quit': 58 cs.stop() 59 break 60 logging.info(threading.enumerate())
客户端程序:
1 import socket 2 import threading 3 4 class ChatClient: 5 def __init__(self, ip='127.0.0.1', port=9999): 6 self.socket = socket.socket() 7 self.event = threading.Event() 8 self.raddr = (ip, port) 9 10 def start(self): 11 self.socket.connect(self.raddr) 12 13 self.send('hello I am client') 14 # while not self.event.is_set(): 15 # data = self.socket.recv(1024) 16 # print(data) 17 threading.Thread(target=self.recv).start() 18 19 def send(self, msg): 20 self.socket.send(msg.encode()) 21 22 def recv(self): 23 while not self.event.is_set(): 24 data = self.socket.recv(1024) 25 print(data) 26 27 def stop(self): 28 self.socket.close() 29 self.event.set() 30 31 cc = ChatClient() 32 cc.start() 33 34 while True: 35 cmd = input('>>') 36 if cmd.strip() == 'quit': 37 cc.stop() 38 break 39 cc.send(cmd)
server:
client 1
client 2: