python-Socket网络编程

1.类的特殊成员方法

1.1__doc__ 表示类的描述信息

1.2__module__和__class__

__module__表示当前操作的对象在那个模块

__class__表示当前操作的对象所属类

1.3__init__构造函数,通过类创建对象时,自动触发

1.4__del__析构方法,函数执行完成时,自动触发。普通情况下不需要手动触发

1.5__call__对象加括号时,自动触发

注意:构造方法的执行时由创建对象触发的,即:对象=类名;对于__call__方法的执行

是由对象后加括号触发即:对象()或者类()()

1.6__dict__查看类或者对象的成员属性

1.7__str__如果一个类中定义了__str__方法,输出对象是自动触发其返回值

 1.8__getitem__,__setitem__,__delitem__

用于索引操作,比如字典,以上分别为获取,设置,删除

 

2.创建类两种方式

#type第一个参数:类名

#type第二个参数:当前类的基类

#type第三个参数:类的成员

 类的生成 调用 顺序依次是 __new__ --> __call__ --> __init__

 3.反射

通过字符串或者修改程序运行状态,属性,方法有以下四种:

hasattr判断属性是否存在

getattr获取属性

setattr设置属性

delattr删除属性

 

 字符串形式导入模块

import importlib
 
__import__('import_lib.metaclass') #这是解释器自己内部用的
#importlib.import_module('import_lib.metaclass') #与上面这句效果一样,官方建议用这个

4.异常处理

 4.1 isinstance(obj,class)

判断对象是否属于这个类的实例化对象

4.2 issubclass(sub,Foo)

判断一个类是否是另外一个类的子类(派生类)

4.3异常种类

复制代码
AttributeError 试图访问一个对象没有的树形,比如foo.x,但是foo没有属性x
IOError 输入/输出异常;基本上是无法打开文件
ImportError 无法引入模块或包;基本上是路径问题或名称错误
IndentationError 语法错误(的子类) ;代码没有正确对齐
IndexError 下标索引超出序列边界,比如当x只有三个元素,却试图访问x[5]
KeyError 试图访问字典里不存在的键
KeyboardInterrupt Ctrl+C被按下
NameError 使用一个还未被赋予对象的变量
SyntaxError Python代码非法,代码不能编译(个人认为这是语法错误,写错了)
TypeError 传入对象类型与要求的不符合
UnboundLocalError 试图访问一个还未被设置的局部变量,基本上是由于另有一个同名的全局变量,
导致你以为正在访问它
ValueError 传入一个调用者不期望的值,即使值的类型是正确的
复制代码
 更多异常种类

ArithmeticError
AssertionError
AttributeError
BaseException
BufferError
BytesWarning
DeprecationWarning
EnvironmentError
EOFError
Exception
FloatingPointError
FutureWarning
GeneratorExit
ImportError
ImportWarning
IndentationError
IndexError
IOError
KeyboardInterrupt
KeyError
LookupError
MemoryError
NameError
NotImplementedError
OSError
OverflowError
PendingDeprecationWarning
ReferenceError
RuntimeError
RuntimeWarning
StandardError
StopIteration
SyntaxError
SyntaxWarning
SystemError
SystemExit
TabError
TypeError
UnboundLocalError
UnicodeDecodeError
UnicodeEncodeError
UnicodeError
UnicodeTranslateError
UnicodeWarning
UserWarning
ValueError
Warning
ZeroDivisionError

更多异常种类

 

4.4捕获所有异常

try:
    pass
except Exception as e:#捕获所有异常
    print(e)

4.5异常完整结构

复制代码
try:
    pass#主代码块
except Exception as e:#捕捉所有异常(存在异常,自动触发)
    print(e)#输出异常
else:
    pass#主代码块执行完成后执行该代码块
finally:
    pass#最终都会执行此代码块
复制代码

4.6主动触发异常

try:
    raise Exception('主动触发异常,抛出错误信息')
except Exception as e:#捕捉所有异常(存在异常,自动触发)
    print(e)#输出异常
finally:
    pass#最终都会执行此代码块

4.7自定义异常

复制代码
class ERROR_TYPE(Exception):#继承全异常类
    def __init__(self,msg):
        self.msg=msg
    def __str__(self):
        return self.msg

try:
    raise ERROR_TYPE('自定义异常类,主动抛出异常信息')
except ERROR_TYPE as e:
    print(e)
复制代码

4.8断言

提示信息。提示代码必须满足该条件

注意:断言条件不成立,不执行后面代码(抛出异常)

但是抛出异常,Exception无法捕捉到

5.网络编程

5.1TCP/IP协议图解

 5.2 socket

socket通常也称作"套接字",用于描述IP地址和端口,是一个通信链的句柄,应用程序通常通过"套接字"向网络发出请求或者应答网络请求。

socket起源于Unix,而Unix/Linux基本哲学之一就是“一切皆文件”,对于文件用【打开】【读写】【关闭】模式来操作。socket就是该模式的一个实现,socket即是一种特殊的文件,一些socket函数就是对其进行的操作(读/写IO、打开、关闭)

socket和file的区别:

  • file模块是针对某个指定文件进行【打开】【读写】【关闭】
  • socket模块是针对 服务器端 和 客户端Socket 进行【打开】【读写】【关闭】

 

 socket server

#!/usr/bin/env python3
# -*- coding:utf-8 -*-

import socket

ip_port = ('127.0.0.1',9999)#设置ip及端口

sk = socket.socket()#创建socket对象
sk.bind(ip_port)#绑定ip及端口
sk.listen(5)#监听5代表连接数

while True:
print('server waiting...')
conn,addr = sk.accept()#接收连接并返回(conn,address)

client_data = conn.recv(1024)#接收信息
print(client_data)
conn.sendall('不要回答,不要回答,不要回答')

conn.close()

socket server

 socket client

#!/usr/bin/env python3
# -*- coding:utf-8 -*-
import socket#导入socket模块
ip_port = ('127.0.0.1',9999)#定义ip及端口

sk = socket.socket()#创建socket对象
sk.connect(ip_port)#创建连接

sk.sendall('请求占领地球')#发送消息

server_reply = sk.recv(1024)#接收信息
print(server_reply)

sk.close()

socket client

5.3socket属性

sk = socket.socket(socket.AF_INET,socket.SOCK_STREAM,0)

复制代码
参数一:地址簇

  socket.AF_INET IPv4(默认)
  socket.AF_INET6 IPv6

  socket.AF_UNIX 只能够用于单一的Unix系统进程间通信

参数二:类型

  socket.SOCK_STREAM  流式socket , for TCP (默认)
  socket.SOCK_DGRAM   数据报式socket , for UDP

  socket.SOCK_RAW 原始套接字,普通的套接字无法处理ICMP、IGMP等网络报文,而SOCK_RAW可以;其次,SOCK_RAW也可以处理特殊的IPv4报文;此外,利用原始套接字,可以通过IP_HDRINCL套接字选项由用户构造IP头。
  socket.SOCK_RDM 是一种可靠的UDP形式,即保证交付数据报但不保证顺序。SOCK_RAM用来提供对原始协议的低级访问,在需要执行某些特殊操作时使用,如发送ICMP报文。SOCK_RAM通常仅限于高级用户或管理员运行的程序使用。
  socket.SOCK_SEQPACKET 可靠的连续数据包服务

参数三:协议

  0  (默认)与特定的地址家族相关的协议,如果是 0 ,则系统就会根据地址格式和套接类别,自动选择一个合适的协议
复制代码
复制代码
import socket
ip_port = ('127.0.0.1',9999)
sk = socket.socket(socket.AF_INET,socket.SOCK_DGRAM,0)
sk.bind(ip_port)

while True:
    data = sk.recv(1024)
    print data

import socket ip_port = ('127.0.0.1',9999) sk = socket.socket(socket.AF_INET,socket.SOCK_DGRAM,0) while True: inp = raw_input('数据:').strip() if inp == 'exit': break sk.sendto(inp,ip_port) sk.close()
复制代码

sk.bind(address)

  将套接字绑定到指定地址,address地址的格式取决于地址簇。在AF_INET下,以元祖(host,port)的形式表示地址

 

sk.listen(backlog)

  开始监听传入连接。backlog指定在拒绝连接之前,可以挂起的最大连接数量。backlog等于5,表示内核已经连接到请求,但是服务器还是没有调用acept进行处理的连接数最大为5

 

sk.setblocking(bool)

  是否阻塞(默认TRUE),如果设置FLASE,那么accept和recv时一旦无数据则报错

 

sk.accept()

  接收连接并返回(conn,address),其中conn是新的套接字对象,可以应来接收和发送数据,address是连接客户端地址,接收TCP客户连接(阻塞式)等待客户端连接

 

sk.connect(address)

  连接到address处的套接字,一般address格式为元祖(ip,port),如果连接出错,返回socket.error错误

 

sk.connet_ex(address)

  作用同上,只不过会有返回值,连接成功时返回0,连接失败时候返回编码,例如:10061

 

sk.close()

  关闭套接字

 

sk.recv(bufsize[,flag])

  接收套接字数据,数据以字符串形式返回,bufsize指导最多可以接收的长度,flag提供有关消息的其他信息通常可以忽略

 

sk.recvfrom(bufsize[,flag])

  与recv()类似,但返回值是(data,address)。其中data是否包含接收数据的字符串,address是发送数据的套接字地址

 

sk.send(string[,flag])

  将string中的数据发送到连接套接字,返回值是要发送的字节长度,该长度小于string的字节大小,即:可能未将指定内容全部发送。

 

sk.sendall(string[,flag])

  将string中的数据发送到连接的套接字,但在返回之前会发送所有数据。成功返回None,失败则抛出异常,内部通过递归调用send,将所有内容发送出去

 

sk.sendto(string[,flag],address)

  将数据发送到套接字,address是形式为(ipaddr,port)的元祖,指定远程地址,返回值是发送的字节数,该函数主要用于UDP协议

 

sk.settimeout(timeout)

  设置套接字操作超时时间,timeout是一个浮点数,单位是秒,值为None表示没有超时时间,一般,超时时间应该在刚创建套接字是设置,因为它们可能用于连接操作(如client连接最多等待5s)

 

sk.getpeername()

  返回连接套接字的远程地址,返回值通常是元祖(ip,port)

 

sk.getsockname()

  返回连接套接字自己的地址,返回值通常是元祖(ip,port)

 

sk.fileno()

  套接字文件描述符

复制代码
# 服务端
import socket
ip_port = ('127.0.0.1',9999)
sk = socket.socket(socket.AF_INET,socket.SOCK_DGRAM,0)
sk.bind(ip_port)

while True:
    data,(host,port) = sk.recvfrom(1024)
    print(data,host,port)
    sk.sendto(bytes('ok', encoding='utf-8'), (host,port))


#客户端
import socket
ip_port = ('127.0.0.1',9999)

sk = socket.socket(socket.AF_INET,socket.SOCK_DGRAM,0)
while True:
    inp = input('数据:').strip()
    if inp == 'exit':
        break
    sk.sendto(bytes(inp, encoding='utf-8'),ip_port)
    data = sk.recvfrom(1024)
    print(data)

sk.close()
复制代码
 利用select监听终端操作实例

#!/usr/bin/env python3
# -*- coding:utf-8 -*-

import select
import threading
import sys

while True:
readable, writeable, error = select.select([sys.stdin,],[],[],1)
if sys.stdin in readable:
print 'select get stdin',sys.stdin.readline()

利用select监听终端操作实例

 利用select实现伪同时处理多个Socket客户端请求:服务端

#!/usr/bin/env python3
# -*- coding:utf-8 -*-

import socket
import select

sk1 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sk1.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sk1.bind(('127.0.0.1',8002))
sk1.listen(5)
sk1.setblocking(0)

inputs = [sk1,]

while True:
readable_list, writeable_list, error_list = select.select(inputs, [], inputs, 1)
for r in readable_list:
# 当客户端第一次连接服务端时
if sk1 == r:
print 'accept'
request, address = r.accept()
request.setblocking(0)
inputs.append(request)
# 当客户端连接上服务端之后,再次发送数据时
else:
received = r.recv(1024)
# 当正常接收客户端发送的数据时
if received:
print 'received data:', received
# 当客户端关闭程序时
else:
inputs.remove(r)

sk1.close()

利用select实现伪同时处理多个Socket客户端请求:服务端

 利用select实现伪同时处理多个Socket客户端请求:客户端

#!/usr/bin/env python3
# -*- coding:utf-8 -*-
import socket

ip_port = ('127.0.0.1',8002)
sk = socket.socket()
sk.connect(ip_port)

while True:
inp = raw_input('please input:')
sk.sendall(inp)
sk.close()

利用select实现伪同时处理多个Socket客户端请求:客户端

此处的Socket服务端相比与原生的Socket,他支持当某一个请求不再发送数据时,服务器端不会等待而是可以去处理其他请求的数据。但是,如果每个请求的耗时比较长时,select版本的服务器端也无法完成同时操作

 基于select实现socket服务端

#!/usr/bin/env python3
#coding:utf8

'''
服务器的实现 采用select的方式
'''

import select
import socket
import sys
import Queue

#创建套接字并设置该套接字为非阻塞模式

server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
server.setblocking(0)

#绑定套接字
server_address = ('localhost',10000)
print >>sys.stderr,'starting up on %s port %s'% server_address
server.bind(server_address)

#将该socket变成服务模式
#backlog等于5,表示内核已经接到了连接请求,但服务器还没有调用accept进行处理的连接个数最大为5
#这个值不能无限大,因为要在内核中维护连接队列

server.listen(5)

#初始化读取数据的监听列表,最开始时希望从server这个套接字上读取数据
inputs = [server]

#初始化写入数据的监听列表,最开始并没有客户端连接进来,所以列表为空

outputs = []

#要发往客户端的数据
message_queues = {}
while inputs:
print >>sys.stderr,'waiting for the next event'
#调用select监听所有监听列表中的套接字,并将准备好的套接字加入到对应的列表中
readable,writable,exceptional = select.select(inputs,outputs,inputs)#列表中的socket 套接字 如果是文件呢?
#监控文件句柄有某一处发生了变化 可写 可读 异常属于Linux中的网络编程
#属于同步I/O操作,属于I/O复用模型的一种
#rlist--等待到准备好读
#wlist--等待到准备好写
#xlist--等待到一种异常
#处理可读取的套接字

'''
如果server这个套接字可读,则说明有新链接到来
此时在server套接字上调用accept,生成一个与客户端通讯的套接字
并将与客户端通讯的套接字加入inputs列表,下一次可以通过select检查连接是否可读
然后在发往客户端的缓冲中加入一项,键名为:与客户端通讯的套接字,键值为空队列
select系统调用是用来让我们的程序监视多个文件句柄(file descrīptor)的状态变化的。程序会停在select这里等待,
直到被监视的文件句柄有某一个或多个发生了状态改变
'''

'''
若可读的套接字不是server套接字,有两种情况:一种是有数据到来,另一种是链接断开
如果有数据到来,先接收数据,然后将收到的数据填入往客户端的缓存区中的对应位置,最后
将于客户端通讯的套接字加入到写数据的监听列表:
如果套接字可读.但没有接收到数据,则说明客户端已经断开。这时需要关闭与客户端连接的套接字
进行资源清理
'''

for s in readable:
if s is server:
connection,client_address = s.accept()
print >>sys.stderr,'connection from',client_address
connection.setblocking(0)#设置非阻塞
inputs.append(connection)
message_queues[connection] = Queue.Queue()
else:
data = s.recv(1024)
if data:
print >>sys.stderr,'received "%s" from %s'% \
(data,s.getpeername())
message_queues[s].put(data)
if s not in outputs:
outputs.append(s)
else:
print >>sys.stderr,'closing',client_address
if s in outputs:
outputs.remove(s)
inputs.remove(s)
s.close()
del message_queues[s]

#处理可写的套接字
'''
在发送缓冲区中取出响应的数据,发往客户端。
如果没有数据需要写,则将套接字从发送队列中移除,select中不再监视
'''

for s in writable:
try:
next_msg = message_queues[s].get_nowait()

except Queue.Empty:
print >>sys.stderr,' ',s,getpeername(),'queue empty'
outputs.remove(s)
else:
print >>sys.stderr,'sending "%s" to %s'% \
(next_msg,s.getpeername())
s.send(next_msg)

 

#处理异常情况

for s in exceptional:
for s in exceptional:
print >>sys.stderr,'exception condition on',s.getpeername()
inputs.remove(s)
if s in outputs:
outputs.remove(s)
s.close()
del message_queues[s]

基于select实现socket服务端

6.SocketServer模块

SocketServer内部使用 IO多路复用 以及 “多线程” 和 “多进程” ,从而实现并发处理多个客户端请求的Socket服务端。即:每个客户端请求连接到服务器时,Socket服务端都会在服务器是创建一个“线程”或者“进程” 专门负责处理当前客户端的所有请求。

ThreadingTCPServer

ThreadingTCPServer实现的Soket服务器内部会为每个client创建一个 “线程”,该线程用来和客户端进行交互。

1、ThreadingTCPServer基础

使用ThreadingTCPServer:

  • 创建一个继承自 SocketServer.BaseRequestHandler 的类
  • 类中必须定义一个名称为 handle 的方法
  • 启动ThreadingTCPServer

 

 SocketServer实现服务器

#!/usr/bin/env python3
# -*- coding:utf-8 -*-
import SocketServer

class MyServer(SocketServer.BaseRequestHandler):

def handle(self):
# print self.request,self.client_address,self.server
conn = self.request
conn.sendall('欢迎致电 10086,请输入1xxx,0转人工服务.')
Flag = True
while Flag:
data = conn.recv(1024)
if data == 'exit':
Flag = False
elif data == '0':
conn.sendall('通过可能会被录音.balabala一大推')
else:
conn.sendall('请重新输入.')


if __name__ == '__main__':
server = SocketServer.ThreadingTCPServer(('127.0.0.1',8009),MyServer)
server.serve_forever()

SocketServer实现服务器

 SocketServer实现客户端

#!/usr/bin/env python3
# -*- coding:utf-8 -*-

import socket


ip_port = ('127.0.0.1',8009)
sk = socket.socket()
sk.connect(ip_port)
sk.settimeout(5)

while True:
data = sk.recv(1024)
print 'receive:',data
inp = raw_input('please input:')
sk.sendall(inp)
if inp == 'exit':
break

sk.close()

SocketServer实现客户端

6.1ThreadingTCPServer源码剖析

 ThreadingTCPServer的类图关系如下:

 

内部调用流程为:

  • 启动服务端程序
  • 执行 TCPServer.__init__ 方法,创建服务端Socket对象并绑定 IP 和 端口
  • 执行 BaseServer.__init__ 方法,将自定义的继承自SocketServer.BaseRequestHandler 的类 MyRequestHandle赋值给self.RequestHandlerClass
  • 执行 BaseServer.server_forever 方法,While 循环一直监听是否有客户端请求到达 ...
  • 当客户端连接到达服务器
  • 执行 ThreadingMixIn.process_request 方法,创建一个 “线程” 用来处理请求
  • 执行 ThreadingMixIn.process_request_thread 方法
  • 执行 BaseServer.finish_request 方法,执行 self.RequestHandlerClass()  即:执行 自定义 MyRequestHandler 的构造方法(自动调用基类BaseRequestHandler的构造方法,在该构造方法中又会调用 MyRequestHandler的handle方法)

 6.2ForkingTCPServer

ForkingTCPServer和ThreadingTCPServer的使用和执行流程基本一致,只不过在内部分别为请求者建立 “线程”  和 “进程”。

 

 服务端

#!/usr/bin/env python3
# -*- coding:utf-8 -*-
import SocketServer

class MyServer(SocketServer.BaseRequestHandler):

def handle(self):
# print self.request,self.client_address,self.server
conn = self.request
conn.sendall('欢迎致电 10086,请输入1xxx,0转人工服务.')
Flag = True
while Flag:
data = conn.recv(1024)
if data == 'exit':
Flag = False
elif data == '0':
conn.sendall('通过可能会被录音.balabala一大推')
else:
conn.sendall('请重新输入.')


if __name__ == '__main__':
server = SocketServer.ForkingTCPServer(('127.0.0.1',8009),MyServer)
server.serve_forever()

服务端

 客户端

#!/usr/bin/env python3
# -*- coding:utf-8 -*-

import socket


ip_port = ('127.0.0.1',8009)
sk = socket.socket()
sk.connect(ip_port)
sk.settimeout(5)

while True:
data = sk.recv(1024)
print 'receive:',data
inp = raw_input('please input:')
sk.sendall(inp)
if inp == 'exit':
break

sk.close()

客户端

以上ForkingTCPServer只是将 ThreadingTCPServer 实例中的代码:

server = SocketServer.ThreadingTCPServer(('127.0.0.1',8009),MyRequestHandler)
变更为:
server = SocketServer.ForkingTCPServer(('127.0.0.1',8009),MyRequestHandler)

SocketServer的ThreadingTCPServer之所以可以同时处理请求得益于 select 和 os.fork 两个东西,其实本质上就是在服务器端为每一个客户端创建一个进程,当前新创建的进程用来处理对应客户端的请求,所以,可以支持同时n个客户端链接(长连接)。

 

参考链接:

http://www.cnblogs.com/wupeiqi/articles/5040823.html

http://www.cnblogs.com/wupeiqi/articles/5017742.html

https://twistedmatrix.com/trac
http://twistedmatrix.com/documents/current/api/

posted on 2017-12-26 22:51  快乐糖果屋  阅读(254)  评论(0编辑  收藏  举报

导航