Socket
socket被称为套接字,用来描述ip地址和端口,可以实现不同计算机或虚拟机之间的通信。计算机上同时运行着几种服务,要区分计算机之间是哪个程序进行数据传送,就需要给每种服务唯一确定一个端口号。计算机之间通信时首先根据IP地址找到相应的计算机,然后根据端口号找到相应的服务。IP地址和端口号就构成了一个socket,所以每种服务都打开了一个socket。
套接字的连接一般可分为三个过程:服务器监听、客户端请求、连接确认。
服务器监听:服务器的套接字实时监听网络状态,等待连接。
客户端请求:客户端的套接字提出请求,根据服务器端套接字的地址和端口号,向服务器套接字提出连接请求。
连接确认:服务器端套接字接收到客户端套接字的连接请求,它建立一个新的线程,把服务器端套接字描述发给客户端,经客户端确认后连接便建立了起来。当然这之后服务器端套接字继续监听请求。
再建立了套接字之后,要进行数据的传输 我们还需要指定协议类型。我们将介绍如何实现TCP编程和UDP编程。前面的文章我们已经
TCP
大多数的连接都是可靠的TCP连接。其中主动发起连接的叫客户端,被动响应连接的叫服务器。
我们直接给出客户端的代码:
import socket
#创建一个socket
#AF_INET表示使用IPv4协议,若想使用IPv6,则指定为AF_INET6;
#SOCK_STREAM指定使用面向流的TCP协议
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
#建立连接,提供的是服务器的IP地址和端口号,以元组形式传入
s.connect(('127.0.0.1',9999))
#recv()为接收套接字的数据,以字符串形式返回,参数指定了最多接收的数量。
print(s.recv(1024).decode('utf-8'))
for data in [b'Micjael',b'Tracy',b'Sarah']:
#发送string到连接的套接字,返回值为要发送的字节数量。
s.send(data)
print(s.recv(1024).decode('utf-8'))
s.send(b'exit')
s.close()
服务器端:
import socket
import threading
import time
def tcplink(sock,addr):
print('Accept new connection from %s:%s...'%addr)
sock.send(b'Welcome')
while True:
data=sock.recv(1024)
time.sleep(1)
if not data or data.decode('utf-8') =='exit':
break
sock.send(('Hello,%s'% data.decode('utf-8')).encode('utf-8'))
sock.close()
print('Connection from %s:%s closed'%addr)
#创建一个基于IPV4和TCP协议的Socket
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
#监听窗口
#'127.0.0.1'表示绑定到本机上,外部计算机无法连接进来
#小于1024的端口号必须管理员权限才能绑定
s.bind(('127.0.0.1',9999))
#监听窗口,传入参数指定等待连接的最大数量
s.listen(5)
print('Waiting for connection...')
while True:
#接受一个新连接
sock,addr=s.accept() #addr是一个元组的形式(host,port)
#创建新线程来处理TCP连接
t=threading.Thread(target=tcplink,args=(sock,addr))
t.start()
UDP
UDP是面向无连接的协议,它不需要建立连接,只需要知道对方的IP地址和端口号就可直接发送数据包,并不保证数据能够到达。当然和TCP相比,UDP协议的优势是速度快。
客户端:
import socket
#创建一个socket
#AF_INET表示使用IPv4协议,若想使用IPv6,则指定为AF_INET6;
#SOCK_STREAM指定使用面向流的TCP协议
s=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
for data in [b'Michael',b'Tracy',b'Sarah']:
#发送数据到套接字,并用IP和端口号组成远程地址
s.sendto(data,('127.0.0.1',9999))
#接收数据
print(s.recv(1024).decode('utf-8'))
s.close()
服务器端:
import socket
#SOCK_DGRAM指定socket的类型为UDP
s=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
#绑定端口
s.bind(('127.0.0.1',9999))
print('Bind UDP on 9999....')
while True:
#接收数据
data,addr=s.recvfrom(1024)
print('Received from %s:%s.'% addr)
s.sendto(b'Hello,%s!'% data,addr)
socketserver
socketserver模块是标准库中许多服务器框架的基础。它包含了4个基本的类:针对TCP流式套接字的TCPServer;针对UDP数据报套接字的UDPServer;以及UnixStreamServer和UnixDatagramServer。
编写一个socketserver框架的服务器时,服务器每接收到一个请求就会实例化一个请求处理程序,大部分的代码放在了这个请求处理程序中,它的处理方法会在处理请求时调用。调用什么方法取决于特定服务器和使用的处理程序类。
处理程序类Handler是一个继承了BaseRequestHandler类它的子类的类,名字可自定义,其中的handle()方法决定了每一个连接的后续操作。
其中还包含着几种常用的变量:
服务端的运行包含两个重要的函数:
下面给出一个例子。
服务端:
from socketserver import BaseRequestHandler,TCPServer,ThreadingTCPServer
class MyTCPHandler(BaseRequestHandler):
def handle(self):
try:
while True:
self.data=self.request.recv(1024)
print("the data is:",self.data)
if not self.data:
print("connection lost")
break
self.request.send("welcome".encode())
except Exception as e:
print(self.client_address,"disconnection")
finally:
self.request.close()
def setup(self):
print("build connection:",self.client_address)
def finish(self):
print("finish connection")
if __name__=="__main__":
HOST,PORT="127.0.0.1",9999
server=TCPServer((HOST,PORT),MyTCPHandler)
server.serve_forever()
客户端:
import socket
client=socket.socket()
client.connect(('127.0.0.1',9999))
while True:
data=input("greeting:").strip()
if len(data)==0:
continue
if data=="quit":
break
client.send(data.encode())
data_res=client.recv(1024)
print(data_res.decode())
client.close()
异步服务端
上述的例子中服务器是同步的处理客户端的请求,即一次只能连接一个客户机并处理它的请求,这显然是不适合的。使服务器能够同时处理多个连接是很重要的。
要实现异步服务,有三种方法:分叉(forking)、线程(threading)、以及异步IO(asynchronous I/O)。我们在此简单的介绍一下前两种方法。通过对socketserver服务器使用mix-in class,进而派生出线程或进程来处理客户端的请求。
当服务器继承这两个类型后,处理客户端时不会阻塞,而是创建新的进程或线程处理。
其中,这两种方法也有各自的特点:
socketserver中提供如下选择:
其中,ThreadingTCPServer就是同时继承了ThreadingMixIn和TCPServer类的类。其他的类似。
class ThreadingTCPServer(ThreadingMixIn, TCPServer): pass
因此要实现多线程服务器,只需要创建相应的实例即可。如在上一个例子中要实现多线程的TCPServer,如下所示:
#多线程版本
server=ThreadingTCPServer((HOST,PORT),MyTCPHandler)