Python网络编程(一)

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()方法决定了每一个连接的后续操作。

  • init():初始化连接套接字、地址、处理实例等
  • handle():定义如何处理每一个连接
  • setup():handle()前执行,设置默认之外的连接配置
  • finish():在handle()方法之后执行

其中还包含着几种常用的变量:

  • self.request:表示套接字对象
  • self.server:表示调用处理程序的实例
  • self.client_address:表示客户端的地址信息,(IP,PORT)的形式

服务端的运行包含两个重要的函数:

  • serve_forever():一个连接报错不会导致程序停止,而是持续与其他客户端通信。
  • shutdown():停止serve_forever()

下面给出一个例子。

服务端:

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,进而派生出线程或进程来处理客户端的请求。

  • ForKingMixIn:为每一个客户端请求派生一个进程进行处理。
  • ThreadingMixIn:为每一个客户端请求派生一个线程进行处理。

当服务器继承这两个类型后,处理客户端时不会阻塞,而是创建新的进程或线程处理。

其中,这两种方法也有各自的特点:

  • 分叉:Windows中不支持分叉;分叉有点耗费资源,因为每个分叉出来的进程都需要有自己的内存,若有太多的客户端则不能很好的分叉。
  • 线程:线程是轻量级的进程或子进程,所有的线程都存在于相同的进程中,共享内存,所以带来的问题时需要确保变量不会产生冲突,或者同时修改内容,否则会造成混乱。

socketserver中提供如下选择:

其中,ThreadingTCPServer就是同时继承了ThreadingMixIn和TCPServer类的类。其他的类似。

class ThreadingTCPServer(ThreadingMixIn, TCPServer): pass

因此要实现多线程服务器,只需要创建相应的实例即可。如在上一个例子中要实现多线程的TCPServer,如下所示:

#多线程版本
server=ThreadingTCPServer((HOST,PORT),MyTCPHandler)

你可能感兴趣的:(Python基础)