Python 提供了两个级别访问的网络服务:
- 低级别的网络服务支持基本的 Socket,它提供了标准的 BSD Sockets API,可以访问底层操作系统Socket接口的全部方法。
- 高级别的网络服务模块 SocketServer, 它提供了服务器中心类,可以简化网络服务器的开发。
一、基本的Socket
socket 又称“套接字”,应用程序通常通过"套接字"向网络发出请求或者应答网络请求,使主机间或者一台计算机上的进程间可以通讯。
1 #!/usr/bin/env python 2 #coding:utf-8 3 4 ''' 5 socket模块是python内置的一个有关网络编程的模块,主要有以下功能: 6 import socket 7 sk = socket.socket(socket.AF_INET,socket.SOCK_STREAM,0) 8 参数一:地址簇 9 socket.AF_INET ipv4 (默认) 10 socket.AF_INET6 ipv6 11 socket.AF_UNIX 只能用于单一的UNIX系统进程间通信(本地通信) 12 13 参数二:传输类型 14 socket.SOCK_STREAM 流式套接字 TCP(默认) 15 socket.SOCK_DGRAM 报式套接字 UDP 16 socket.SOCK_RAW 原始套接字 IP 17 18 传输类型差别: 19 普通的套接字无法处理ICMP、IGMP等网络报文,而SOCK_RAW可以;SOCK_RAW也可以处理特殊的IPv4报文;此外,利用原始套接字,可以通过IP_HDRINCL套接字选项由用户构造IP头。 20 socket.SOCK_DGRAM 是一种可靠的UDP形式,即保证交付数据报但不保证顺序。SOCK_RAM用来提供对原始协议的低级访问,在需要执行某些特殊操作时使用,如发送ICMP报文。SOCK_RAM通常仅限于高级用户或管理员运行的程序使用。 21 22 参数三:协议 23 0 (默认)与特定的地址家族相关的协议,如果是 0 ,则系统就会根据地址格式和套接类别,自动选择一个合适的协议 24 25 sk.bind(address) 26 将套接字绑定到地址。address地址的格式取决于地址族。在AF_INET下,以元组(host,port)的形式表示地址。 27 28 sk.listen(backlog) 29 开始监听传入连接。backlog指定在拒绝连接之前,可以挂起的最大连接数量。超过最大连接数再进行连接客户端就会报错 30 31 sk.setblocking(bool) 32 是否阻塞(默认是True),若改为False,那么accept和recv时一旦无数据,就会报错 33 34 sk.accept() 35 接收TCP客户端的连接(阻塞式,没有就一直等待)并返回(conn,address),其中conn是新的套接字对象,可以用来接收和发送数据。address是连接客户端的地址。 36 37 sk.connect(address) 38 连接到address处的套接字。一般address的格式为元组(hostname,port),如果连接出错,返回socket.error错误。 39 40 sk.connect_ex(address) 41 同上,只不过会有返回值,连接成功时返回 0 ,连接失败时候返回编码,例如:10061 42 43 sk.close() 44 关闭套接字 45 46 sk.recv(bufsize[,flag]) 47 接收套接字的数据,数据以字节形式返回,bufsize指定最多可以接受字节的数量。flag提供有关消息的其他信息,通常可以忽略。 48 在没收到数据时,会一直等待,直到远程地址断开并且所有的数据接收完,就会返回一个空字符串(空字符串在判断是为假) 49 50 sk.recvfrom(bufsize[,flag]) 51 类似recv接收数据,但返回值是(data,address),其中data是数据,address是发送者的地址 52 53 sk.send(data) 54 将data中的数据发送到已连接的套接字,返回值是真实要发送的字节数量,该数量可能小于data的总大小(eg:网络繁忙时不会把data全部发送) 55 56 sk.sendall(data) 57 将data中的数据发送到已连接到套接字,但在返回之前会尝试发送所有数据。成功返回None,失败则抛出异常 58 内部通过递归调用send,将所以数据发送出去 59 60 sk.sendto(data,address) 61 将数据发送给套接字,address是形式为(ipaddr,port)的元组,指定远程地址。返回值是发送的字节数。该函数主要用于UDP协议。 62 63 sk.settimeout(timeout) 64 设置套接字操作的超时期,timeout是一个浮点数,单位是秒。值为None表示没有超时期。一般,超时期应该在刚创建套接字时设置,因为它们可能用于连接的操作(如 client 连接最多等待5s ) 65 66 sk.getpeername() 67 返回连接套接字的远程地址。返回值通常是元组(ipaddr,port)。 68 69 sk.getsockname() 70 返回套接字自己的地址。通常是一个元组(ipaddr,port) 71 72 sk.fileno() 73 套接字的文件描述符 74 '''
套接字实现简单对话
1 #!/usr/bin/env python 2 3 '''实现命令执行并返回''' 4 5 import socket 6 import subprocess 7 8 ip_port = ('127.0.0.1',6666) 9 10 sk = socket.socket() 11 sk.bind(ip_port) 12 sk.listen(5) 13 14 while True: 15 print('server is waiting...') 16 conn, addr = sk.accept() 17 while True: 18 client_data = conn.recv(1024) 19 if not client_data: 20 break 21 cmd_client = str(client_data,'gb2312') 22 res = subprocess.Popen(cmd_client,shell=True,stdout=subprocess.PIPE) 23 result_data = res.stdout.read() 24 if len(result_data) == 0: 25 result_data = b'The result of your order is null!' 26 data = result_data.decode('gb2312') 27 conn.send(bytes('DATA_SIZE | %s' % len(data),'gb2312')) 28 ACK_DATA = conn.recv(50).decode('gb2312') 29 #这里的作用是防止因为缓冲区导致在发送数据大小时会参杂部分数据的问题,这样比sleep(1)好多了 30 if ACK_DATA == "ACK_CLIENT_OK": 31 conn.send(result_data) 32 conn.close()
1 #!/usr/bin/env python 2 3 4 '''实现执行命令并返回''' 5 ''' 6 注意:在windows下是gb2312编码,保存文件和shell输入时,都要是gb2312才可以 7 ''' 8 import socket 9 10 ip_port = ('127.0.0.1',6666) 11 BUFSIZE = 500 12 13 sk = socket.socket() 14 sk.connect(ip_port) 15 16 #print('sockname:',sk.getsockname()) # sockname: ('127.0.0.1', 1186) 17 #print('peername:',sk.getpeername()) # peername: ('127.0.0.1', 6666) 18 #print('fileno:',sk.fileno()) # fileno: 624 19 20 while True: 21 user_input= input('(Enter q to exit) cmd :').strip() 22 if len(user_input) == 0: 23 continue 24 if user_input == 'q': 25 sk.close() 26 break 27 sk.sendall(bytes(user_input,'gb2312')) 28 SERVER_DATA_BYTE = sk.recv(50) 29 SERVER_DATA = str(SERVER_DATA_BYTE,'gb2312').split('|') 30 #print(SERVER_DATA,type(SERVER_DATA)) #['DATA_SIZE ', ' 1878'] <class 'list'> 31 SERVER_DATA_SIZE = int(SERVER_DATA[1].strip()) 32 #print(SERVER_DATA_SIZE,type(SERVER_DATA_SIZE)) #1878 <class 'int'> 33 sk.send(b"ACK_CLIENT_OK") 34 res = "" 35 RECEIVED_DATA_SIZE = 0 36 while SERVER_DATA_SIZE > RECEIVED_DATA_SIZE: 37 data = sk.recv(BUFSIZE) 38 RECEIVED_DATA_SIZE += len(data) 39 res += data.decode('gb2312') # decode是字节对象的方法,把byte格式转为gb2312 40 else: 41 print(res) #windows下中文的默认字符编码是gb2312 42 43 sk.close()
二、socketserver模块
socketserver底层才有多线程实现,所以用socketserver实现的服务器自己就能多线程处理请求。
server端:
1 #!/usr/bin/env python 2 # -*- coding: utf-8 -*- 3 4 import socketserver 5 import sys 6 import os 7 base_dir = os.path.dirname(os.path.dirname(os.path.dirname(__file__))) 8 sys.path.append(base_dir) #导入自定义包时路径设置 9 10 class MyTCPHandler(socketserver.BaseRequestHandler): #自定义一个类 必须要有 handle 方法 11 12 def handle(self): 13 '''定义好的接口,这里是客户端每次请求后要执行的代码''' 14 15 16 if __name__ == '__main__':
17 IP_PORT = ('127.0.0.1',6666) 18 server = socketserver.ThreadingTCPServer(IP_PORT,MyTCPHandler) #把自定义的类作为参数传进来 19 server.serve_forever() #让server循环接收客户端请求
客户端就普通的客户端 没改变
1 #!/usr/bin/env python 2 # -*- coding: utf-8 -*- 3 4 import socket 5 6 IP_PORT = ('127.0.0.1',6666) 7 8 sk = socket.socket() 9 sk.connect(IP_PORT) 10 11 while True: 12 user = input("username: ").strip() 13 passwd = input("password: ").strip() 14 if not user or not passwd: 15 print('please input your username or password!') 16 continue 17 user_info = "username | %s | password | %s" %(user,passwd) 18 sk.send(bytes(user_info,'utf8')) 19 check_data = sk.recv(1024).decode('utf8') 20 # print(type(check_data)) 21 check_data_list = check_data.split('|') 22 # print(check_data_list) #['200', 'Pass authentication!'] 23 if check_data_list[0].strip() == '200' : 24 break 25 else: 26 print(check_data_list[1]) 27 28 res = '' 29 RECEIVED = 0 30 while True: 31 cmd = input('%s $ ' % user).strip() 32 if len(cmd) == 0:continue 33 if cmd == 'q':break 34 sk.send(bytes(cmd,'utf8')) 35 SERVER_DATA = sk.recv(1024).decode('utf8') 36 SERVER_DATA_LIST = SERVER_DATA.split('|') 37 SERVER_DATA_SIZE = int(SERVER_DATA_LIST[1].strip()) 38 sk.send(bytes('CLIENT_ACK_OK', 'UTF8')) 39 40 while RECEIVED < SERVER_DATA_SIZE: 41 server_data = sk.recv(1024).decode('utf8') 42 res += server_data 43 RECEIVED += len(res) 44 45 print(res) 46 sk.close()