python之socket
一、初识socket
socket 是网络连接端点,每个socket都被绑定到一个特定的IP地址和端口。IP地址是一个由4个数组成的序列,这4个数均是范围 0~255中的值(例如,220,176,36,76);端口数值的取值范围是0~65535。端口数小于1024的都是为众所周知的网络服务所保留的 (例如Web服务使用的80端口);最大的保留数被存储在socket模块的IPPORT_RESERVED变量中。你也可以为你的程序使用另外的端口数 值。
不是所有的IP地址都对世界的其它地方可见。实际上,一些是专门为那些非公共的地址所保留的(比如形如192.168.y.z或10.x.y.z)。地址127.0.0.1是本机地址;它始终指向当前的计算机。程序可以使用这个地址来连接运行在同一计算机上的其它程序。
IP地址不好记,你可以花点钱为特定的IP地址注册一个主机名或域名(比如使用www.jb51.net代替222.76.216.16)。域名服务器(DNS)处理名字到IP地址的映射。
多少信息通过一个网络被传送基于许多因素,其中之一就是使用的协议。许多的协议是基于简单
的、低级协议以形成一个协议栈。例如HTTP协议,它是用在Web浏览器与Web服务器之间通信的协议,它是基于TCP协议,而TCP协议又基于IP协议。
当 在你自己的两个程序间传送信息的时候,你通常选择TCP或UDP协议。
TCP协议在两端间建立一个持续的连接,并且你所发送的信息有保证的按顺序到达它们 的目的地。
UDP不建立连接,它的速度快但不可靠。你发送的信息也可能到不了另一端;或它们没有按顺序到达。有时候一个信息的多个复制到达接收端,即使你只发送了一次。
二、使用地址和主机名
socket模块提供了几个函数用于使用主机名和地址来工作。
socket 也定义了一些变量来代表保留的IP地址。
INADDR_ANY和INADDR_BROADCAST是被保留的IP地址分别代表任意IP地址和广播地 址;
INADDR_LOOPBACK 代表loopback设备,总是地址127.0.0.1。这些变量是32位字节数字形式的。
getfqdn([name])函数返回关于给定主机名的全域名(如果省略,则返回本机的全域名)。
server.py
import socket
ip_port=('127.0.0.1',5555)
s=socket.socket()
s.bind(ip_port)
s.listen(5)
conn,addr=s.accept()
while True:
try:
recv_data=conn.recv(1024)
if str(recv_data,encoding='utf-8')=='exit':break
print(str(recv_data,encoding='utf8'))
send_data=recv_data.upper()
conn.send(send_data)
#如果客户端断开连接,服务器会抛出异常,自动停止
except Exception as ex:
break
conn.close()
client.py
import socket
ip_port=('127.0.0.1',5555)
s=socket.socket()
s.connect(ip_port)
while True:
data=input('>>').strip()
if len(data)==0:continue
#如果直接输入空格或者回车,直接会卡住,因为服务器方面recv不会接受空值,会导致阻塞
s.send(bytes(data,encoding='utf8'))
if data=='exit':break
recv_data=s.recv(1024)
print(str(recv_data,encoding='utf8'))
s.close()
三、使用低级的socket通信
尽管Python提供了一些封装,使得使用socket更容易,但是你也可以直接使用socket来工作。
1、创建和销毁socket
socket 模块中的socket(family,type[,proto])函数创建一个新的socket对象。family的取值通常是AF_INET。type 的取值通常是SOCK_STREAM(用于定向的连接,可靠的TCP连接)或SOCK_DGRAM(用于UDP):
>>> from socket import *
>>> s=socket(AF_INET,SOCK_STREAM)
family和type参数暗指了一个协议,但是你可以使用socket的第三个可选的参数(proto的取值如IPPROTO_TCP或IPPROTO_RAW)来指定所使用的协议。代替使用IPPROTO_XX变量,你可以使用函数
getprotobyname:
>>> getprotobyname('tcp')
6
>>> IPPROTO_TCP
6
fromfd(fd,type[,proto]) 是一个很少被使用的函数,它用来从打开的一个文件描述符创建一个socket对象(文件描述符由文件的fileno()方法返回)。文件描述符与一个真实 的socket连接,而非一个文件。socket对象的fileno()方法返回关于这个socket的文件描述符。
当你使用完工 socket对象时,你应调用close()方法显式的关闭socket以尽快释放资源(尽管socket被垃圾回收器回收时将自动被关闭)。另外,你也
可以使用shutdown(how)方法来关闭连接一边或两边。参数0阻止socket接收数据,1阻止发送,2阻止接收和发送。
2、连接socket
当 两个socket连接时(例如使用TCP),一端监听和接收进来的连接,而另一端发起连接。监听端创建一个socket,调用bind(address) 函数去绑定一个特定的地址和端口,调用listen(backlog)来临听进来的连接,最后调用accept()来接收这个新的,进来的连接,下面是在 服务器端的代码:
>>> s=socket(AF_INET,SOCK_STREAM)
>>> s.bind(('127.0.0.1',44444))
>>> s.listen(1)
>>> q,v=s.accept() #返回socket q和地址v
注意:上面的代码将一直处于等待直到连接被建立。下面我们再打开另一个Python解释器,用作客户端;然后键入如下代码:
>>> from socket import *
>>> s=socket(AF_INET,SOCK_STREAM)
>>> s.connect(('127.0.0.1',44444) #发起连接
好了,我们验证一下连接是否建立了。我们在服务器端键入以下代码来发送一条信息:
>>> q.send('hello,i come from pythontik.com')
注:有时可能出现send() argument 1 must be string or buffer,not str 错误,原因可能是您的机器不支持UTF-8字符集,临时解决方案是q.send(b' hello...')
31 #发送的字节数
在客户端键入以下代码来接收信息:
>>> s.recv(1024)
'hello,i come from pythontik.com'
你传递给bind和connect的地址是一个关于AF_INET的socket的元组(ipAddress,port)。代替connect,你也可以调 用connect_ex(address)方法。如果背后对C的connect的调用返回一个错误,那么connect_ex也将返回一个错误(否则返回 0代表成功),代替引发一个异常。
当你调用listen时,你给了它一个参数,这个数值表示在等待队列中允许放置的进来的连接总数。
当等待队列已满时,如果有更多的连接到达,那么远程端将被告知连接被拒绝。
在socket模块中的SOMAXCONN变量表明了等待队列所能容纳的最大量。
accept()方法返回形如bind和connect的一个地址,代表远程socket的地址。下面显示变量v的值:
>>> v
('127.0.0.1', 1334)
UDP是不定向的连接,但是你仍然可以使用给定的目的地址和端口来调用connect去关联一个socket。
3、发送和接收数据
函 数send(string[,flags])发送给定的字符串到远程socket。
sendto(string[,flags],address)发送给 定的字符串到一个特定的地址。
通常,
send方法用于可靠连接的socket,
sendto方法用于不可靠连接的socket,但是如果你在一个 UDP socket上调用connect来使它与一个特定的目标建立联系,那么这时你也可以使用send方法来代替sendto。
send和sendto都返回实际发送的字节数。当你快速发送大量的数据的时候,你可能想去确保全部信息已被发送,那么你可以使用如下的一个函数:
def safeSend(sock,msg):
sent=0
while msg:
i=sock.send(msg)
if i==-1: #发生了错误
return -1
sent+=i
msg=msg[i:]
time.sleep(25)
return sent
recv(bufsize[,flags]) 方法接收一个进来的消息。如果有大量的数据在等待,它只返回前面的bufsize字节数的数据。recvfrom(bufsize[,flags])做同 样的事,除了它使用AF_INET socket的返回值是(data,(ipAddress,port)),这便于你知道消息来自哪儿(这对于非连接的 socket是有用的)。
send,sendto,recv和recvfrom方法都有一个可选的参数flags,默认值为0。你可以通过对socket.MSG_*变量进行组合(按位或)来建立flags的值。这些值因平台而有所不同,但是最通用的值如下所示:
MSG_OOB:处理带外数据(既TCP紧急数据)。
MSG_DONTROUTE:不使用路由表;直接发送到接口。
MSG_PEEK:返回等待的数据且不把它们从队列中删除。
例如,如果你有一个打开的socket,它有一个消息等待被接收,你可以接收这个消息后并不把它从进来的数据的队列中删除:
>>> q.recv(1024,MSG_PEEK)
'hello'
>>> q.recv(1024,MSG_PEEK) #因为没有删除,所以你可以再得到它。
'hello'
makefile([mode[,bufsize]]) 方法返回一个文件类对象,其中封装了socket,以便于你以后将它传递给要求参数为一个文件的代码(或许你喜欢使用文件的方法来代替send和 recv)。这个可选的mode和bufsize参数的取值和内建的open函数一样。
4、使用socket选项
socket对象的getpeername()和 getsockname()方法都返回包含一个IP地址和端口的二元组(这个二元组的形式就像你传递给connect和bind的)。 getpeername返回所连接的远程socket的地址和端口,getsockname返回关于本地socket的相同信息。
在默认 情况下,socket是阻塞式的,意思就是socket的方法的调用在任务完成之前是不会返回的。例如,如果存储向外发送的数据的缓存已满,你又企图发送 更多的数据,那么你对send的调用将被阻塞直到它能够将更多的数据放入缓存。你可以通过调用setblocking(flag)方法(其中flag取值 是0,setblocking(0))来改变这个默认行为,以使socket为非阻塞式。当socket为非阻塞式的时候,如果所做的动作将导致阻塞,将 会引起error异常。下面一段代码将试图不断地接受新的连接并使用函数processRequest来处理。如果一个新连接无效,它将间隔半秒再试。另 一方法是在你的监听socket上调用select或poll来检测一个新的连接的到达。
别的socket的选项可以使用 setsockopt(level,name,value)和getsockopt(level,name[,buflen])方法来设置和获取。 socket代表了一个协议栈的不同层,level参数指定了选项应用于哪一层。level的取值以SOL_开头(SOL_SOCKET,SOL_TCP 等等)。name表明你涉及的是哪个选项。对于value,如果该选项要求数值的值,value只能传入数字值。你也可以传递入一个缓存(一个字符串), 但你必须使用正确的格式。对getsockopt,不指定buflen参数意味你要求一个数字值,并返回这个值。如果你提供了 buflen,getsockopt返回代表一个缓存的字符串,它的最大长度是buflen的字节数。下面的例子设置了一个socket的用于发送的缓存 尺寸为64KB:
>>> s=socket(AF_INET,SOCK_STREAM)
>>> s.setsockopt(SOL_SOCKET,SO_SNDBUF,65535)
要得到一个包在被路由丢弃前所能有的生命周期(TTL)和跳数,你可以使用如下代码:
>>> s.getsockopt(SOL_IP,IP_TTL)
32
5、数值转换
由于不同平台的字节顺序不一样,所以当在网络中传输数据时我们使用标准的网络字节顺序。nthol(x)和ntohs(x)函数要求一个网络字节顺序的数值并把它转换为当前主机字节顺序的相同数值,而htonl(x)和htons(x)则相反:
>>> import.socket
>>> socket.htons(20000) #转换为一个16位的值
8270
>>> socket.htonl(20000) #转换为一个32位的值
541982720
>>> socket.ntohl(541982720)
20000
使用SocketServers
SocketServers模块为一组socket服务类定义了一个基类,这组类压缩和隐藏了监听、接受和处理进入的socket连接的细节。
1、SocketServers家族
TCPServer和UDPServer都是SocketServer的子类,它们分别处理TCP和UDP信息。
注意:SocketServer也提供UnixStreamServer(TCPServer的子类)和UNIXdatagramServer(UDPServer的子类),它们都如同其父类一样除了在创建监听socket时使用AF_UNIX代替了AF_INET。
默 认情况下,socket服务一次处理一个连接,但是你可以使用ThreadingMixIN和ForkingMixIn类来创建任一 SocketServer的线程和子进程。实际上,SocketServer模块提供了一些对些有用的类来解决你的麻烦,它们 是:ForkingUDPServer、ForkingTCPServer、ThreadingUDPServer、 ThreadingTCPServer、ThreadingUnixStreamServer和 ThreadingUnixDatagramServer。
SocketServer以通常的方法处理进入的连接;要使它更有用,你应该 提供你自己的请求处理器类给它以便它传递一个socket去处理。SocketServer模块中的BaseRequestHandler类是所有请求处 理器的父类。假设,例如你需要写一个多线程的电子邮件服务器,首先你要创建一个MailRequestHandler,它是 BaseRequestHandler的子类,然后把它传递给一个新创建的SocketServer:
import SocketServer
...#创建你的MailRequestHandler
addr=('220.172.20.6',25) #监听的地址和端口
server=SocketServer.ThreadingTCPServer(addr,MailRequestHandler)
server.serve_forever()
每 次一个新的连接到来时,这个server创建一个新的MailRequestHandler实例并调用它的handle()方法来处理这个新的请求。因为 server继承自ThreadingTCPServer,对于每个新的请求它都启动一个单独的线程来处理这个请求,以便于多个请求能够被同时处理。如果 用handle_request()代替server_forever,它将一个一个的处理连接请求。server_forever 只是反复调用 handle_request而已。
一般来说,你只需使用socket服务之一,但是如果你需要创建你自己的子类的话,你可以覆盖我们下面提到的方法来定制它。
当 服务被第一次创建的时候,__init__函数调用server_bind()方法来绑定监听socket(self.socket)到正确的地址 (self.server_address)。然后调用server_activate()来激活这个服务(默认情况下,调用socket的listen 方法)。
这个socket服务不做任何事情直到调用了handle_request或serve_forever方法。 handle_request调用get_request()去等待和接收一个新的socket连接,然后调用 verify_request(request,client_address)去看服务是否会处理这个连接(你可以在访问控制中使用这个,默认情况下 verify_request总是返回true)。如果会处理这个请求,handle_request然后调用 process_request(request,client_address),如果 process_request(request,client_address)导致一个异常的话,将调用 handle_error(request,client_address)。默认情况下,process_request简单地调用 finish_request(request,client_address);子进程和线程类覆盖了这个行为去开始一新的进程或线程,然后调用 finish_request。finish_request实例化一个新的请求处理器,请求处理器轮流调用它们的handle()方法。
当SocketServer创建一个新的请求处理器时,它传递给这个处理器的__init__函数的self变量,以便于这个处理器能够访问关于这个服务的信息。
SocketServer 的fileno()方法返回监听socket的文件描述符。address_family成员变量指定了监听socket的socket族(如 AF_INET),server_address包含了监听socket被绑定到的地址。socket变量包含监听socket自身。
2、请求处理器
请 求处理器有setup()、handle()和finish()方法,你可以覆盖它们来定制你自己的行为。一般情况下,你只需要覆盖handle方法。 BaseRequestHandler的__init__函数调用setup()方法来做初始化的工作,handle()服务于请求,finish()用 于执行清理工作,如果handle或setup导致一个异常,finish不会被调用。记住,你的请求处理器会为每个请求创建一个新的实例。
request 成员变量有关于流(TCP)服务的最近接受的socket;对于数据报服务,它是一个包含进入消息和监听socket的元组。 client_address包含发送者的地址,server有对SocketServer的一个引用(通过这你可以访问它的成员,如 server_address)。
下面的例子实现了一个EchoRequestHandler,这作为一个服务端它将客户端所发送的数据再发送回客户端:
>>> import SocketServer
>>> class EchoRequestHandler(SocketServer.BaseRequestHandler):
... def handle(self):
... print 'Got new connection!'
... while 1:
... mesg=self.request.recv(1024)
... if not msg:
... break
... print 'Received:',msg
... self.request.send(msg)
... print 'Done with connection'
>>> server=SocketServer.ThreadingTCPServer(('127.0.0.1',12321),EchoReuestHandler)
>>> server.handle_request() #执行后将等待连接
Got new connection!
Received: Hello!
Received: I like Tuesdays!
Done with connection
打开另一个Python解释器作为客户端,然后执行如下代码:
>>> from socket import *
>>> s=socket(AF_INET,SOCK_STREAM)
>>> s.connect(('120.0.0.1',12321))
>>> s.send('Hello!')
6
>>> print s.recv(1024)
Hello!
>>> s.send('I like Tuesdays!')
16
>>> print s.recv(1024)
I like Tuesdays!
>>> s.close()
SocketServer 模块也定义了BaseRequestHandler的两个子类:StreamRequestHandler和 DatagramRequestHandler。它们覆盖了setup和finish方法并创建了两个文件对象rfile和wfile,你可以用这两个文 件对象来向客户端读写数据,从而代替使用socket方法。
socket的阻塞或同步编程
三、使用socket
网 络编程中最基本的部分就是socket(套接字)。socket有两种:服务端socket和客户端 socket。在你创建了一个服务端socket之 后,你告诉它去等待连接。然后它将监听某个网络地址(形如:xxx.xxx.xxx.xxx:xxx) 直到客户端连接。然后这两端就可以通信了。
处理客户端socket通常比处理服务端socket要容易一点,因为服务端必须时刻准备处理来自客户端的连接,并且它必须处理多个连接,而客户端只需要简单的连接,然后做点什么,然后断开连接。
实 例化一个socket时,可以指定三个参数:地址系列(默认为socket.AF_INET)、流socket(这是个默认 值: socket.SOCK_STREAM)或数据报socket(socket.SOCK_DGRAM)、协议(默认值是0)。对于简单的 socket,你可以不指定任何参数而全部使用默认值。
服务端socket在使用bind方法之后调用listen方法去监听一个给定的 地址。然后,客户端socket就可以通过使用connect方法(connect方法所使用的地址参数与bind相同)去连接服务端。listen方法 要求一个参数,这个参数就是等待连接队列中所能包含的连接数。
一旦服务端socket调用了listen方法,就进入了临听状态,然后通 常使用一个无限的循环:1、开始接受客房端的连接,这通过调用accept方法来实现。调用了这个方法后将处于阻塞状态(等待客户端发起连接)直到一个客 户端连接,连接后,accept返回形如(client,address)的一个元组,其中client是一个用于与客户端通信的 socket,address是客户端的形如xxx.xxx.xxx.xxx:xxx的地址;2、然后服务端处理客户端的请求;3、处理完成之后又调用 1。
关于传输数据,socket有两个方法:send和recv。send使用字符串参数发送数据;recv参数是字节数,表示一次接受的数据量,如果你不确定一次该接受的数据量的话,最好使用1024。
下面给出一个最小的服务器/客户机的例子:
服务端:
import socket
s = socket.socket()
host = socket.gethostname()
port = 1234
s.bind((host, port))
s.listen(5)
while True:
c, addr = s.accept()
print 'Got connection from', addr
c.send('Thank you for connecting')
c.close()
客户端:
import socket
s = socket.socket()
host = socket.gethostname()
port = 1234
s.connect((host, port))
print s.recv(1024)
注意:如果你使用Ctrl-C来停止服务端的话,如果再次使用相同的端口可能需要等待一会儿。
四、使用SocketServer
SocketServer模块简单化了编写网络服务器的工作。
它提供了四个基本的服务类:TCPServer(使用TCP协议)、UDPServer(使用数据报)、UnixStreamServer、
UnixDatagramServer。UnixStreamServer和UnixDatagramServer用于类Unix平台。
这四个类处理请求都使用同步的方法,也就是说,在下一个请求处理开始之前当前的请求处理必须已完成
。
用SocketServer创建一个服务器需要四步:
1、通过子类化BaseRequestHandler类和覆盖它的handle()方法来创建一个请求处理器类,用于处理进来
的请求;
2、实例化服务类如TCPServer,并传递给它参数:服务器地址和请求处理器类;
3、调用服务实例对象的handle_request()或serve_forever()方法去处理请求。
下面使用SocketServer用同步的方法写一个最简单的服务器:
from SocketServer import TCPServer, StreamRequestHandler
#第一步。其中StreamRequestHandler类是BaseRequestHandler类的子类,它为流socket定义了
#rfile和wfile方法
class Handler(StreamRequestHandler):
def handle(self):
addr = self.request.getpeername()
print 'Got connection from', addr
self.wfile.write('Thank you for connecting')
#第二步。其中''代表运行服务器的主机
server = TCPServer(('', 1234), Handler)
#第三步。serve_forever()导致进入循环状态
server.serve_forever()
注意:使用阻塞或同步的方法一次只能连接一个客户端,处理完成后才能连接下一个客户端。
非阻塞或异步编程
五、简单的例子
1.socket server
#!/usr/bin/env python
# -*- coding:utf-8 -*-
# Author:Alex Li
#!/usr/bin/env python
# -*- coding:utf-8 -*-
#import SocketServer
import socketserver,json,sys,subprocess
class MyServer(socketserver.BaseRequestHandler):
def handle(self):
# print self.request,self.client_address,self.server
self.request.sendall(bytes('欢迎进入管理界面.',encoding="utf-8"))
while True:
#第一次获取客服端发送内容
data = self.request.recv(1024)
#如果输入为空,继续下一次输入
if len(data) == 0:break
#如果输入为查看目录内容
if str(data,encoding='utf-8')=='dir':
cmd=subprocess.Popen(data.decode(),shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
cmd_res=cmd.stdout.read()
if not cmd_res:
cmd_res=cmd.stderr.read()
if len(cmd_res)==0:
cmd_res=bytes("result",encoding='gbk')
self.request.send(cmd_res)
#接受下一次任务
continue
#目录切换,如果接收内容包含cd
if 'cd' in str(data,encoding='utf-8'):
cmd=subprocess.Popen(data.decode(),shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
cmd_res=cmd.stdout.read()
if not cmd_res:
cmd_res=cmd.stderr.read()
if len(cmd_res)==0:
cmd_res=bytes("result",encoding='gbk')
self.request.send(bytes("%s成功"%data,encoding='gbk'))
continue
print("data", data)
print("[%s] says:%s" % (self.client_address,data.decode() ))
#执行传输任务
task_data = json.loads( data.decode() )
task_action = task_data.get("action")
if hasattr(self, "task_%s"%task_action):
func = getattr(self,"task_%s" %task_action)
func(task_data)
else:
print("task action is not supported",task_action)
def task_put(self,*args,**kwargs):
print("---put",args,kwargs)
filename = args[0].get('filename')
filesize = args[0].get('file_size')
server_response = {"status":200}
self.request.send(bytes( json.dumps(server_response), encoding='utf-8' ))
f = open(filename,'wb')
recv_size = 0
while recv_size < filesize:
data = self.request.recv(4096)
f.write(data)
recv_size += len(data)
#进度条
rate = recv_size / filesize
rate_num= int(rate * 100)
r = '\r[%-100s]%d%%' % ('=' * rate_num,rate_num, )
sys.stdout.write(r)
print("connect success")
sys.stdout.flush()
f.close()
if __name__ == '__main__':
server = socketserver.ThreadingTCPServer(('127.0.0.1',8009),MyServer)
server.serve_forever()
2.socket client
#!/usr/bin/env python
# -*- coding:utf-8 -*-
# Author:Alex Li
import socket
import os ,json
ip_port=('127.0.0.1',8009)
#买手机
s=socket.socket()
#拨号
s.connect(ip_port)
#发送消息
welcome_msg = s.recv(1024)
print("from server:",welcome_msg.decode())
while True:
send_data=input(">>: ").strip()
if len(send_data) == 0:continue
#查看目录下的文件
if send_data=='dir':
s.send(bytes(send_data,encoding='utf-8'))
recv_data=s.recv(1024)
print(str(recv_data,encoding='gbk'))
continue
cmd_list = send_data.split( ' ')
if len(cmd_list) <2:continue
task_type = cmd_list[0]
#目录切换方法
if task_type=='cd':
s.send(bytes(send_data,encoding='utf-8'))
recv_data=s.recv(1024)
print(str(recv_data,encoding='gbk'))
continue
#文件传输
if task_type == 'put':
abs_filepath = cmd_list[1]
if os.path.isfile(abs_filepath):
file_size = os.stat(abs_filepath).st_size
filename = abs_filepath.split("\\")[-1]
print('file:%s size:%s' %(abs_filepath,file_size))
msg_data = {"action":"put",
"filename":filename,
"file_size":file_size}
s.send( bytes(json.dumps(msg_data),encoding="utf-8") )
server_confirmation_msg = s.recv(1024)
confirm_data = json.loads(server_confirmation_msg.decode())
if confirm_data['status'] ==200:
print("start sending file ",filename)
f = open(abs_filepath,'rb')
for line in f:
s.send(line)
print("send file done ")
#跳出本次任务,开始下个任务
continue
else:
print("\033[31;1mfile [%s] is not exist\033[0m" % abs_filepath)
continue
else:
print("doesn't support task type",task_type)
continue
#s.send(bytes(send_data,encoding='utf8'))
#收消息
recv_data=s.recv(1024)
print(str(recv_data,encoding='utf8'))
#挂电话
s.close()
六、socket之IO多路复用
Python中有一个select模块,其中提供了:select、poll、epoll三个方法,分别调用系统的 select,poll,epoll 从而实现IO多路复用
rlist,wlist,elist=select.select(inputs,outputs,[sk,],1)
select()方法提供四个参数 并提取返回值,rlist监听socket对象的连接变化,wlist监听服务端和客户端的收发消息变化,elist监听socket对象的操作正确与否,最后一个参数表明该服务端没个多少秒去监听一次;
socket单进程单线程只提供了一个服务端,一个客户端的交互,采用了select的IO多路复用后,避免了客户端连接及收发消息时的阻塞,从而达到了伪多线程的效果,以下是简单代码:
服务端:
# -*- coding: utf-8 -*-
__author__ = 'pxb'
import socket,select
sk=socket.socket()
sk.bind(('127.0.0.1',9999),)
sk.listen(5)
inputs=[sk,]
outputs=[]
#按用户存放收到的客户端消息
messages={}
while True:
#IO多路复用,将每个变化的socket对象存到rlist列表中
rlist,wlist,elist=select.select(inputs,outputs,[sk,],1)
print(len(inputs),len(rlist),len(wlist),len(outputs))
for r in rlist:
if r==sk:
conn,address=r.accept()
inputs.append(conn)
messages[conn]=[]
conn.sendall(bytes('hello',encoding='utf-8'))
print('hello')
else:
print("=====================")
try:
ret=r.recv(1024)
if not ret:
raise Exception('断开连接')
else:
outputs.append(r)
messages(r).append(ret)
except Exception as e:
inputs.remove(r)
del messages[r]
for w in wlist:
#w.sendall(bytes('response',encoding='utf-8'))
msg=messages[w].pop()
resp=msg+bytes('response',encoding='utf-8')
w.sendall(resp)
outputs.remove(w)
客户端:
# -*- coding: utf-8 -*-
__author__ = 'pxb'
import socket
sk=socket.socket()
sk.connect(('127.0.0.1',9999),)
data=sk.recv(1024)
print(data)
while True:
inp=input('>>>>')
sk.sendall(bytes(inp,encoding='utf-8'))
print(sk.recv(1024))
sk.close()
七、作用域
1、python中无块级作用域
if 1==1:
name='alex'
print('无块级作用域测试',name)
for i in range(10):
name1=i
print('无块级作用域测试',name1)
2、python以函数为作用域(函数以外调用局部变量会报错)
def fun():
name2='alex'
fun()
print('函数作用域测试',name2)
3、python的作用域在执行之前已经确定(作用域不关乎程序的执行顺序,基于以上两条进行)
name='mqq'
def f1():
print(name)
def f2():
name='pxb'
return f1
ret=f2()
ret()
八、多线程
多线程是为了更好地利用资源及节省时间而形成的一套独有的编码方式,结合alex甄嬛西游传,python的多线程有一定的局限性,即每个进程同时只能派出一个线程去执行,io操作不占用cpu资源,计算代码会消耗cpu
多线程演示小代码:
import time
def f1(arg):
time.sleep(1)
print(arg)
#单进程,单线程的应用程序
import threading
t=threading.Thread(target=f1,args=(123,))
t.setDaemon(True)#true表示主线程不在此等子线程
t.start()#不代表当前线程会被立刻执行,取决于操作系统
t.join(2)#表示主线程在此等待,直到子线程完毕
#参数,表示主线程在此最多等待n秒
print('end')
print('end')
print('end')
f1(888)
九、socket源码查看
以下面集成关系为例,class A,class B(A),class C(A),class D(B),class E(C)
以最底层 方法开始集成,不同版本的python集成顺序是不同的
python2.7:D、B、A、E、C
python3.5:D、B、E、C、A
Python Socket模块中包含一些有用IP转换函数,说明如下:
socket.ntohl(x) 把32位正整数从网络序转换成主机字节序。
socket.ntohs(x) 把16位正整数从网络序转换成主机字节序。
socket.htonl(x) 把32位正整数从主机字节序转换成网络序。
socket.htons(x) 把16位正整数从主机字节序转换成网络序。
socket.inet_aton(ip_string) 转换IPV4地址字符串(192.168.10.8)成为32位打包的二进制格式(长度为4个字节的二进制字符串),它不支持IPV6。inet_pton()支持IPV4/IPV6地址格式。
socket.inet_ntoa(packed_ip) 转换32位打包的IPV4地址为IP地址的标准点号分隔字符串表示。
socket.inet_pton(address_family,ip_string)
转换IP地址字符串为打包二进制格式。地址家族为AF_INET和AF_INET6,它们分别表示IPV4和IPV6。
socket.inet_ntop(address_family,packed_ip)
转换一个打包IP地址为标准字符串表达式,例如:“5aef:2b::8”或“127.0.0.1”。
>>>import socket
>>>import struct
>>>socket.ntohl(struct.unpack("i",socket.inet_aton("10.10.58.64"))[0])
168442432L
>>>socket.inet_ntoa(struct.pack("i", socket.htonl(168442432L)))
'10.10.58.64'
>>>struct.unpack("=I", socket.inet_aton("190.10.58.64"))
(1077545662,)
>>>socket.inet_ntoa(struct.pack("=I", 1077545662))
'190.10.58.64'
注意
socket.inet_ntoa(struct.pack('I',int(row[27]))) 和
socket.inet_ntoa(struct.pack('I',socket.htonl(int(row[27])))) 区别
# 从IP地址字符串转换为整数值
defIp2Int(ip_string):
return struct.unpack(“!I”,socket.inet_aton(ip))[0]
# 从网络字节序的数字转换为IP地址(点号分隔)
def Int2Ip(ip):
return socket.inet_ntoa(struct.pack(“!I”,ip))
顶
将数据在网络字节序和主机字节序之间相互转化。
通过调用ntohl和htonl函数,l代表长整型32bit,s代表短整型16bit。
import socket
def convert_integer():
data = 1234
# 32-bit
print "Original: %s => Long host byte order: %s, Network byte order: %s" %(data, socket.ntohl(data), socket.htonl(data))
# 16-bit
print "Original: %s => Short host byte order: %s, Network byte order: %s" %(data, socket.ntohs(data), socket.htons(data))
if __name__ == '__main__':
convert_integer()
可以调用gettimeout()获取默认的超时时间,而调用settimeout()可以设置一个超时时间。
AF = Address Family
PF = Protocol Family
意思就是 AF_INET 主要是用于互联网地址,而 PF_INET 是协议相关,通常是sockets和端口,AF_INET address即使用IP。
import socket
def socket_timeout():
[python] view plain copy
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
print "Default socket timeout:%s"%s.gettimeout()#获取套接字超时时间
s.settimeout(100)#设置套接字超时时间
print "Current socket timeout:%s"%s.gettimeout()<span style="font-family: Arial, Helvetica, sans-serif;">#获取套接字超时时间</span>
if __name__ == '__main__':
socket_timeout()
网络字节顺序NBO(Network Byte Order): 按从高到低的顺序存储,在网络上使用统一的网络字节顺序,可以避免兼容性问题。
主机字节顺序(HBO,Host Byte Order): 不同的机器HBO不相同,与CPU设计有关,数据的顺序是由cpu决定的,而与操作系统无关。
如 Intel x86结构下, short型数0x1234表示为34 12, int型数0x12345678表示为78 56 34 12
如 IBM power PC结构下, short型数0x1234表示为12 34, int型数0x12345678表示为12 34 56 78
1、Socket的地址表示单独的字符串,用于AF_UNIX地址族(host,port)对,用于AF_INET地址族。其中host是一字符串,可以是‘www.google.com’ 域名形式或是‘203.208.46.180’这种ipv4地址的形式;port是一整数。
(host,port,flowinfo,scopeid)四元组(4-tuple),用于AF_INET6地址族
2、错误与异常
所有的错误都将引发异常。一般的异常有如 invalidargument type 或是 out-of-memoryconditions。与socket或地址语义相关的错误将引发socket.error异常。与Socket相关的异常有:
socket.error:由Socket相关错误引发
socket.herror:由地址相关错误引发
socket.gaierror:由地址相关错误,如getaddrinfo()或getnameinfo()引发
socket.timeout:当socket出现超时时引发。超时时间由settimeout()提前设定
(与异常相伴的错误信息请查阅API说明)
3、常用函数
socket.has_ipv6:判断平台是否支持IPV6
socket.create_connection(address[,timeout[, source_address]]):
创建一个正在监听的地址,并返回Socket对象
socket.getaddrinfo(host,port, family=0, socktype=0, proto=0, flags=0):
返回一个包含5元组的list,用来获得host的地址信息
socket.gethostbyname(hostname):
将host主机名转换为ipv4地址
socket.gethostbyname_ex(hostname):根据hostname获取一个主机关于IP和名称的全面的信息。
功能扩展的gethostbyname函数,返回主机名、主机别名列表、主机IP地址列表
socket.gethostname():返回python解释器运行的机器hostname,返回当前主机名
socket.gethostbyaddr(ip_address):
通过ip地址,返回包括主机名的三元组:(hostname, aliaslist, ipaddrlist)
socket.getnameinfo(sockaddr,flags):
socket.getprotobyname(protocolname):
socket.getservbyname(servicename[,protocolname]):
通过给定的服务名,协议名,返回该服务所在的端口号
socket.getservbyport(port[,protocolname]):
返回该端口号的服务名,如‘80’:‘http’
socket.socket([family[,type[, proto]]]):
通过给定的地址族,socket类型和端口号,创建新的Socket。默认地址族为AF_INET,socket类型为SOCK_STREAM,端口号为0或省略
socket.socketpair([family[,type[, proto]]]):可用平台,unix
socket.fromfd(fd, family,type[, proto]):可用平台,unix
socket.ntohl(x):将32位正整数从网络字节序转换为机器字节序
socket.ntohs(x):将16为正整数从网络字节序转换为机器字节序
socket.htonl(x):将32为正整数从机器字节序转换为网络字节序
socket.htons(x):将16位正整数从机器字节序转换为网络字节序
socket.inet_aton(ip_string):
将点分十进制字符串ipv4地址形式转化为32位二进制形式,即一个4个字符的字符串,一般用于标准C库函数中的struct_in_addr
socket.inet_ntoa(packed_ip):上个函数的反操作
socket.inet_pton(address_family,ip_string):类似上述操作,可用平台,部分unix
socket.inet_ntop(address_family,packed_ip):类似上述操纵,可用平台,部分unix
socket.getdefaulttimeout():返回socket默认超时时间,以秒计(float)
socket.setdefaulttimeout(timeout):设置Socket默认超时时间,以秒计(float)
socket.SocketType:这是python的类型对象,表示socket的类型
4、Socket对象方法
socket.accept():返回(conn,address)对,其中conn是新的socket对象,在其上可以发送和接收数据;address是另一端的socket地址
socket.bind(address):将socket绑定到地址上,该socket必须之前没有做过绑定操作
socket.close():关闭socket,该socket之后对该socket所做的的所有操作将失败,远端连接将不会收到数据。当虚拟机进行垃圾回收时,该socket将被自动关闭
socket.connect(address):连接该地址上的远端Socket
socket.connect_ex(address):类似上面操作,但出错时返回错误代码而非触发异常,可以很好的支持异步连接
socket.fileno():
socket.getpeername():
socket.getsockname():返回Socket自己的地址,对查找端口号有用
socket.getsockopt(level,optname[, buflen]):
socket.ioctl(control,option):可用平台 windows
socket.listen(backlog):监听socket连接,参数表示最大连接队列数。该参数与系统有关。通常是5,最小为0
socket.makefile([mode[,bufsize]]):返回与socket相关的file对象
socket.recv(bufsize[,flags]):接收数据,返回表示接收到数据的String。buffersize表示一次接收到的数据的最大量
socket.recvfrom(bufsize[,flags]):接收数据,返回(String ,address)对。
socket.recvfrom_into(buffer[,nbytes[,flags]]):接收数据,将其写入参数buffer而非生成字符串。返回(nbyte,address),其中nbyte是接收到的数据量,address是发送端的地址。
socket.recv_into(buffer[,nbytes[, flags]]):接收数据,区别于上的是,仅返回nbyte——接收数量,没有地址
socket.send(string[,flags]):发送数据,该socket必须与远端socket连接。返回发送的数据量。程序自己负责检查是否所有的数据均已发送,并自己处理未发送数据。
socket.sendall(string[,flags]):发送数据。与上函数不同的是,该函数会持续发送数据直到数据发送完毕或出现错误为止。若成功发送,返回none,但当错误发生时,将无法判断发送了多少数据。
socket.sendto(string[,flags],address):向socket发送数据,该socket不应该连接远端socket,因为目的socket使用地址表示的。该函数返回发送的数据量
socket.setblocking(flag):设置阻塞或非阻塞模式。flag=0时被设置为非阻塞模式,其他为阻塞模式。新建的socket均为阻塞模式。当在非阻塞模式下,如果recv()函数执行中,没有收到任何数据,或是send()函数没有立即处理掉数据,error异常将会被触发;在阻塞模式下,执行函数将被阻塞直到其可以执行。s.setblocking(0)等同于 s.settimeout(0.0),s.setblocking(1)等同于s.settimeout(None)
socket.settimeout(value):设置阻塞模式下socket的超时时间,其值可以是非负数的float类型,以秒计,或是None。若给定float,socket的后续操作若在给定超时时间内没有完成,将触发timeout异常;若给定None,则使超时设置失效
socket.gettimeout():返回超时时间(float,以秒计)或None
socket.setsockopt(level,optname, value):
socket.shutdown(how):
socket.family:python类型,socket族
socket.type:python类型,socket类型
socket.proto:python类型,socket协议