1、Socket介绍:
Python中提供socket.py标准库,非常底层的接口库。
Socket是一种通用的网络编程接口,和网络层次没有一一对应关系。
跨操作系统的。紧密结合tcp和udp来使用的。
接口简单,但是背后的原理不简单,ip加tcp,通过插两端。通过socket通道;连接程序。
建立关联。
apc库。
加端口是因为应用程序太多了。绑定ip地址,作为假端口。
端口是由谁管理的
一般都是tcp和udp编程。Socket基本的,chatserver。
协议族:AF 表示address family ,用于socket()的第一个参数。
名称 |
含义 |
AF_INET |
Ipv4 |
AF_INET6 |
Ipv6 |
AF_UNIX |
Unix domain socket,windows没有 |
第三个本机使用效率不错的,通用的话就是应该进一步考虑了。
Socket类型:
名称 |
含义 |
Sock_STREAM |
面向连接的流套接字,默认值,tcp协议 |
SOCK_DGRAM |
无连接的数据报文套接字,UDP协议 |
2、tcp编程
Tcp编程是IO密集型。多线程处理的问题。
Server端:1、要有socket接口。2、找ip地址和端口绑定。3、监听端口。4、accept,接受socket,创建小的socket端。直接和应用程序连接在一起的。5、读取用户数据。6、写,发送数据。7、数据完成后断开。
Client端:1、要有socket端,主动连接别人。2、connect建立连接,有socket,和端口和ip。
3、写,发送数据。4、读取服务器端数据。5、数据完成后关闭了。
服务器端没有响应了,tcp协议管理。
Socket会占用描述符,每一个都会创建一个文件描述符。客户端看到只有的是一个。
Import socket
Server = socket.socket() socket接口。
Server.bind(ipaddr)
Server.bind((‘0.0.0.0’,9999))绑定
Server.listen()监听
S2,iP2 = server.accept()
S1.recv(1024)缓冲区大小。
S1.send(b’ack’)
decode()解码
encode()编码
创建socket对象,
一个ip和一个端口只能被一个程序使用。端口只能进行一次监听,绑定,再次监听或者绑定的话就会报错。
使用完毕后必须进行关闭。
应用:
简单的实现:解决中文的情况,编解码的时候全部注明统一编码和解码。
import socket server =socket.socket() server.bind((‘127.0.0.1‘,99)) server.listen(99) s1,ip = server.accept() print(s1) print(ip) while True: data = s1.recv(1024) print(data) s1.send(‘ack{}‘.format(data.decode(‘gbk‘)).encode(‘gbk‘)) s1.close() server.close()
<socket.socket fd=192, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=(‘127.0.0.1‘, 99), raddr=(‘127.0.0.1‘, 50149)>
(‘127.0.0.1‘, 50149)
b‘\xd6\xd0\xb9\xfa‘
客服端和服务器端建立间接,需要建立一条socket通道,每次建立连接,listen的端口都会和客户端建立的新的端口,因为连接端口就会阻塞,所以需要建立新的端口。隐士的还是看到连接的端口还是原来的socket。
应用:写一个群聊程序
1)第一步:import threading import logging import socket FORMAT = ‘%(asctime)s %(threadName)s %(thread)d %(message)s‘ logging.basicConfig(format=FORMAT,level=logging.INFO) class ChatServer: def __init__(self,ip=‘127.0.0.1‘,port=999): self.addr = (ip , port) self.socket = socket.socket() def start(self): self.socket.bind(self.addr) self.socket.listen() threading.Thread(target=self.accept,name=‘accept‘).start() def accept(self): while True: s,ip = self.socket.accept() logging.info(s) logging.info(ip) threading.Thread(target=self.connt,name=‘connt‘,args=(s,)).start() def connt(self,sockets): while True: data = sockets.recv(1024) logging.info(data) sockets.send(‘ack-{}‘.format(data.decode()).encode()) def stop(self): self.socket.close() cs = ChatServer() cs.start()
2)第二步
把所有的客户端的ip和端口保留在一个容器里面,一个客户端发送消息到服务器端,服务器端,进行消息的转发等。
import threading import logging import socket FORMAT = ‘%(asctime)s %(threadName)s %(thread)d %(message)s‘ logging.basicConfig(format=FORMAT,level=logging.INFO) class ChatServer: def __init__(self,ip=‘127.0.0.1‘,port=999): self.addr = (ip , port) self.socket = socket.socket() self.cliens = {} def start(self): self.socket.bind(self.addr) self.socket.listen() threading.Thread(target=self.accept,name=‘accept‘).start() def accept(self): while True: s,ip = self.socket.accept() logging.info(s) logging.info(ip) self.cliens[ip] = s threading.Thread(target=self.connt,name=‘connt‘,args=(s,)).start() def connt(self,sockets): while True: data = sockets.recv(1024) logging.info(data) sockets.send(‘ack-{}‘.format(data.decode(‘gbk‘)).encode(‘gbk‘)) for s in self.cliens.values(): s.send(‘ack1-{}‘.format(data.decode(‘gbk‘)).encode(‘gbk‘)) def stop(self):
for s in self.cliens.values(): s.close()
self.socket.close() cs = ChatServer() cs.start()
其他方法:
名称 |
含义 |
Socket.recv(bufsize[,flags]) |
获取数据,默认阻塞的方式 |
Socket.recvfrom(bufsize[,flags]) |
获取数据,返回一个二元组(bytes,address) |
Socket.recv_into(buffer[,nbytes[,flags]]) |
获取nbytes的数据后,存储到buffer中,如果nbytes没有指定或0,将buffer大小的数据存入buffer中,返回接受的字节数 |
Socket.recvfrom_into(buffer[,nbytes[,flags]]) |
获取数据,返回一个二元组(bytes,address)到buffer中 |
Socket.send(bytes[,flags]) |
TCP 发送数据 |
Socket.sendall(bytes[,flags]) |
TCP发送全部数据,成功返回None |
Socket.sendto(string[,flag],address) |
UDP发送数据 |
Socket.sendfile(file,offset=0,count=None) |
发送一个文件直到EOF,使用高性能的os.sendfile机制,返回发送的字节数,如果win下不支持sendfile,或者不是普通文件,使用send()发送文件,offset告诉其实位置,3.5版本开始。 |
Makefile
Socket.makefile(mode=’r’,buffering=None,*,encoding=None,errors=None,newline=None)创建一个与该套接字相关连的文件对象,将recv方法看做读方法,将send方法看做是写方法。
异常不捕获会导致当前线程异常退出,不捕获直接到最外层,也就是主线程。
3、客户端tcp编程
import socket raddr = (‘127.0.0.1‘,999) client = socket.socket() client.connect(raddr) while True: data = client.recv(1024) print(data) if data.strip() == b‘quit‘: break client.send(b‘ack‘) client.close()
import threading import socket import logging import datetime FORMAT = ‘%(asctime)s %(threadName)s %(thread)d %(message)s‘ logging.basicConfig(format=FORMAT,level=logging.INFO) class ChatClient: def __init__(self,ip=‘127.0.0.1‘,port=8080): self.clients = socket.socket() self.raddr = (ip,port) self.event = threading.Event() def start(self): self.clients.connect(self.raddr) self.send(‘I am ok‘) threading.Thread(target=self.recive,name=‘receive‘).start() def recive(self): while not self.event.is_set(): data = self.clients.recv(1024) # logging.info(data) if data.strip() == b‘quit‘: break message = ‘{:%Y/%m/%d %H:%M:%S}{}:{}\n{}\n‘.format(datetime.datetime.now(),*self.raddr,data.strip()) logging.info(message) def send(self,message:str): data = ‘{}\n‘.format(message.strip()).encode() self.clients.send(data) def stop(self): self.event.set() self.clients.close() def main(): cc = ChatClient() cc.start() while True: cmd = input(‘>>>>‘) if cmd.strip() == ‘quit‘: cc.stop() break cc.send(cmd) logging.info(threading.enumerate()) if __name__ == ‘__main__‘: main()
4、udp编程
同一个协议下绑定同一个端口,才会有端口冲突。Udp不会真的连接。
Import socket
Server=socket.socket(type=)
Server.bind(laddr)绑定本地自己用的。
Server.recv(1024)
Data ,raddr = Server.recvfrom(1024)
Server.sendto(b’back’,raddr);后面的ip可以是不存在的,都会发送出去的。
Server.connect(raddr)后面才可以使用send。一般都是客户端向服务端连接用的。
服务器端代码:
import socket import threading import logging FORMAT = ‘%(asctime)s %(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.addr = (ip,port) self.sockets = socket.socket(type=socket.SOCK_DGRAM) self.event = threading.Event() def start(self): self.sockets.bind(self.addr) threading.Thread(target=self.recv,name=‘recv‘).start() def recv(self): while not self.event.is_set(): data,laddr = self.sockets.recvfrom(1024) logging.info(data) logging.info(laddr) msg = ‘ack.{}from{}{}‘.format(data.decode(),*laddr) masg1 = msg.encode() logging.info(msg) self.sockets.sendto(masg1,laddr) def stop(self): self.sockets.close() self.event.set() def main(): cs = ChatServer() cs.start() while True: cmd = input(‘>>>>‘) if cmd.strip() == ‘quit‘: cs.stop() break logging.info(threading.enumerate()) if __name__ == ‘__main__‘: main()
客户端代码:
import threading import socket import logging import datetime FORMAT = ‘%(asctime)s %(threadName)s %(thread)d %(message)s‘ logging.basicConfig(format=FORMAT,level=logging.INFO) class ChatUdpClient: def __init__(self,ip=‘127.0.0.1‘,port=8080): self.addr = (ip,port) self.cucsocket = socket.socket(type=socket.SOCK_DGRAM) self.event = threading.Event() def start(self): self.cucsocket.connect(self.addr) threading.Thread(target=self.recive,name=‘recive‘).start() def recive(self): while not self.event.is_set(): data,raddr =self.cucsocket.recvfrom(1024) logging.info(data) logging.info(raddr) message = ‘{}from{}{}‘.format(data.decode(),*raddr) def send(self,message:str): self.cucsocket.sendto(message.encode(), self.addr) def stop(self): self.cucsocket.close() self.event.set() def main(): cuc = ChatUdpClient() cuc.start() while True: cmd = input(‘>>>‘) if cmd.strip() == ‘quit‘: cuc.stop() break cuc.send(cmd) logging.info(threading.enumerate()) if __name__ == ‘__main__‘: main()
ack机制和心跳heartbeat。
心跳机制:
1)一般来说客户端定时发往服务器端,服务器端并不需要ack回复客户端,只是需要记录客户端活着就可以了。(严格考虑时间的问题)
2)服务器端定时发往客户端,一般需要客户端ack响应来表示活着,如果没有收到ack的客户端,服务端移除其信息,这种实现复杂,用的较少。
3)也可以是双向都发心跳包的,用的情况下较少。
为True的时候就不进入循环了。
心跳包客户端代码:
import threading import socket import logging import datetime FORMAT = ‘%(asctime)s %(threadName)s %(thread)d %(message)s‘ logging.basicConfig(format=FORMAT,level=logging.INFO) class ChatUdpClient: def __init__(self,ip=‘127.0.0.1‘,port=8080): self.addr = (ip,port) self.cucsocket = socket.socket(type=socket.SOCK_DGRAM) self.event = threading.Event() def start(self): self.cucsocket.connect(self.addr) threading.Thread(target=self.sen_hb,name=‘hb‘).start() threading.Thread(target=self.recive,name=‘recive‘).start() def recive(self): while not self.event.is_set(): data,raddr =self.cucsocket.recvfrom(1024) logging.info(data) logging.info(raddr) message = ‘{}from{}{}‘.format(data.decode(),*raddr) def send(self,message:str): self.cucsocket.sendto(message.encode(), self.addr) def sen_hb(self): self.send(‘hb‘) def stop(self): self.cucsocket.close() self.event.set() def main(): cuc = ChatUdpClient() cuc.start() while True: cmd = input(‘>>>‘) if cmd.strip() == ‘quit‘: cuc.stop() break cuc.send(cmd) logging.info(threading.enumerate()) if __name__ == ‘__main__‘: main()
心跳包服务器端代码:
import threading import socket import logging import datetime FORMAT = ‘%(asctime)s %(threadName)s %(thread)d %(message)s‘ logging.basicConfig(format=FORMAT,level=logging.INFO) class ChatUdpServer: def __init__(self,ip=‘127.0.0.1‘,port=8080,interval=10): self.addr = (ip,port) self.udpsocket = socket.socket(type=socket.SOCK_DGRAM) self.event = threading.Event() self.interval = interval self.clients = {} def start(self): self.udpsocket.bind(self.addr) threading.Thread(target=self.revice,name=‘recive‘).start() def revice(self): while not self.event.is_set(): lset = set() data,raddr = self.udpsocket.recvfrom(1024) logging.info(data) logging.info(raddr) current = datetime.datetime.now().timestamp() if data.strip() == b‘hb‘: self.clients[raddr]=current continue elif data.strip() == b‘quit‘: #有可能发出来的数据不在clients。 self.clients.pop(raddr,None) logging.info(‘{}leaving‘.format(raddr)) continue self.clients[raddr] = current message = ‘{}form{}{}‘.format(data.decode(),*raddr) for c ,stamp in self.clients.items(): if current - stamp >self.interval: lset.add(c) else: self.udpsocket.sendto(message.encode(), raddr) for c in lset: self.clients.pop(c) def stop(self): self.event.set() self.udpsocket.close() def main(): cus = ChatUdpServer() cus.start() while True: cmd = input(‘>>>>‘) if cmd == ‘quit‘: cus.stop() break logging.info(threading.enumerate()) if __name__ == ‘__main__‘: main()
正在迭代字典的时候不能进行pop。。
将其添加到set中,之后再进行pop。
Udp协议应用:
是无连接协议,基于以下假设:网络足够好 消息不会丢包,包不会乱序。
但是,即使在局域网,也不能保证不丢包,而且包到达不一定有序。
应用场景在视频、音频传输,一般来说,丢些包,问题不大,最多丢图像,听不清话语,可以重新发话语来解决,海量采集数据,例如传感器发来的数据,丢十几,几百条没有太大问题,DNS协议,数据内容小,一个包能 查询到结果,不存在乱序,丢包,重新请求解析。
Udp性能优于tcp,但是可靠性场所适用于tcp。
Udp广域网。
二、Socketserver
Socket编程过于底层,Python中对api进行封装的就是socketserver模块,是网络编程框架,便于企业快速开发;
类的继承关系
Socketserver简化了网络服务器的编写。
有四个同步类:TCPserver,UDPserver,Unixstreamserver,Unixdatagramserver
2个Mixin类:ForkingMixin和threadingMixin类,用来支持异步
Class forKingUDPserver(forKingMixin,UDPserver):pass
Class forKingTCPserver(forKingMixin,TCPPserver):pass
Class ThreadingUDPserver(ThreadingMixin,UDPserver):pass
Class ThreadingTCPserver(ThreadingMixin,TCPserver):pass
class BaseServer:
def __init__(self, server_address, RequestHandlerClass): """Constructor. May be extended, do not override.""" self.server_address = server_address self.RequestHandlerClass = RequestHandlerClass self.__is_shut_down = threading.Event() self.__shutdown_request = False
def finish_request(self, request, client_address): """Finish one request by instantiating RequestHandlerClass.""" self.RequestHandlerClass(request, client_address, self)
baserequesthandler类:
他是和用户连接的用户请求处理类的基类,定义为baserequesthandler(request,client_address,server)
服务器端server实例接受用户的请求偶,最后会实例化这个类。
被初始化以后,送入三个构造参数,request,client_address,server 本身。
以后就可以在baserequesthandler类的实例上使用以下属性:
Self.request是和客户端的连接的socket对象,
Self.server是TCPserver本身
Self.client_address是客户端地址。
这个类在初始化的过程中,会依次调用3个方法,子类可以覆盖这些方法
class BaseRequestHandler: """Base class for request handler classes. This class is instantiated for each request to be handled. The constructor sets the instance variables request, client_address and server, and then calls the handle() method. To implement a specific service, all you need to do is to derive a class which defines a handle() method. The handle() method can find the request as self.request, the client address as self.client_address, and the server (in case it needs access to per-server information) as self.server. Since a separate instance is created for each request, the handle() method can define other arbitrary instance variables. """ def __init__(self, request, client_address, server): self.request = request self.client_address = client_address self.server = server self.setup() try: self.handle() finally: self.finish() def setup(self): pass def handle(self): pass def finish(self): pass
import threading import socketserver class Myhandler(socketserver.BaseRequestHandler): def handle(self): print(‘--------‘) print(self.server) print(self.request) print(self.client_address) print(self.__dict__) print(self.server.__dict__) print(threading.enumerate()) print(threading.current_thread()) addr = (‘127.0.0.1‘,8080) server = socketserver.ThreadingTCPServer(addr,Myhandler) server.serve_forever()
import threading import socketserver import logging FORMAT = ‘%(asctime)s %(threadName)s %(thread)d %(message)s‘ logging.basicConfig(format=FORMAT,level=logging.INFO) class Myhandler(socketserver.BaseRequestHandler): def handle(self): print(‘--------‘) print(self.server) print(self.request) print(self.client_address) print(self.__dict__) print(self.server.__dict__) print(threading.enumerate()) print(threading.current_thread()) for i in range(2): data = self.request.recv(1024) logging.info(data) addr = (‘127.0.0.1‘,8080) server = socketserver.ThreadingTCPServer(addr,Myhandler) server.serve_forever()
类命名:
编程接口:
Socketserver.baseserver(server_address,RequestHandlerclass)
需要提供服务器绑定的地址信息,和用于请求处理请求的requesthandlerclass类。
Requesthandlerclass类必须是baserequesthandler类的子类,在baseserver中代码如下:
创建、传端口、handler。
ThreadingTCPServer。多线程,异步的,同时处理多个连接,
TCPServer TCP的,串行的。同步的,一个处理完毕后,才能处理下一个。只有主线程。
创建服务器需要几个步骤:
1)从baseRequestHandler类派生出子类,并覆盖其handler()方法来创建请求处理程序类,此方法将处理传入处理。
2)实例化一个服务器类,传参服务器的地址和请求处理类。
3)调用服务器实例的handle_request()或server_forever()方法。
4)调用server_close()关闭套接字。
实现echoserver
import threading import socketserver import logging import sys FORMAT = ‘%(asctime)s %(threadName)s %(thread)d %(message)s‘ logging.basicConfig(format=FORMAT,level=logging.INFO) class EchoHandler(socketserver.BaseRequestHandler): clients = {} def setup(self): self.event = threading.Event() self.clients[self.client_address] = self.request def finish(self): self.event.set() def handle(self): while not self.event.is_set(): data = self.request.recv(1024) logging.info(data) if data == b‘‘ or data ==‘quit‘: break msg = ‘{}‘.format(data.decode()) for c in self.clients.values(): c.send(msg.encode()) addr = (‘127.0.0.1‘,8080) server = socketserver.ThreadingTCPServer(addr,EchoHandler) # server.serve_forever() t = threading.Thread(target=server.serve_forever,name=‘encho‘) t.start() if __name__ == ‘__main__‘: try: while True: cmd = input(‘>>>‘) if cmd.strip() == ‘quit‘: server.server_close() break logging.info(threading.enumerate()) except Exception as e: logging.info(e) finally: print(‘exit‘) sys.exit(0)
解决客户端主动断开连接服务器端报错的方式:客户端主动断开,会导致recv方法会立即返回一个空bytes,并没有同事抛出异常,当循环到recv这一句的时候就会抛出异常,所以,可以通过判断data数据是否为空客户端是否断开。
总结:
为每一个连接提供requesthandlerclass类实例,依次调用setup、handler、finish方法,且使用了try..finally结构,保证finish方法一定被调用、这些方法一次执行完毕,如果想维持这个连接和客户端通信,就需要在handler函数中循环。
所持socketserver模块提供不同的类,但是编程接口一样的,即使是多进程、多线程的类也是一样,大大减少了编程的难度。
原文地址:https://www.cnblogs.com/wangchunli-blogs/p/9949903.html