1.1socket编程之tcp编程
""" socket类型 sock_stream 面向连接的流套接字,默认值 tcp协议 sock_dgram 无连接的数据报文套接字,udp协议 """ import socket s = socket.socket() s.bind(('127.0.0.1',9999)) #bind接受一个2元祖 s.listen() """ Accept a connection. The socket must be bound to an address and listening for connections. The return value is a pair (conn, address) where conn is a new socket object usable to send and receive data on the connection, and address is the address bound to the socket on the other end of the connection """ new_socker,info = s.accept() #只接受一个client请求,阻塞 data=new_socker.recv(1024) #阻塞 print(data) print(type(data)) new_socker.send('back {}'.format(data).encode()) s.close()
例子
有阻塞就要尽量放到线程中去执行,不要影响主线程
import socket server = socket.socket() server.bind(('127.0.0.1',9999)) server.listen() ''' 建立一个client,socket连接之后,只能发一次数据 在accept出阻塞,需要建立第二个连接 ''' while True: new_socket,ip = server.accept() data = new_socket.recv(1024) new_socket.send('ck test {}'.format(data).encode())
socket群聊实例
import socket import threading class ChatTcpServer: def __init__(self,ip,port): self.ip = ip self.port = port self.cliets = {} #实例化就创建一个socket对象 self.socket = socket.socket() def start(self): self.socket.bind((self.ip,self.port)) self.socket.listen() threading.Thread(target=self._accept,name="_aceept").start() def _accept(self): #只开启一个accept线程,连接产生的new_socket负责和线程receive同信 while True: print('1~~~~~~~~~~~~~~~~~~~',threading.enumerate()) new_socket,raddr = self.socket.accept() #阻塞主线程,所以开启一个工作线程receive threading.Thread(target=self._receive,name="reveive",args=(new_socket,)).start() self.cliets[new_socket] = raddr print(self.cliets) print(type(self.cliets.keys())) def _receive(self,new_socket): #客户端连接几个socket 就开几个receive线程 while True: print('2~~~~~~~~~~~~~~~~~~~',threading.enumerate()) data = new_socket.recv(1024) #阻塞 for k in self.cliets.keys(): k.send('ack {}'.format(data).encode()) def stop(self): for s in self.cliets.values(): s.close() self.socket.close() if __name__ == '__main__': cs = ChatTcpServer('127.0.0.1',9999) cs.start() # print('3~~~~~~~~~~~~~~~~~~~',threading.enumerate()) # cs.stop()
problem:there has a error when cliens exit
ConnectionAbortedError
import socket import threading class ChatTcpServer: def __init__(self,ip,port): self.ip = ip self.port = port self.cliets = {} #实例化就创建一个socket对象 self.socket = socket.socket() self.event = threading.Event() def start(self): self.socket.bind((self.ip,self.port)) self.socket.listen() threading.Thread(target=self._accept,name="_aceept").start() def _accept(self): #只开启一个accept线程,连接产生的new_socket负责和线程receive同信 while not self.event.is_set(): new_socket,raddr = self.socket.accept() #阻塞主线程,所以开启一个工作线程receive threading.Thread(target=self._receive,name="reveive",args=(new_socket,)).start() self.cliets[new_socket] = raddr print(self.cliets) print(type(self.cliets.keys())) def _receive(self,new_socket): #客户端连接几个socket 就开几个receive线程 while not self.event.is_set(): data = new_socket.recv(1024) #阻塞 for k in self.cliets.keys(): k.send('ack {}'.format(data).encode()) def stop(self): if self.cliets: for s in self.cliets.values(): s.close() self.socket.close() self.event.set() if __name__ == '__main__': cs = ChatTcpServer('127.0.0.1',9999) cs.start() while True: cmd = input("please set stop:>>>") if cmd == 'quit': cs.stop() break
以上版本服务端可以断开连接,但是客户端断开连接抛出异常
增加客户端断开命令
客户端主动断开连接的问题,服务端知道自己何时断开 如果是客户端断开服务器不知道,所有好的做法客户端发出特殊消息通知服务器断开连接,但是客户端主动断开服务端主动发送一个空消息,超时返回异常,捕获异常并清理连接,即使为客户端提供了断开命令,也不能保证客户端会使用它断开连接,还是要增加这个退出功能
def _receive(self,new_socket): #客户端连接几个socket 就开几个receive线程 while not self.event.is_set(): data = new_socket.recv(1024) #阻塞 if data == b"quit": self.cliets.pop(new_socket) new_socket.close() break for k in self.cliets.keys(): k.send('ack {}'.format(data).encode())
socket常用方法
socket = socket.socket() socket.recv(1024) bufsize=1024 获取数据,默认是阻塞状态 socket.recvfrom(1024) 获取数据,返回一个二元组(bytes,address) socket.recv_into(buffer=1024,nbytes=10,flags=None) 获取到nbytes的数据后,存储到buffer中,如果nbytes没有指定或者0,将buffer大小的数据存入buffer中,返回接收的字节数 socket.send(bytes) tcp发送设备 socket.sendall(bytes) tcp发送全部设备,成功返回None
Makefile
socket.socket().makefile(mode='r',buffering=None) 创建一个与该套接字相关联的文件对象,将recv方法看作读方法,将send方法看作写方法
socket.getpeername() 返回连接套接字的远程地址,返回值通常是元组(ipaddr,port)
sockete.getsockname() 返回套接字自己的地址,通常是一个元祖(ipaddr,port)
socket.setbloking(flag) 如果flag为0则将套接字设为非阻塞模式,否则将套接字设为阻塞模式(默认值)
非阻塞模式下,如果调用recv没有发现任何数据,或send()调用无法立即发送数据,那么将引起socket.error异常
socket.settimeout(value) 设置套接字操作的超时期,timeout是一个浮点数单位秒
值为none表示没有超时期,一般超时期应该在刚创建套接字时设置,因为他们可能用于连接的操作(connect())
read和readline(行读取,包括换行符)
import socket socket = socket.socket() socket.bind(('127.0.0.1',9999)) socket.listen() while True: new_socket,raddr = socket.accept() f = new_socket.makefile('rw') print(f) line = f.readline() #Read and return one line from the stream line = f.read(10) #Read up to n bytes from the object and return them #只有输入十个字节才会return print(line) f.write('ack {}'.format(line)) f.flush() f.close()
client编写
import socket socket = socket.socket() socket.bind(('127.0.0.1',9999)) #server ip socket.listen() new_socket,raddr = socket.accept() data = new_socket.recv(1024) print(data) new_socket.send('ack {}'.format(data).encode())
import socket import threading import datetime import logging FORMAT = '%(asctime)s %(threadName)s %(thread)d %(message)s' logging.basicConfig(level=logging.INFO,format=FORMAT) class ChatClient: def __init__(self,ip='127.0.0.1',port=9999): self.ip = ip self.port = port self.sock =socket.socket() self.event = threading.Event() def start(self): self.sock.connect((self.ip,self.port)) self.send('i am ready') threading.Thread(target=self.receive,name='receive').start() def receive(self): while not self.event.is_set(): try: #连接不成功,捕获异常 data = self.sock.recv(1024) logging.info(data) except Exception as e: logging.info(e) def send(self,msg:str): self.sock.send('{}'.format(msg).encode()) def stop(self): self.sock.close() self.event.set() logging.info('Client stops') def main(): cc = ChatClient() cc.start() while True: cmd = input("please set a number:") if cmd.strip() == b'quit': cc.stop() break cc.send(cmd) if __name__ == '__main__': main()