Quantcast
Channel: CodeSection,代码区,Python开发技术文章_教程 - CodeSec
Viewing all articles
Browse latest Browse all 9596

Python高手之路【十二】socket网络编程

$
0
0
什么是客户/服务器架构?

什么是客户/服务器架构?不同的人有不同的答案。这要看你问的是什么人,以及指的是软件系统还是硬件系统了。但是,有一点是共通的:服务器是一个软件或硬件,用于提供客户需要的“服务”。服务器存在的唯一目的就是等待客户的请求,给这些客户服务,然后再等待其它的请求。另一方面,客户连上一个(预先已知的)服务器,提出自己的请求, 发送必要的数据,然后就等待服务器的完成请求或说明失败原因的反馈。服务器不停地处理外来的请求,而客户一次只能提出一个服务的请求,等待结果。然后结束这个事务。客户之后也可以再提出其它的请求,只是,这个请求会被视为另一个不同的事务了。


Python高手之路【十二】socket网络编程

上图就是Internet 上典型的客户/服务器概念。 展示了如今最常见的客户/服务器结构。一个用户或客户电脑通过 Internet 从服务器上取数据。 这的确是一个客户/服务器架构的系统, 但还有更多类似的系统满足客户/服务器架构。而且,客户/服务器架构也可以应用到电脑硬件上。

客户/服务器网络编程

在完成服务之前,服务器必需要先完成一些设置动作。先要创建一个通讯端点,让服务器能“监听”请求。你可以把我们的服务器比做一个公司的接待员或回答公司总线电话的话务员,一旦电话和设备安装完成,话务员也到了之后,服务就可以开始了。

在网络世界里,基本上也是这样――一旦通讯端点创建好之后,我们在“监听”的服务器就可以进入它那等待和处理客户请求的无限循环中了。当然, 我们也不能忘记在信纸上,杂志里,广告中印上公司的电话号码。否则,就没有人会打电话进来了!同样地,服务器在准备好之后,也要通知潜在的客户,让它们知道服务器已经准备好处理服务了。否则,没有人会提请求的。比方说,你建立了一个全新的网站。这个网站非常的出色,非常的吸引人,非常的有用,是所有网站中最酷的一个。但如果你不把网站的网址或者说统一资源定位符(URL)广而告之的话,没有人会知道这个网站的存在的。这个网站也就永远不见天日了。对于公司
总部的新电话也是这样,你不把电话公之于众,那就没有人会打电话进来。
现在,你对服务器如何工作已经有了一个很好的认识。你已经完成了最难的那一部分。客户端的编程相对服务器端来说就简单得多了。 所有的客户只要创建一个通讯端点, 建立到服务器的连接。然后客户就可以提出请求,请求中,也可以包含必要的数据交互。一旦请求处理完成,客户收到了结果,通讯就结束了。 什么是套接字?

套接字是一种具有之前所说的“通讯端点”概念的计算机网络数据结构。网络化的应用程序在开始任何通讯之前都必需要创建套接字。就像电话的插口一样,没有它就完全没办法通讯。套接字起源于 20 世纪 70 年代加利福尼亚大学伯克利分校版本的 Unix,即人们所说的 BSD Unix。因此,有时人们也把套接字称为“伯克利套接字”或“BSD 套接字”。一开始,套接字被设计用在同一台主机上多个应用程序之间的通讯。这也被称进程间通讯,或 IPC。套接字有两种,分别是基于文件型的和基于网络型的。


Unix 套接字是我们要介绍的第一个套接字家族。其“家族名”为 AF_UNIX(在 POSIX1.g 标准中也叫 AF_LOCAL),表示“地址家族:UNIX”。包括 python 在内的大多数流行平台上都使用术语“地址家族”及其缩写“AF”。而老一点的系统中,地址家族被称为“域”或“协议家族”,并使用缩写“PF”而不是“AF”。同样的,AF_LOCAL(在 2000-2001 年被列为标准)将会代替 AF_UNIX。不过,为了向后兼容,很多系统上,两者是等价的。Python 自己则仍然使用 AF_UNIX。由于两个进程都运行在同一台机器上,而且这些套接字是基于文件的。所以,它们的底层结构是由文件系统来支持的。这样做相当有道理,因为,同一台电脑上,文件系统的确是不同的进程都能访问的。
另一种套接字是基于网络的,它有自己的家族名字: AF_INET,或叫“地址家族: Internet”。 还有一种地址家族 AF_INET6 被用于网际协议第 6 版(IPv6)寻址上。还有一些其它的地址家族,不 过,它们要么是只用在某个平台上,要么就是已经被废弃,或是很少被使用,或是根本就还没有实现。所有地址家族中,AF_INET 是使用最广泛的一个。Python 2.5 中加入了一种 linux 套接字的支持:AF_NETLINK(无连接[见下])套接字家族让用户代码与内核代码之间的 IPC 可以使用标准 BSD 套接字接口。而且,相对之前那些往操作系统中加入新的系统调用,proc 文件系统支持或是“IOCTL”等笨重的方案来说,这种方法显得更为优美,更为安全。
Python 只支持 AF_UNIX,AF_NETLINK,和 AF_INET 家族。由于我们只关心网络编程,所以在本章的大部分时候,我们都只用 AF_INET
套接字地址:主机与端口

如果把套接字比做电话的插口――即通讯的最底层结构,那主机与端口就像区号与电话号码的一对组合。有了能打电话的硬件还不够,你还要知道你要打给谁,往哪打。一个 Internet 地址由网络通讯所必需的主机与端口组成。而且不用说,另一端一定要有人在听才可以。否则,你就会听到熟悉的声音“对不起,您拨的是空号,请查对后再播”。你在上网的时候,可能也见过类似的情况,如“不能连接该服务器。服务器无响应或不可达”。


合法的端口号范围为 0 到 65535。其中,小于 1024 的端口号为系统保留端口。如果你所使用的是 Unix 操作系统,保留的端口号(及其对应的服务/协议和套接字类型)可以通过/etc/services文件获得。常用端口号列表可以从下面这个网站获得:http://www.iana.org/assignments/port-numbers socket()模块函数 我们先用一个实例来说明socket函数的基本使用,在本实例中 会创建一个 TCP 服务器程序,这个程序会把客户发送过来的字符串加上一个时间戳(格式:'[时间]数据')返回给客户。

TCP 时间戳服务器 (t_server.py):

import socket import time host = '127.0.0.1' port = 21567 bufsiz = 1024 add = (host,port) tcpSerSock = socket.socket() tcpSerSock.bind(add) tcpSerSock.listen(5) while True: print('waiting for connection ... ') tcpCliSock,address = tcpSerSock.accept() print('...connected from : ',address) while True: recv_data = tcpCliSock.recv(bufsiz) recv_data = str(recv_data,encoding='utf-8') if not recv_data: break tcpCliSock.sendall(bytes('[%s] %s' % (time.ctime(),recv_data),encoding='utf-8')) sk.bind(address)

s.bind(address) 将套接字绑定到地址。address地址的格式取决于地址族。在AF_INET下,以元组(host,port)的形式表示地址。

sk.listen(backlog)

开始监听传入连接。backlog指定在拒绝连接之前,可以挂起的最大连接数量。

backlog等于5,表示内核已经接到了连接请求,但服务器还没有调用accept进行处理的连接个数最大为5

这个值不能无限大,因为要在内核中维护连接队列

sk.setblocking(bool)

是否阻塞(默认True),如果设置False,那么accept和recv时一旦无数据,则报错。

sk.accept()

接受连接并返回(conn,address),其中conn是新的套接字对象,可以用来接收和发送数据。address是连接客户端的地址。

接收TCP 客户的连接(阻塞式)等待连接的到来

sk.connect(address)

连接到address处的套接字。一般,address的格式为元组(hostname,port),如果连接出错,返回socket.error错误。

sk.connect_ex(address)

同上,只不过会有返回值,连接成功时返回 0 ,连接失败时候返回编码,例如:10061

sk.close()

关闭套接字

sk.recv(bufsize[,flag])

接受套接字的数据。数据以字符串形式返回,bufsize指定 最多 可以接收的数量。flag提供有关消息的其他信息,通常可以忽略。

sk.recvfrom(bufsize[.flag])

与recv()类似,但返回值是(data,address)。其中data是包含接收数据的字符串,address是发送数据的套接字地址。

sk.send(string[,flag])

将string中的数据发送到连接的套接字。返回值是要发送的字节数量,该数量可能小于string的字节大小。即:可能未将指定内容全部发送。

sk.sendall(string[,flag])

将string中的数据发送到连接的套接字,但在返回之前会尝试发送所有数据。成功返回None,失败则抛出异常。

内部通过递归调用send,将所有内容发送出去。

sk.sendto(string[,flag],address)

将数据发送到套接字,address是形式为(ipaddr,port)的元组,指定远程地址。返回值是发送的字节数。该函数主要用于UDP协议。

sk.settimeout(timeout)

设置套接字操作的超时期,timeout是一个浮点数,单位是秒。值为None表示没有超时期。一般,超时期应该在刚创建套接字时设置,因为它们可能用于连接的操作(如 client 连接最多等待5s )

sk.getpeername()

返回连接套接字的远程地址。返回值通常是元组(ipaddr,port)。

sk.getsockname()

返回套接字自己的地址。通常是一个元组(ipaddr,port)

sk.fileno()

套接字的文件描述符

TCP 时间戳客户端(t_client.py) :
import socket host = '127.0.0.1' port = 21567 bufsiz = 1024 addr = (host,port) tcpCliSock = socket.socket() tcpCliSock.connect(addr) while True: data = input('> ') if not data: break tcpCliSock.sendall(bytes(data,encoding='utf-8')) data = tcpCliSock.recv(bufsiz) recv_data = str(data,encoding='utf-8') if not recv_data: break print(recv_data) tcpCliSock.close() 运行我们的客户端与服务器程序 :

先运行服务端t_server.py文件


Python高手之路【十二】socket网络编程
下面就是客户端的输入与输出,不输入数据,直接按回车键就可以退出程序:
Python高手之路【十二】socket网络编程

上面的实例中,无论发送还是接收,都是以字符串的形式,下面一个实例是发送图片文件及接收图片文件:

1 import socket 2 3 sk = socket.socket() 4 sk.bind(('127.0.0.1',9999,)) 5 sk.listen(5) 6 7 while True: 8 conn,address = sk.accept() 9 conn.sendall(bytes('welcome to conair',encoding='utf-8')) 10 11 #先接收文件大小,然后再开始接收 12 file_size = str(conn.recv(1024),encoding='utf-8') 13 14 #为解决粘包,设置的一个标志 15 conn.sendall(bytes('ko',encoding = 'utf-8')) 16 17 total_size = int(file_size) 18 has_recv = 0 19 20 f = open('new.jpg','wb') 21 22 while True: 23 if total_size == has_recv: 24 break 25 data = conn.recv(1024) 26 f.write(data) 27 has_recv += len(data) 28 f.close() 服务器端文件 1 import socket 2 import os 3 4 obj = socket.socket() 5 obj.connect(('127.0.0.1',9999)) 6 7 ret_bytes = obj.recv(1024) 8 ret_str = str(ret_bytes,encoding='utf-8') 9 print(ret_str) 10 11 #获取图片文件大小,然后发送 12 size = os.stat('06.jpg').st_size 13 obj.sendall(bytes(str(size),encoding='utf-8')) 14 15 obj.recv(1024) 16 17 with open('06.jpg','rb') as f : 18 for line in f: 19 obj.sendall(line) 20 21 obj.close() 客户端文件 socketserver模块实现并发操作

SocketServer内部使用 IO多路复用 以及 “多线程” 和 “多进程” ,从而实现并发处理多个客户端请求的Socket服务端。即:每个客户端请求连接到服务器时,Socket服务端都会在服务器是创建一个“线程”或者“进程” 专门负责处理当前客户端的所有请求。


Python高手之路【十二】socket网络编程
ThreadingTCPServer

ThreadingTCPServer实现的Soket服务器内部会为每个client创建一个 “ 线程 ”,该线程用来和客户端进行交互。

1、ThreadingTCPServer基础

使用ThreadingTCPServer:

创建

Viewing all articles
Browse latest Browse all 9596

Trending Articles