socket编程步骤
服务端创建一个socket,绑定地址和端口,然后监听端口上传入的连接,一旦有连接进来,就通过accept函数接收传入的连接。
客户端也是创建一个socket。绑定远程地址和端口,然后建立连接,发送数据。
服务端socket
下面通过一段实例代码来详细说明 服务端 socker_server.py
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 ‘could not open socket’
sys.exit(1)
conn, addr = s.accept()
print ‘connected by’, addr
while 1:
data = conn.recv(1024)
if not data: break
conn.send(data)
conn.close()
首先我们通过socket.getaddrinnfo函数将host/port转换成一个包含5元组的序列。这个5元组包含我们创建一个socket连接所需要的所有必要参数。返回的5元组分别是 (family, sockettype, proto, canonname, sockaddr)
family 地址簇,用与socket()函数的第一个参数。主要有以下几个
socket.af_unix 用与单一机器下的进程通信socket.af_inet 用与服务器之间相互通信,通常都用这个。socket.af_inet6 支持ipv6sockettype 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。
s = socket.socket(af, socktype, proto)
然后绑定我的socket address
s.bind(sa)
开启监听模式
s.listen(5)
listen函数会监听连接到socket上的连接,参数表示在拒绝连接之前系统可以挂起的最大连接队列数量为5。这些连接还没有被accept处理。数量不能无限大,通常指定5。
一旦我们监听到了连接,就会调用accept函数接收连接
conn, addr = s.accept()
accept函数返回一个二元组,conn是一个新的socket对象,用来接收和发送数据。addr表示另一端的socket地址。
接下来我们就可以用conn对象发送和接收数据了
data = conn.recv(1024) # 接收数据, 这里指定一次最多接收的字符数量为1024
conn.send(data) # 发送数据
这里我们接收到一个连接socket就会停止运行,所以如果要循环连接的话,将accept函数放入到一个死循环里。
客户端socket
客户端socket编程相对比较简单,通过connect和服务端建立连接之后,就可以相互通信了。socket_client.py如下
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 ‘could not open socket’
sys.exit(1)
s.sendall(‘hello, world’)
data = s.recv(1024)
s.close()
print ‘received’, repr(data)
以上主要是针对tcp流数据的socket编程。对于udp协议的数据,处理略有不同。譬如发送接收udp数据包处理函数为:
socket.sendto(string, flags, address)
socket.recvfrom(bufsize[, flags]) #返回(string, address),string是返回的数据,address是发送方的socket地址
socketserver模块
python中网络编程除了socket模块还提供了socketserver模块,这一模块主要是对socket模块进行了封装,将socket的对象的创建,绑定,连接,接收,发送,关闭都封装在里面,大大简化了网络服务的编程。
此模块提供了以下2个主要的网络服务类,用于创建相应的套接字流
tcpserver 创建tcp协议的套接字流
udpserver 创建udp协议的套接字流
我们有了套接字流对象,还需要一个请求处理类。socketserver模块提供了请求处理类有baserequesthandler,以及它的派生类streamrequesthandler和datagramrequesthandler。所以只要继承这3个类中的一个,然后重写handle函数,此函数将用来处理接收到的请求。下面看一个服务端的代码示例
import socketserver
class mytcphandler(socketserver.streamrequesthandler):
“””创建请求处理类,重写handle方法。此外也可以重写setup()和finish()来做一些请求处理前和处理后的一些工作”””
def handle(self):
# self.request is the tcp socket connected to the client
self.data = self.request.recv(1024).strip()
print “{} wrote:”.format(self.client_address[0])
print self.data
# just send back the same data, but upper-cased
self.request.sendall(self.data.upper())
if __name__ == “__main__”:
host, port = “localhost”, 10000
server = socketserver.tcpserver((host, port), mytcphandler)
# activate the server; this will keep running until you
# interrupt the program with ctrl-c
# server.shutdown()
server.serve_forever() # 一直循环接收请求
# server.handle_request() # 只处理一次请求就退出
看着是不是代码简单了很多,而且socketserver模块内部使用了多路复用io技术,可以实现更好的连接性能。看serve_forever函数的源代码用到了select模块。通过传入socket对象调用select.select()来监听socket对象的文件描述符,一旦发现socket对象就绪,就通知应用程序进行相应的读写操作。源代码如下:
def serve_forever(self, poll_interval=0.5):
“””handle one request at a time until shutdown.
polls for shutdown every poll_interval seconds. ignores
self.timeout. if you need to do periodic tasks, do them in
another thread.
“””
self.__is_shut_down.clear()
try:
while not self.__shutdown_request:
# xxx: consider using another file descriptor or
# connecting to the socket to wake this up instead of
# polling. polling reduces our responsiveness to a
# shutdown request and wastes cpu at all other times.
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()
即使使用了select技术,tcpserver,udpserver处理请求仍然是同步的,意味着一个请求处理完,才能处理下一个请求。但socketserver模块提供了另外2个类用来支持异步的模式。
forkingmixin 利用多进程实现异步
threadingmixin 利用多线程实现异步
看名字就知道使用了mixin模式。而mixin模式可以通过多继承来实现,所以通过对网络服务类进行多继承的方式就可以实现异步模式
class threadedtcpserver(socketserver.threadingmixin, socketserver.tcpserver):
pass
针对threadindmixin,实现异步的原理也就是在内部对每个请求创建一个线程来处理。看源码
def process_request(self, request, client_address):
“””start a new thread to process the request.”””
t = threading.thread(target = self.process_request_thread,
args = (request, client_address))
t.daemon = self.daemon_threads
t.start()
下面提供一个异步模式的示例
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__”:
# port 0 means to select an arbitrary unused port
host, port = “localhost”, 0
server = threadedtcpserver((host, port), threadedtcprequesthandler)
ip, port = server.server_address
# start a thread with the server — that thread will then start one
# more thread for each request
server_thread = threading.thread(target=server.serve_forever)
# exit the server thread when the main thread terminates
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()
以上是本人对socket相关的理解,有什么不当或错误之处,还请指出。