python网络编程socket编程_python的网络编程(socket)

一、Socket介绍

Socket套接字,python中提供socket.py标准库,非常底层的接口库

Socket是一种通用的网络编程接口,和网络层次没有一一对应的关系

1、Socket类型

SOCK_STREAM :面向连接的流套接字,默认值,TCP协议

SOCK_DGRAM : 五连接的数据报文套接字,UDP协议

二、TCP编程

Socket编程,需要两端,一般来说需要一个服务端,一个客户端,服务端称为Sever,客户端称为lient

1、TCP服务端

服务器端编程步骤:

创建Socket对象

绑定IP地址Address和端口Port,bind()方法,IPv4地址为一个二元组('ip地址字符串',Port)

开始监听,将在指定的IP的端口上监听,listen()方法

获取用于传送数据的Socket对象:socket.accept() ->(socket object, address info)

accept方法阻塞等待客户端建立连接,返回一个新的Socket对象和客户端地址的二元组

地址是远程客户端的地址,IPv4中它是一个二元组(clientaddr,port)

s = socket.socket() #创建socket对象

s.bind(('127.0.0.1',8888)) # 一个二元组

s.listen() #开始监听

#开启一个连接

s1, info = s.accept() #阻塞直到客户端成功建立连接,返回一个socket对象和客户端地址

#使用缓冲区获取数据

data = s1.recv(1024)

print(data, info)

s1.send(b'magedu.com ack')

2、 写一个群聊程序

聊天工具是CS程序,C是每一个客户端,S是服务器端,服务器端应该具有的功能:

启动服务,包括绑定地址和端口,监听

建立连接,能和多个客户端建立连接,接收不同用户的信息

分发,将接收的某个用户的信息转发到已连接的所有客户端

停止服务,记录连接的客户端,服务器端应该对应一个类

举例

importlogging

importsocket

importthreading

importdatetime

FORAMT = '%(asctime)s %(thread)d %(message)s'logging.basicConfig(level=logging.INFO, format=FORAMT)

classChatSever:

def __init__(self, ip='127.0.0.1', port=8888): # 启动服务

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() # 监听

#accept会阻塞主线程,所以要开一个新线程

print('------------')

threading.Thread(target=self.accept).start()

def accept(self): # 多人连接

sock, client = self.sock.accept() # 阻塞

print(111,sock, client)

self.clients[client] = sock # 添加到客户端字典

#准备接收数据,recv是阻塞的,开启新的线程

threading.Thread(target=self.recv, args=(sock, client)).start()

def recv(self, sock:socket, client): # 接收客户端数据

while notself.event.is_set():

data = sock.recv(1024) #阻塞到数据到来

msg = "{:%Y/%m/%d %H:%M:%S} {} : {}\n{}\n".format(datetime.datetime.now(), *client, data.decode())

print('msg')

logging.info(msg)

for s inself.clients.values():

s.send(msg.encode())

def stop(self): #停止服务

for s inself.clients.values():

s.close()

self.sock.close()

self.event.set()

cs =ChatSever()

cs.start()

e =threading.Event()

while not e.wait(1):

cmd = input('>>>').strip()

if cmd == "quit":

cs.stop()

e.wait(3)

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

服务端知道自己何时断开,如果客户端断开,服务器不知道

所以,好的做法是,客户端开发出特殊消息通知服务器端断开连接

但是,如果客户端主动断开,服务端主动发送一个空消息,超时返回异常,捕获异常并清理连接

即使为客户端提供了断开命令,也不能保证客户端会使用它断开连接,但是还是要增加这个退出功能

为下面代码增加功能

增加客户端退出命令

增加多客户端支持

importlogging

importsocket

importthreading

importdatetime

FORAMT = '%(asctime)s %(thread)d %(message)s'logging.basicConfig(level=logging.INFO, format=FORAMT)

classChatSever:

def __init__(self, ip='127.0.0.1', port=8888): # 启动服务

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() # 监听

#accept会阻塞主线程,所以要开一个新线程

print('------------')

threading.Thread(target=self.accept).start()

def accept(self): # 多人连接

sock, client = self.sock.accept() # 阻塞

print(111,sock, client)

self.clients[client] = sock # 添加到客户端字典

#准备接收数据,recv是阻塞的,开启新的线程

threading.Thread(target=self.recv, args=(sock, client)).start()

def recv(self, sock:socket, client): # 接收客户端数据

while notself.event.is_set():

data = sock.recv(1024) #阻塞到数据到来

msg =data.decode().strip()

#客户端退出命令

if msg == "quit":

self.clients.pop(client)

sock.close()

logging.info("{} quits".format(client))

breakmsg = "{:%Y/%m/%d %H:%M:%S} {} : {}\n{}\n".format(datetime.datetime.now(), *client, data.decode())

print('msg')

logging.info(msg)

for s inself.clients.values():

s.send(msg.encode())

def stop(self): #停止服务

for s inself.clients.values():

s.close()

self.sock.close()

self.event.set()

cs =ChatSever()

cs.start()

e =threading.Event()

while not e.wait(1):

cmd = input('>>>').strip()

if cmd == "quit":

cs.stop()

e.wait(3)

break

3、其他方法

socket.recv() : 获取数据,默认是阻塞的方式

socket.recvfrom() : 获取数据,返回一个二元组

socket.recv_info() : 获取到nbytes的数据后,存储到buffer中

创建一个与该套接字相关连的文件对象

使用makefile改写群聊类

importlogging

importsocket

importthreading

importdatetime

FORAMT = '%(asctime)s %(thread)d %(message)s'logging.basicConfig(level=logging.INFO, format=FORAMT)

classChatSever:

def __init__(self, ip='127.0.0.1', port=8888): # 启动服务

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() # 监听

#accept会阻塞主线程,所以要开一个新线程

print('------------')

threading.Thread(target=self.accept).start()

def accept(self): # 多人连接

while notself.event.is_set():

sock, client = self.sock.accept() # 阻塞

print(111,sock, client)

f = sock.makefile(mode='rw')

self.clients[client] = f # 添加到客户端字典

#准备接收数据,recv是阻塞的,开启新的线程

threading.Thread(target=self.recv, args=(f, client), name='recv').start()

def recv(self, f, client): # 接收客户端数据

while notself.event.is_set():

try:

data = f.readline() # 阻塞到换行符

exceptException as e:

logging.error(e) #有任何异常保证退出

data = 'quit'msg =data.strip()

#客户端退出命令

if msg == "quit":

self.clients.pop(client)

f.close()

logging.info("{} quits".format(client))

breakmsg = "{:%Y/%m/%d %H:%M:%S} {} : {}\n{}\n".format(datetime.datetime.now(), *client, data)

print('msg')

logging.info(msg)

for s inself.clients.values():

s.wtitelines(msg)

s.flush()

def stop(self): #停止服务

for s inself.clients.values():

s.close()

self.sock.close()

self.event.wait(3)

self.event.set()

defshow_thread(e:threading.Event):

while not e.wait(3):

logging.info(threading.enumerate())

defmain():

e =threading.Event()

cs =ChatSever()

cs.start()

threading.Thread(target=show_thread, args=(e,), name='showthread').start()

while not e.wait(1):

cmd = input('>>>').strip()

if cmd == "quit":

cs.stop()

e.wait(3)

break

if __name__ == "__main__":

main()

Socket太底层了,实际开发中很少使用这么底层的接口

三、TCP客户端

客户端编程步骤

创建Socket对象

连接到远程服务端的ip和port,connect()

传输数据:使用send,recv方法,接收数据

关闭连接,释放资源

importlogging

importsocket

importthreading

importdatetime

FORAMT = '%(asctime)s %(thread)d %(message)s'logging.basicConfig(level=logging.INFO, format=FORAMT)

classChatClient:

def __init__(self, ip='127.0.0.1', port=8888):

self.sock =socket.socket()

self.addr =(ip, port)

self.event =threading.Event()

self.start()

def start(self): #启动对远端的连接

self.sock.connect(self.addr)

#准备接收数据,recv是阻塞的,开启新的线程

threading.Thread(target=self._recv, name="recv").start()

def _recv(self): #接收客户端数据

while notself.event.is_set():

try:

data = self.sock.recv(1024) #阻塞

exceptException as e:

logging.error(e)

breakmsg = "{:%Y/%m/%d %H:%M:%S} {} : {}\n{}\n".format(datetime.datetime.now(), *client, data)

logging.info(msg)

defsend(self, msg:str):

data = "{}\n".format(msg.strip()).encode()

self.sock.send(data)

defstop(self):

logging.info("{} broken".format(self.addr))

self.sock.close()

self.event.wait(3)

self.event.set()

logging.info("client stops")

defshow_thread(e:threading.Event):

while not e.wait(3):

logging.info(threading.enumerate())

defmain():

e =threading.Event()

cc =ChatClient()

whileTrue:

msg = input('>>>')

if msg.strip() == "quit":

cs.stop()

breakcc.send(msg)

if __name__ == '__main__':

main()

四、Socketserver

socket编程过于底层,编程虽然有套路,但是想要写出健壮的代码还是比较困难的,所以很多语言都对socket底层API进行封装,Python的封装就是socketserver模块

网络服务编程框架,便于企业级快速开发

1、类的继承关系

SocketSever简化了网络服务器的编写

它有4个同步类:TCPServer,UDPServer,UnixStreamServer,UnixDatagramServer

2个Mixin类ForkingMixln和ThreadMixln类来支持异步

2、编程接口

socketserver.BaseServer(sever_address, RequestHandleClass)

需要提供服务器绑定的地址信息,和用于处理请求的RequestHandlerClass类,RequestHandleClass类必须是BaseRequestHandler类的子类

3、BaseRequestHandler类

和用户连接的用户请求处理类,Server实例接收用户请求后,最后会实例化这个类;它被初始化时,送入3个构造参数:request,client_address,server

以后可以在BaseRequestHandler类的实例上通过

self.request是客户端的连接的socket对象

self.server是TCPserver本身

self.client_address是客户端地址

它会一次调用3个函数,子类可以覆盖

importthreading

importsocketserver

classMyHandler(socketserver.BaseRequestHandler):

defhandle(self):

print(self.server, self.request, self.client_address)

print('{} handler'.format(self.__class__))

print(self.__class__)

print(type(self).__dict__)

print(self.__class__.__bases__[0].__dict__)

print(threading.enumerate(), threading.current_thread())

addr = ('127.0.0.1', 8888)

server =socketserver.ThreadingTCPServer(addr, MyHandler)

server.serve_forever()

测试结果说明,handler方法和socket的accept对应,用户连接请求过来后,建立连接并生成一个

socket对象保存在self.request中,客户端地址保存在self.client_address中,

4、创建服务器需要几个步骤

必须通过BaseRequestHandler类进行子类化并覆盖其他handle()方法来创建请求处理程序类,此方法将处理传入请求

必须实例化一个服务器类,将它传递给服务器的地址和请求处理程序类

然后调用服务器对象的handle_request()或server_forever()方法

调用server_close()关闭套接字,shutdown()方法,等待停止server_forever()

5、实现EchoServer

顾名思义,Echo来什么消息回显什么消息,客户端发来什么信息,返回什么信息

importthreading

importsocketserver

classEchoHandler(socketserver.BaseRequestHandler):

deffinish(self):

super().finish()

self.event.set() # 清理工作

defsetup(self):

super().setup()

self.event = threading.Event() # 初始化工作

defhandle(self):

super().handle()

while notself.event.is_set():

data = self.request.recv(1024).decode()

msg = "{} {}".format(self.client_address, data).encode()

self.request.send(msg)

print('End')

addr = ('127.0.0.1', 8888)

server =socketserver.ThreadingTCPServer(addr, EchoHandler)

server_thread = threading.Thread(target=server.serve_forever, daemon=True)

server_thread.start()

try:

whileTrue:

cmd = input('>>>')

if cmd.strip() == 'quit':

break

exceptException as e:

print(e)

exceptKeyboardInterrupt:

print('Exit')

finally:

server.shutdown()

server.server_close()

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值