socket编程步骤
服务端创建一个socket,绑定地址和端口,然后监听端口上传入的连接,一旦有连接进来,就通过accept函数接收传入的连接。
客户端也是创建一个socket。绑定远程地址和端口,然后建立连接,发送数据。
服务端socket
下面通过一段实例代码来详细说明 服务端 socker_server.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | import socket
import sys
HOST = "127.0.0.1"
PORT = 10000
s = None
for res in socket.getaddrinfo(HOST, PORT, socket.AF_UNSPEC,
socket.SOCK_STREAM, 0 , socket.AI_PASSIVE):
af, socktype, proto, canonname, sa = res
try :
s = socket.socket(af, socktype, proto)
except socket.error as msg:
s = None
continue
try :
s.bind(sa)
s.listen( 5 )
except socket.error as msg:
s.close()
s = None
continue
break
if s is None :
print &
sys.exit( 1 )
conn, addr = s.accept()
print &
while 1 :
data = conn.recv( 1024 )
if not data: break
conn.send(data)
conn.close()
|
Copier après la connexion
首先我们通过socket.getaddrinnfo函数将host/port转换成一个包含5元组的序列。这个5元组包含我们创建一个socket连接所需要的所有必要参数。返回的5元组分别是 (family, sockettype, proto, canonname, sockaddr)
family 地址簇,用与socket()函数的第一个参数。主要有以下几个
socket.AF_UNIX 用与单一机器下的进程通信
socket.AF_INET 用与服务器之间相互通信,通常都用这个。
socket.AF_INET6 支持IPv6
sockettype socket类型,用与socket()函数的第二个参数,常用的有
socket.SOCK_STREAM 默认,用于TCP协议
socket.SOCK_DGRAM 用于UDP协议
proto 协议,用于socket()函数的第三个参数。 getaddrinnfo函数会根据地址格式和socket类型,返回合适的协议
canonname 一个规范化的host name。
sockaddr 描述了一个socket address .是一个二元组,主要用于bind()和connect()函数
接下来创建一个socket对象,传入getaddrinnfo函数返回的af,sockettype,proto。
1 | s = socket.socket(af, socktype, proto)
|
Copier après la connexion
然后绑定我的socket address
Copier après la connexion
开启监听模式
Copier après la connexion
listen函数会监听连接到socket上的连接,参数表示在拒绝连接之前系统可以挂起的最大连接队列数量为5。这些连接还没有被accept处理。数量不能无限大,通常指定5。
一旦我们监听到了连接,就会调用accept函数接收连接
Copier après la connexion
accept函数返回一个二元组,conn是一个新的socket对象,用来接收和发送数据。addr表示另一端的socket地址。
接下来我们就可以用conn对象发送和接收数据了
1 2 | data = conn.recv( 1024 )
conn.send(data)
|
Copier après la connexion
这里我们接收到一个连接socket就会停止运行,所以如果要循环连接的话,将accept函数放入到一个死循环里。
客户端socket
客户端socket编程相对比较简单,通过connect和服务端建立连接之后,就可以相互通信了。socket_client.py如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | for res in socket.getaddrinfo(HOST, PORT, socket.AF_UNSPEC, socket.SOCK_STREAM):
af, socktype, proto, canonname, sa = res
try :
s = socket.socket(af, socktype, proto)
except socket.error as msg:
s = None
continue
try :
s.connect(sa)
except socket.error as msg:
s.close()
s = None
continue
break
if s is None :
print &
sys.exit( 1 )
s.sendall(&
data = s.recv( 1024 )
s.close()
print &
|
Copier après la connexion
以上主要是针对TCP流数据的socket编程。对于UDP协议的数据,处理略有不同。譬如发送接收UDP数据包处理函数为:
1 2 | socket.sendto(string, flags, address)
socket.recvfrom(bufsize[, flags])
|
Copier après la connexion
SocketServer模块
python中网络编程除了socket模块还提供了SocketServer模块,这一模块主要是对socket模块进行了封装,将socket的对象的创建,绑定,连接,接收,发送,关闭都封装在里面,大大简化了网络服务的编程。
此模块提供了以下2个主要的网络服务类,用于创建相应的套接字流
TCPServer 创建TCP协议的套接字流
UDPServer 创建UDP协议的套接字流
我们有了套接字流对象,还需要一个请求处理类。SocketServer模块提供了请求处理类有BaseRequestHandler,以及它的派生类StreamRequestHandler和DatagramRequestHandler。所以只要继承这3个类中的一个,然后重写handle函数,此函数将用来处理接收到的请求。下面看一个服务端的代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | import SocketServer
class MyTCPHandler(SocketServer.StreamRequestHandler):
def handle( self ):
self .data = self .request.recv( 1024 ).strip()
print "{} wrote:" . format ( self .client_address[ 0 ])
print self .data
self .request.sendall( self .data.upper())
if __name__ = = "__main__" :
HOST, PORT = "localhost" , 10000
server = SocketServer.TCPServer((HOST, PORT), MyTCPHandler)
server.serve_forever()
|
Copier après la connexion
看着是不是代码简单了很多,而且SocketServer模块内部使用了多路复用IO技术,可以实现更好的连接性能。看serve_forever函数的源代码用到了select模块。通过传入socket对象调用select.select()来监听socket对象的文件描述符,一旦发现socket对象就绪,就通知应用程序进行相应的读写操作。源代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | def serve_forever( self , poll_interval = 0.5 ):
self .__is_shut_down.clear()
try :
while not self .__shutdown_request:
r, w, e = _eintr_retry(select.select, [ self ], [], [],
poll_interval)
if self in r:
self ._handle_request_noblock()
finally :
self .__shutdown_request = False
self .__is_shut_down. set ()
|
Copier après la connexion
即使使用了select技术,TCPServer,UDPServer处理请求仍然是同步的,意味着一个请求处理完,才能处理下一个请求。但SocketServer模块提供了另外2个类用来支持异步的模式。
ForkingMixIn 利用多进程实现异步
ThreadingMixIn 利用多线程实现异步
看名字就知道使用了mixin模式。而mixin模式可以通过多继承来实现,所以通过对网络服务类进行多继承的方式就可以实现异步模式
1 2 | class ThreadedTCPServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer):
pass
|
Copier après la connexion
针对ThreadindMixIn,实现异步的原理也就是在内部对每个请求创建一个线程来处理。看源码
1 2 3 4 5 6 | def process_request( self , request, client_address):
t = threading.Thread(target = self .process_request_thread,
args = (request, client_address))
t.daemon = self .daemon_threads
t.start()
|
Copier après la connexion
下面提供一个异步模式的示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | import socket
import threading
import SocketServer
class ThreadedTCPRequestHandler(SocketServer.BaseRequestHandler):
def handle( self ):
data = self .request.recv( 1024 )
cur_thread = threading.current_thread()
response = "{}: {}" . format (cur_thread.name, data)
self .request.sendall(response)
class ThreadedTCPServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer):
pass
def client(ip, port, message):
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((ip, port))
try :
sock.sendall(message)
response = sock.recv( 1024 )
print "Received: {}" . format (response)
finally :
sock.close()
if __name__ = = "__main__" :
HOST, PORT = "localhost" , 0
server = ThreadedTCPServer((HOST, PORT), ThreadedTCPRequestHandler)
ip, port = server.server_address
server_thread = threading.Thread(target = server.serve_forever)
server_thread.daemon = True
server_thread.start()
print "Server loop running in thread:" , server_thread.name
client(ip, port, "Hello World 1" )
client(ip, port, "Hello World 2" )
client(ip, port, "Hello World 3" )
server.shutdown()
server.server_close()
|
Copier après la connexion
以上是本人对socket相关的理解,有什么不当或错误之处,还请指出。