python的学习之路day7-socket网络编程

python基础部分学习完了,时间也已经过了两个月左右,感觉没学到什么,可能是我学习之后忘记的太多了。

由于没钱买书,要是去培训就更没钱了,所以在网上找了一本书,感觉还不错,讲的比较好,比较详细。

Python核心编程(第3版)PDF高清晰完整中文版 ,需要的小伙伴可以去下载,当然如果大家不像我这么穷逼,也可以去网上购买。

 

入门半小时,精通一 杯子” !!!

接下来继续更新Python

 

大纲

1、一些原理及解释

客户端/服务器的架构原理

客户端/服务器的网络编程

套接字:通信端点

2、Python中的网络编程

 socket()模块函数

套接字对象(内置)方法

创建一个简单的TCP服务器以及客户端

创建一个简单的UDP服务器以及客户端

3、socket()模块属性

4、SocketServer模块

SocketServer模块类

SocketServerTCP服务器和客户端(这个地方的代码有问题,如果有大神路过,希望能帮我看看,谢谢了)

 

一些原理及解释:

客户端/服务器的架构原理

什么是客户端/服务器架构?对于不同的人来说,它意味着不同的东西,这取决于你问谁以及描述的是软件还是硬件系统。在这两种情况中的任何一种下,前提都很简单:服务器就是一系列硬件或软件,为一个或多个客户端(服务的用户)提供所需的“服务”。它存在唯一目的就是等待客户端的请求,并响应它们(提供服务),然后等待更多请求。另一方面,客户端因特定的请求而联系服务器,并发送必要的数据,然后等待服务器的回应,最后完成请求或给出故障的原因。服务器无限地运行下去,并不断地处理请求;而客户端会对服务进行一次性请求,然后接收该服务,最后结束它们之间的事务。客户端在一段时间后可能会再次发出其他请求,但这些都被当作不同的事务。

客户端/服务器的网络编程

在服务器响应客户端请求之前,必须进行一些初步的设置流程来为之后的工作做准备。首先会创建一个通信端点,它能够使服务器监听请求。可以把服务器比作公司前台,或者应答公司主线呼叫的总机接线员。一旦电话号码和设备安装成功且接线员到达时,服务就可以开始了。

这个过程与网络世界一样,一旦一个通信端点已经建立,监听服务器就可以进入无限循环中,等待客户端的连接并响应它们的请求。当然,为了使公司电话接待员一直处于忙碌状态,我们绝不能忘记将电话号码放在公司信笺、广告或一些新闻稿上;否则,将没有人会打电话过来!
相似地,必须让潜在的客户知道存在这样的服务器来处理他们的需求;否则,服务器将永远不会得到任何请求。想象着创建一个全新的网站,这可能是最了不起的、劲爆的、令人惊异的、有用的并且最酷的网站,但如果该网站的 Web 地址或 URL 从来没有以任何方式广播或进行广告宣传,那么永远也不会有人知道它,并且也将永远不会看到任何访问者。现在你已经非常了解了服务器是如何工作的,这就已经解决了较困难的部分。客户端比服务器端更简单,客户端所需要做的只是创建它的单一通信端点,然后建立一个到服务器的连接。然后,客户端就可以发出请求,该请求包括任何必要的数据交换。一旦请求被服务器处理,且客户端收到结果或某种确认信息,此次通信就会被终止。

套接字:通信端点

套接字是计算机网络数据结构,它体现了上节中所描述的“通信端点”的概念。在任何类型的通信开始之前,网络应用程序必须创建套接字。可以将它们比作电话插孔,没有它将无法进行通信。
套接字的起源可以追溯到 20 世纪 70 年代,它是加利福尼亚大学的伯克利版本 UNIX(称为 BSD UNIX)的一部分。因此,有时你可能会听过将套接字称为伯克利套接字或 BSD 套接字。套接字最初是为同一主机上的应用程序所创建,使得主机上运行的一个程序(又名一个进程)与另一个运行的程序进行通信。这就是所谓的进程间通信(Inter Process Communication,IPC)。有两种类型的套接字:基于文件的和面向网络的。
UNIX 套接字是我们所讲的套接字的第一个家族,并且拥有一个“家族名字”AF_UNIX(又名 AF_LOCAL,在 POSIX1.g 标准中指定),它代表地址家族(address family):UNIX。包括 Python 在内的大多数受欢迎的平台都使用术语地址家族及其缩写 AF;其他比较旧的系统可能会将地址家族表示成域(domain)或协议家族(protocol family),并使用其缩写 PF 而非 AF。类似地,AF_LOCAL(在 2000~2001 年标准化)将代替 AF_UNIX。然而,考虑到后向兼容性,很多系统都同时使用二者,只是对同一个常数使用不同的别名。Python 本身仍然在使用 AF_UNIX。

因为两个进程运行在同一台计算机上,所以这些套接字都是基于文件的,这意味着文件系统支持它们的底层基础结构。这是能够说得通的,因为文件系统是一个运行在同一主机上的多个进程之间的共享常量。第二种类型的套接字是基于网络的,它也有自己的家族名字 AF_INET,或者地址家族:因特网。另一个地址家族 AF_INET6 用于第 6 版因特网协议(IPv6)寻址。此外,还有其他的地址家族,这些要么是专业的、过时的、很少使用的,要么是仍未实现的。在所有的地址家族之中,目前 AF_INET 是使用得最广泛的。
Python 2.5 中引入了对特殊类型的 Linux 套接字的支持。套接字的 AF_NETLINK 家族(无连接[见2.3.3节])允许使用标准的BSD套接字接口进行用户级别和内核级别代码之间的IPC。
之前那种解决方案比较麻烦,而这个解决方案可以看作一种比前一种更加优雅且风险更低的解决方案,例如,添加新系统调用、/proc 支持,或者对一个操作系统的“IOCTL”。针对 Linux 的另一种特性(Python 2.6 中新增)就是支持透明的进程间通信(TIPC)协议。TIPC 允许计算机集群之中的机器相互通信,而无须使用基于 IP 的寻址方式。Python 对TIPC 的支持以 AF_TIPC 家族的方式呈现。
总的来说,Python 只支持 AF_UNIX、AF_NETLINK、AF_TIPC 和 AF_INET 家族。

 

Python中的网络编程

socket()模块函数

要创建套接字,必须使用socket.socket()函数

如何创建socket()函数:
socket(socket_family, socket_type, protocol=0)
socket_family是AF_UNIX或AF_INET(AF_INET6)(ipv4和ipv6)
socket_type是SOCK_STREAM或SOCK_DGRAM(前者表示TCP,后者表示UDP)
protocol通常省略,默认为0

import socket

# 创建TCP/IP套接字:
tcpsocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 创建UDP/IP套接字:
udpsocket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

有很多socket模块属性,所以可以使用“from socket import *”:

from socket import * tcpsocket = socket(AF_INET, SOCK_STREAM)

 

一旦有了一个套接字对象,那么使用套接字对象的方法将可以进行进一步的交互

 

套接字对象(内置)方法:

名称

描述

服务器套接字方法

s.bind()

将地址(主机名、端口号对)绑定到套接字上

s.listen()

设置并启动TCP监听器

s.accept()

被动接受TCP客户端连接,一直等待直到连接到达(阻塞)

客户端套接字方法

s.connect()

主动发起TCP服务器连接

s.connect_ex()

Connect()的扩展版本,此时会以错误码的形式返回问题,而不是抛出一个异常

普通的套接字方法

s.recv()

接收TCP消息

s.recv_into()

接收TCP消息到指定的缓冲区

s.send()

发送TCP消息

s.sendall()

完整地发送TCP消息

s.recvfrom()

接收UDP消息

s.recvfrom_into()

接收UDP消息到指定的缓冲区

s.sendto()

发送UDP消息

s.getpeername()

连接到套接字(TCP)的远程地址

s.getsockname()

当前套接字的地址

s.getsockopt()

返回给定套接字选项的值

s.setsockopt()

设置给定套接字选项的值

s.shutdown()

关闭连接

s.close()

关闭套接字

s.detach()

在未关闭文件描述符的情况下关闭套接字,返回文件描述符

s.ioctl()

控制套接字的模式(仅支持Windows)

面向阻塞的套接字方法

s.setblocking()

设置套接字的阻塞或非阻塞模式

s.settimeout()

设置阻塞套接字操作的超时时间

s.gettimeout()

获取阻塞套接字操作的超时时间

面向文件的套接字方法

s.fileno()

套接字的文件描述符

s.makefile()

创建与套接字关联的文件对象

数据属性

s.family

套接字家族

s.type

套接字类型

s.proto

套接字协议

 

创建一个简单的TCP服务器以及客户端

服务器端:

# TCP时间戳服务器的伪代码
# import socket
# ss = socket.socket()          #创建服务器套接字
# ss.bind()                     #套接字与地址绑定
# ss.listen()                   #监听连接
# inf_loop:                     #服务器无限循环
#     cs = ss.accept()          #接收客户端连接
#     comm_loop:                #通信循环
#         cs.recv()/cs.send()   #对话(接收/发送)
#     cs.close()                #关闭客户端套接字
# ss.close()                    #关闭服务器套接字(可选)

import socket
import time  # 分别导入两个模块

HOST = ''  # 这是对bind()方法的标识,表示可以使用任何可用的地址
PORT = 1504  # 端口
BUFSIZ = 1024  # 设置缓存区大小
ADDR = (HOST, PORT)
tcp_ser_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
tcp_ser_sock.bind(ADDR)  # 将(地址、端口号)绑定到套接字在上
tcp_ser_sock.listen(5)
while True:  # 进入一个无限的循环
    print("waiting for connection...")  # 提示信息
    tcp_cli_sock, addr = tcp_ser_sock.accept()  # 被动的等待客户端连接
    print("...connected from: ", addr)  # 某个客户端已连接
    while True:  # 进入一个对话循环
        data = tcp_cli_sock.recv(BUFSIZ).decode()  # 接收TCP消息
        if not data:
            break  # 如果接收的消息为空,跳出当前循环,继续等待
        tcp_cli_sock.send(('[%s] %s' % (time.ctime(), data)).encode())  # 发送接收的消息,并且在前面加上当前时间
    tcp_cli_sock.close()  # 关闭客户端

客户端:

# 伪代码
# import socket
# cs = socket.socket()    #创建客户端套接字
# cs.connect()            #尝试连接服务器
# comm_loop:              #通信循环
#     cs.send()/cs.recv() #对话(发送/接收)
# cs.close()              #关闭客户端套接字

import socket

HOST = 'localhost'
PORT = 1504
BUFSIZ = 1024
ADDR = (HOST, PORT)
tcp_cli_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
tcp_cli_sock.connect(ADDR)  # 主动发起TCP服务器连接

while True:
    data = input(">>>")
    if not data:  # 判断输入是否为空
        break
    tcp_cli_sock.send(data.encode())  # 发送输入的内容
    data = tcp_cli_sock.recv(BUFSIZ).decode()  # 接收返回的内容
    if not data:  # 判断返回的内容是否为空
        break
    print(data)  # 将返回的内容打印出来
tcp_cli_sock.close()  # 然后关闭连接

 输出:

服务器:
waiting for connection...
...connected from:  ('127.0.0.1', 52064)
waiting for connection...

客户端:
>>>qwe
[Thu Jan 18 20:09:59 2018] qwe
>>>456
[Thu Jan 18 20:10:04 2018] 456
>>>

 

当我们从服务器退出时,必须跳出它,这就会导致一个异常。为了避免这种错误,最好的方式就是创建一种更优雅的退出方式

核心提示:优雅地退出和调用服务器 close()方法
在开发中,创建这种“友好的”退出方式的一种方法就是,将服务器的 while 循环放在一个 try-except 语句中的 except 子句中,并监控 EOFError 或 KeyboardInterrupt 异常,这样你就可以在 except 或 finally 字句中关闭服务器的套接字。在生产环境中,你将想要能够以一种更加自动化的方式启动和关闭服务器。在这些情况下,需要通过使用一个线程或创建一个特殊文件或数据库条目来设置一个标记以关闭服务。

 

创建一个简单的UDP服务器以及客户端

服务器端:

# 伪代码:
# ss = socket.socket()                  #创建服务器套接字
# ss.bind()                             #绑定服务器套接字
# inf_loop:                             #服务器无限循环
#     cs = cs.recvfrom()/ss.sendto()    #对话(接收/发送)
# ss.close()                            #关闭服务器套接字

import socket
import time  # 分别导入两个模块

HOST = ''  # 主机地址
PORT = 1504  # 端口号
BUFSIZ = 1024  # 缓存区大小
ADDR = (HOST, PORT)
udp_ser_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)  # 使用udp协议
udp_ser_sock.bind(ADDR)  # 将地址和端口绑定到套接字上
# 可以看到没有listen,因为udp是无连接的,所以不用调用“监听传入连接”
while True:  # 进入一个无限循环
    print("waiting for massage...")  # 提示输出
    data, addr = udp_ser_sock.recvfrom(BUFSIZ)  # 接收UDP消息
    udp_ser_sock.sendto(("[%s] %s" % (time.ctime(), data.decode("utf-8"))).encode(), addr)  # 将接收到的消息加上时间发送给客户端
    print("...received from and returned to:", addr)  # 打印已发送给谁
udp_ser_sock.close()

 客户端:

# 伪代码
# cs = socket()                     #创建客户端套接字
# comm_loop:                        #通信循环
#     cs.sendto()/cs.recvfrom()     #对话(发送/接收)
# cs.close()                        #关闭客户端套接字

import socket

HOST = 'localhost'  # 地址
PORT = 1504  # 端口
BUFSIZ = 1024  # 缓存
ADDR = (HOST, PORT)
udp_cli_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)  # 使用udp协议
while True:  # 进入一个无限循环
    data = input(">>>")  # 输入
    if not data:  # 判断输入是否为空
        break
    udp_cli_sock.sendto(data.encode(), ADDR)  # 将输入的内容和本地的地址和端口发送到服务器
    data, ADDR = udp_cli_sock.recvfrom(BUFSIZ)  # 接收服务器传回来的内容
    if not data:
        break
    print(data.decode("utf-8"))  # 打印
udp_cli_sock.close()

 输出:

服务器:
waiting for massage...
...received from and returned to: ('127.0.0.1', 64721)
waiting for massage...
...received from and returned to: ('127.0.0.1', 64722)
waiting for massage...


客户端:
>>>asd
[Fri Jan 19 14:21:47 2018] asd
>>>asd
[Fri Jan 19 14:21:51 2018] qwe

 

socket模块属性

 

SocketServer模块

SocketServer 是标准库中的一个高级模块(Python 3.x 中重命名为 socketserver),它的目标是简化很多样板代码,它们是创建网络客户端和服务器所必需的代码。这个模块中有为你创建的各种各样的类;

SocketServer模块类

 

描述

BaseServer

包含核心服务器功能和 mix-in 类的钩子;仅用于推导,这样不会创建这个类的实例;可以用 TCPServer 或 UDPServer 创建类的实例

TCPServer/UDPServer

基础的网络同步 TCP/UDP 服务器

UnixStreamServer/UnixDatagramServer

基于文件的基础同步 TCP/UDP 服务器

ForkingMixIn/ThreadingMixIn

核心派出或线程功能;只用作 mix-in 类与一个服务器类配合实现一些异步性;不能直接实例化这个类

ForkingTCPServer/ForkingUDPServer

ForkingMixIn 和 TCPServer/UDPServer 的组合

ThreadingTCPServer/ThreadingUDPServer

ThreadingMixIn 和 TCPServer/UDPServer 的组合

BaseRequestHandler

包含处理服务请求的核心功能;仅仅用于推导,这样无法创建这个类的实例;可以使用 StreamRequestHandler 或 DatagramRequestHandler 创建类的实例

StreamRequestHandler/DatagramRequestHandler

实现 TCP/UDP 服务器的服务处理器

 

SocketServerTCP服务器和客户端(表示没有成功,书上写的python2,我用的是python3,不知道如何改代码,如果有哪位大神路过看到能否救救我,谢谢了)

服务器端:

import socketserver
import time

HOST = ""
PORT = 1504
ADDR = (HOST, PORT)


class my_request_handler(socketserver.StreamRequestHandler):
    def handle(self):
        print("...connected from:", self.client_address)
        self.wfile.write('[%s] %s' % (time.ctime(), self.rfile.readline()))


tcp_serv = socketserver.TCPServer(ADDR, my_request_handler)
print("waiting for connection...")
tcp_serv.serve_forever()

 客户端:

import socket

HOST = "localhost"
PORT = 1504
BUFSIZ = 1024
ADDR = (HOST, PORT)
while True:
    tcp_cli_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    tcp_cli_sock.connect(ADDR)
    data = input(">>>")
    if not data:
        break
    tcp_cli_sock.send(data.encode())
    data = tcp_cli_sock.recv(BUFSIZ).decode()
    if not data:
        break
    print(data.strip())
    tcp_cli_sock.close()

 输出:

服务器:
waiting for connection...
...connected from: ('127.0.0.1', 59956)

客户端:
>>>sdas
就这样一直卡着

如果像书上一样:
TypeError: a bytes-like object is required, not 'str'

 

posted @ 2018-01-16 20:07 smelond 阅读(...) 评论(...) 编辑 收藏