python核心编程--第十六章

本文详细介绍了网络编程的基础知识,包括套接字的概念、类型(面向连接与无连接)、地址、数据传输方式(基于文件与网络),以及Python中的socket模块使用方法,通过实例展示了如何创建TCP和UDP服务器与客户端。

16.2 套接字:通讯端点

16.2.1 什么是套接字?

套接字是一种具有之前所说的“通讯端点”概念的计算机网络数据结构,网络化的应用程序在开始任何通讯之前都必须创建套接字。就像电话的插口一样,没有它就完全没办法通讯。

基于文件型的套接字:由于两个进程都运行在同一台机器上,而且这些套接字是基于文件的,所以它们的底层结构是由文件系统来支持的。这样做相当有道理,因为,同一台电脑上,文件系统的确是不同的进程都能访问的。

基于网络的套接字:家族名为:AF_INET

16.2.2 套接字地址:主机与端口

如果把套接字比作电话的插口--即通讯的最底层结构,那主机与端口就像区号与电话号码的一对组合。

合法的端口号范围为0~65535。其中,小于1024的端口号为系统保留端口。


16.2.3 面向连接与无连接

面向连接

无论你使用哪一种地址家族。套接字的类型只有两种。一种是面向连接的套接字,即在通讯之前一定要建立一条连接,就像跟朋友打电话一样。这种通讯方式也被称为“虚电路”或“流套接字”。面向连接的通讯方式提供了顺序的,可靠的,不会重复的数据传输,而且也不会被加上数据边界。这也意味着,每一个要发送到信息,可能会被拆分成多份,每一份都会不多不少的正确到达目的地。然后被重新按顺序拼接起来,传给正在等待的应用程序。

实现这种连接的主要协议就是传输控制协议(即TCP)。要创建TCP 套接字就得在创建的时候,指定套接字类型为SOCK_STREAM。TCP 套接字采用SOCK_STREAM 这个名字,表达了它做为流套接字的特点。由于这些套接字使用Internet 协议(IP)来查找网络中的主机,这样形成的整个系统,一般会由这两个协议(TCP 和IP)来提及,即TCP/IP。

无连接

与虚电路完全相反的是数据报型的无连接套接字。这意味着,无需建立连接就可以进行通讯。但这时,数据到达的顺序,可靠性及数据不重复性就无法保证了。数据报会保留数据边界,这就表示,数据不会像面向连接的协议那样被拆分成小块。
使用数据报来传输数据就像邮政服务一样。邮件和包裹不一定会按它们发送的顺序到达。事实上,它们还有可能根本到不了!而且,由于网络的复杂性,数据还可能被重复传送。
既然数据报有这么多缺点,为什么还要使用它呢?(一定有什么方面能胜过流套接字的!)由于面向连接套接字要提供一些保证,以及要维持虚电路连接,这都是很重的额外负担。数据报没有这些负担,所以它更“便宜”。通常能提供更好的性能,更适合某些应用场合。
实现这种连接的主要协议就是用户数据报协议(即UDP)。要创建UDP 套接字就得在创建的时候,指定套接字类型为SOCK_DGRAM。SOCK_DGRAM 这个名字,也许你已经猜到了,来自于单词“datagram”(“数据报”)。由于这些套接字使用Internet 协议来查找网络中的主机,这样形成的整个系统,一般会由这两个协议(UDP 和IP)来提及,即UDP/IP。

16.3 python中的网络编程

16.3.1 socket()模块函数

要使用socket.socket()函数来创建套接字。其语法如下:

socket(socket_family, socket_type, protocol=0)

socket_family 可以是AF_UNIX 或AF_INET。socket_type 可以是SOCK_STREAM 或SOCK_DGRAM。这几个常量的意义可以参考之前的解释。protocol 一般不填,默认值为0。

创建一个TCP/IP 的套接字,你要这样调用socket.socket():

tcpSock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

同样地,创建一个UDP/IP 的套接字,你要这样:

udpSock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

由于socket 模块中有太多的属性。我们在这里破例使用了'from module import *'语句。使用'from socket import *',我们就把socket 模块里的所有属性都带到我们的命名空间里了,这样能大幅减短我们的代码。
tcpSock = socket(AF_INET, SOCK_STREAM)

16.3.2 套接字对象(内建)方法

服务器端套接字函数
s.bind()                 绑定地址(主机,端口号对)到套接字
s.listen()               开始TCP 监听
s.accept()             被动接受TCP 客户的连接,(阻塞式)等待连接的到来客户端套接字函数
s.connect()            主动初始化TCP 服务器连接
s.connect_ex() connect()        函数的扩展版本,出错时返回出错码,而不是抛异常


公共用途的套接字函数
s.recv()                 接收TCP 数据
s.send()                 发送TCP 数据
s.sendall()             完整发送TCP 数据
s.recvfrom()         接收UDP 数据
s.sendto()             发送UDP 数据
s.getpeername()     连接到当前套接字的远端的地址
s.getsockname()     当前套接字的地址
s.getsockopt()         返回指定套接字的参数
s.setsockopt()         设置指定套接字的参数
s.close()                 关闭套接字

Blocking-Oriented Socket Methods
s.setblocking()         设置套接字的阻塞与非阻塞模式
s.settimeout()         设置阻塞套接字操作的超时时间
s.gettimeout()         得到阻塞套接字操作的超时时间
面向文件的套接字的函数
s.fileno()             套接字的文件描述符
s.makefile()         创建一个与该套接字关连的文件

16.3.3 创建一个TCP服务器

伪代码:

ss = socket()             # 创建服务器套接字
ss.bind()                     # 把地址绑定到套接字上
ss.listen()                 # 监听连接
inf_loop:                     # 服务器无限循环
cs = ss.accept()             # 接受客户的连接
comm_loop:                 # 通讯循环
cs.recv()/cs.send()         # 对话(接收与发送)
cs.close()                     # 关闭客户套接字
ss.close()                 # 关闭服务器套接字(可选)

核心提示:创建线程来处理客户的请求。

创建一个新的线程或进程来完成与客户的通讯是一种非常常用的手段。SocketServer 模块是一个基于socket 模块的高级别的套接字通讯模块,它支持在新的线程或进程中处理客户的请求。

TCP时间戳服务器

from socket import *
from time import ctime

HOST = ""           #表示bind()函数可以绑定在所有有效的地址上
PORT = 21567
BUFSIZ = 1024
ADDR = (HOST, PORT)

tcpSerSock = socket(AF_INET, SOCK_STREAM)
tcpSerSock.bind(ADDR)
tcpSerSock.listen(5)

while True:
    print "waiting for connection..."
    tcpCliSock, addr = tcpSerSock.accept()
    print "...connected from...", addr

    while True:
        data = tcpCliSock.recv(BUFSIZ)
        if not data:
            break
        tcpCliSock.send("[%s]%s" % (ctime(), data))
    tcpCliSock.close()
tcpSerSock.close()
TCP客户端:
from socket import *
HOST = "192.168.37.21"
PORT = 21567
BUFSIZ = 1024
ADDR = (HOST, PORT)

tcpCliSock = socket(AF_INET, SOCK_STREAM)
tcpCliSock.connect(ADDR)
while True:
    data = raw_input(">")
    if not data:
        break
    tcpCliSock.send(data)
    data = tcpCliSock.recv(BUFSIZ)
    if not data:
        break
    print data
tcpCliSock.close()

程序运行情况:

服务端:

>>> 
waiting for connection...
...connected from... ('192.168.37.197', 1875)
waiting for connection...
客户端:
>>> 
>hello server
[Sat Jun 22 16:22:53 2013]hello server
>my name is fzyz.abc
[Sat Jun 22 16:22:57 2013]my name is fzyz.abc
>
核心提示:优美的退出和调用服务器的close()函数
“友好地”退出的一个方法就是把服务器的无限循环放在一个try-except 语句的try 子句当中,并捕获EOFError 和KeyboardInterrupt 异常。在异常处理子句中,调用close()函数关闭服务器的套接字。

16.3.6 创建一个UDP服务器

伪代码:

ss = socket() # 创建一个服务器套接字
ss.bind() # 绑定服务器套接字
inf_loop: # 服务器无限循环
cs = ss.recvfrom()/ss.sendto() # 对话(接收与发送)
ss.close() # 关闭服务器套接字
UDP服务器:
from socket import *
from time import ctime

HOST = ""
PORT = 21567
BUFSIZ = 1024
ADDR = (HOST, PORT)

udpSerSock = socket(AF_INET, SOCK_DGRAM)
udpSerSock.bind(ADDR)

while True:
    print "waiting for message..."
    data, addr = udpSerSock.recvfrom(BUFSIZ)
    udpSerSock.sendto("[%s]%s" % (ctime(), data), addr)
    print "...received from and returned to:", addr
udpSerSock.close()
客户端:
from socket import *
HOST = "192.168.37.21"
PORT = 21567
BUFSIZ = 1024
ADDR = (HOST, PORT)

udpCliSock = socket(AF_INET, SOCK_DGRAM)

while True:
    data = raw_input(">")
    if not data:
        break
    udpCliSock.sendto(data, ADDR)
    data, ADDR = udpCliSock.recvfrom(BUFSIZ)
    if not data:
        break
    print data
udpCliSock.close()
程序输出:

服务端:

>>> 
waiting for message...
...received from and returned to: ('192.168.37.197', 62126)
waiting for message...
...received from and returned to: ('192.168.37.197', 62126)
waiting for message...
客户端:
>>> 
>hello server
[Sat Jun 22 16:26:47 2013]hello server
>my name is fzyz.abc
[Sat Jun 22 16:26:51 2013]my name is fzyz.abc
>


16.3.9 套接字模块属性

数据属性
AF_UNIX, AF_INET, AF_INET6        Python 支持的套接字家族
SO_STREAM, SO_DGRAM             套接字类型 (TCP = 流, UDP = 数据报)
has_ipv6b                                 表示是否支持IPv6 的标志变量

异常
error                                         套接字相关错误
herror                                         主机和地址相关的错误
gaierror                                     地址相关的错误
timeout                                     超时

函数
socket()                                  用指定的地址家族,套接字类型和协议类型(可选)创建一个套接字对象
socketpair()                             用指定的地址家族,套接字类型和协议类型(可选)创建一对套接字对象
fromfd()                                  用一个已经打开的文件描述符创建一个套接字对象

数据属性
ssl()                                     在套接字初始化一个安全套接字层(SSL)。不做证书验证。
getaddrinfo()                         得到地址信息
getfqdn()                             返回完整的域的名字
gethostname()                         得到当前主机名

gethostbyname()                     由主机名得到对应的ip 地址
gethostbyname_ex()                gethostbyname()的扩展版本,返回主机名,主机所有的别名和IP 地址表。
gethostbyaddr()                     由IP 地址得到DNS 信息,返回一个类似gethostbyname_ex()的3 元组。
getprotobyname()                     由协议名(如'tcp')得到对应的号码。
getservbyname()/                     由服务名得到对应的端口号或相反
getservbyport()                         两个函数中,协议名都是可选的。
ntohl()/ntohs()                         把一个整数由网络字节序转为主机字节序
htonl()/htons()                         把一个整数由主机字节序转为网络字节序
inet_aton()/                             把IP 地址转为32 位整型,以及反向函数。(仅对IPv4 地址有效)
inet_ntoa()
inet_pton()/                             把IP 地址转为二进制格式以及反向函数。(仅对IPv4 地址有效)
inet_ntop()
getdefaulttimeout()/                 得到/设置默认的套接字超时时间,单位秒(浮点数)
setdefaulttimeout()


16.4 SocketServer模块

类                                        描述

BaseServer                           包含服务器的核心功能与混合类的钩子功能。这个类用于派生,不要直接生成
TCPServer/UDPServer              基本的网络同步TCP/UDP服务器

UnixStreamServer/UnixDatagramerver    基本的基于文件同步TCP/UDP服务器

ForkingMixIn                            实现了核心的进程化或线程化的功能,用于与服务器类进行混合,以提些

                                                一些异步的特性

ThreadingMixIn                        不要直接生成这个类的对象

ForkingTCPServer/ForkingUDPServer    ForkingMixIn和TCPServer/UDPServer的组合

ThreadingTCPServer/ThreadingUDPServer    ThreadingMixIn和TCPServer/UDPServer的组合

BaseRequesHandler            包含处理服务请求的和性功能,只用于派生新的类,不要直接生成这个类的对象

StreamRequestHandler/DatagramRequestHandler    TCP/UDP服务器的请求处理类的一个实现

16.4.1 创建一个SocketServerTCP服务器

服务器:

from SocketServer import (TCPServer as TCP, StreamRequestHandler as SRH)
from time import ctime

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

class MyRequestHandler(SRH):
    def handle(self):
        print "...connected from:", self.client_address
        self.wfile.write("[%s]%s" % (ctime(), self.rfile.readline()))
        
tcpServ = TCP(ADDR, MyRequestHandler)
print "waiting for connection.."
tcpSer.serve_forever()
主要的工作:

我们从SocketServer 的StreamRequestHandler 类中派生出一个子类,并重写handle()函数。在BaseRequest 类中,这个函数什么也不做:

def handle(self):
    pass
在有客户消息进来的时候,handle()函数就会被调用。StreamRequestHandler 类支持像操作文件对象那样操作输入输出套接字。我们可以用readline()函数得到客户消息,用write()函数把字符串发给客户。

16.4.2 创建SocketServerTCP客户端

from socket import *
HOST = "192.168.37.21"
PORT = 21567
BUFSIZ = 1024
ADDR = (HOST, PORT)

while True:
    tcpCliSock = socket(AF_INET, SOCK_STREAM)
    tcpCliSock.connect(ADDR)
    data = raw_input(">")
    if not data:
        break
    tcpCliSock.send("%s\r\n" % data)
    data = tcpCliSock.recv(BUFSIZ)
    if not data:
        break
    print data.strip()
tcpCliSock.close()
运行程序:

服务器:

>>> 
waiting for connection..
...connected from: ('192.168.37.197', 2067)
...connected from: ('192.168.37.197', 2068)
...connected from: ('192.168.37.197', 2069)
客户端:
>>> 
>hello server
[Sat Jun 22 17:05:44 2013]hello server
>my name is fzyz.abc
[Sat Jun 22 17:05:47 2013]my name is fzyz.abc
>

twisted框架不知道为什么,安装不成功,所以无法编写twisted的程序。

16-1 套接字。面向连接和无连接有什么区别?

简单来说就是:一个为了可靠性而设计,一个为了方便性而设计。

16-2. 客户/服务器架构。用你自己的语言描述这个架构,并给出几个例子。

服务器建立连接,不断轮询客户端的请求,接收客户端的请求并响应起请求。客户端请求连接服务器,请求成功后进行通信。

16-3. 套接字。TCP 和UDP 中,哪一种服务器在接受连接后,把连接交给不同的套接字处理与客户的通讯。

TCP。

16-4. 客户。修改TCP(tsTclnt.py)和UDP(tsUclnt.py)客户端,让服务器的名字不要在代码里写死,要允许用户指定一个主机名和端口,只有在两个值都没有输入的时候,才使用默认值。

服务端:

from socket import *
from time import ctime

HOST = raw_input("please enter the host:")
PORT = int(raw_input("please enter the port:"))
BUFSIZ = 1024
ADDR = (HOST, PORT)

tcpSerSock = socket(AF_INET, SOCK_STREAM)
tcpSerSock.bind(ADDR)
tcpSerSock.listen(5)

while True:
    print "waiting for connection...."
    tcpCliSock, addr = tcpSerSock.accept()
    print "...connected from:", addr

    while True:
        data = tcpCliSock.recv(BUFSIZ)
        if not data:
            break
        print "...recieve data:", data
        tcpCliSock.send("[%s]%s" % (ctime(), data))
    tcpCliSock.close()
tcpSerSock.close()
客户端:

from socket import *
from time import ctime

HOST = raw_input("please enter the host:")
PORT = int(raw_input("please enter the port:"))
BUFSIZ = 1024
ADDR = (HOST, PORT)

tcpCliSock = socket(AF_INET, SOCK_STREAM)
tcpCliSock.connect(ADDR)

while True:
    data = raw_input(">")
    if not data:
        break
    tcpCliSock.send(data)
    data = tcpCliSock.recv(BUFSIZ)
    if not data:
        break
    print data
tcpCliSock.close()
程序输入输出:

服务端:

>>> 
please enter the host:192.168.37.21
please enter the port:23456
waiting for connection....
...connected from: ('192.168.37.197', 1842)
...recieve data: hello server
...recieve data: my name is fzyz.abc
...recieve data: ok, that is it.goobye
waiting for connection....
服务端一直在死循环中。。。

客户端:

>>> 
please enter the host:192.168.37.21
please enter the port:23456
>hello server
[Sat Jun 22 20:30:20 2013]hello server
>my name is fzyz.abc
[Sat Jun 22 20:30:25 2013]my name is fzyz.abc
>ok, that is it.goobye
[Sat Jun 22 20:30:39 2013]ok, that is it.goobye
>

16-6. 日期时间服务。使用socket.getservbyname()函数得到UDP 协议中,“daytime”服务所对应的端口。请参考getservbyname() 函数的文档, 查阅详细的语法。( 即:socket.getservbyname.__doc__)。现在,写一个程序发送一个随便什么数据过去,等待回答。一旦你收到了服务器的信息,显示到屏幕上。
烦躁,自己写的程序不过不能成功运行。。。。。

先列出来吧:

服务端:

from socket import *
from time import ctime

HOST = "localhost"
PORT = getservbyname("daytime","udp")
BUFSIZ = 1024
ADDR = (HOST, PORT)

udpSerSock = socket(AF_INET, SOCK_DGRAM)
udpSerSock.bind(ADDR)
while True:
    print "waiting for message..."
    data, addr = udpSerSock.recvfrom(BUFSIZ)
    udpSerSock.sendto("[%s]%s" % (ctime(), data))
    print "...received from and returned to:", addr
udpSerSock.close()
客户端:

from socket import *
from time import ctime

HOST = "192.168.37.21"
PORT = getservbyname("daytime","udp")
BUFSIZ = 1024
ADDR = (HOST, PORT)

udpCliSock = socket(AF_INET, SOCK_DGRAM)

while True:
    data = raw_input(">")
    if not data:
        break
    udpCliSock.sendto(data, ADDR)
    data, ADDR = udpCliSock.recvfrom(BUFSIZ)
    if not data:
        break
    print data
tcpCliSock.close()
烦躁!!!!!

16-7. 半双工聊天。创建一个简单的,半双工的聊天程序。“半双工”的意思是当创建一个连接,服务启动的时候,只有一个人可以打字,另一个人只有在等到有消息通知他输入消息时,才能说话。一旦消息发送出去后,要等到有回复了才能发送下一条消息。一个人是服务端,另一个人是客户端。
写了这个程序,感觉好滑稽:

服务端:

from socket import *

HOST = "192.168.37.21"
PORT = 23346
BUFSIZ = 1024
ADDR = (HOST, PORT)

tcpServSock = socket(AF_INET, SOCK_STREAM)
tcpServSock.bind(ADDR)
tcpServSock.listen(5)

while True:
    print "waiting for connection..."
    tcpCliSock, addr = tcpServSock.accept()
    print "connected from:", addr
    while True:
        data = tcpCliSock.recv(BUFSIZ)
        if not data:
            continue
        print "client>", data
        data = raw_input("server>")
        tcpCliSock.send(data)
    tcpCliSock.close()
tcpServSock.close()
客户端:

from socket import *

HOST = "192.168.37.21"
PORT = 23346
BUFSIZ = 1024
ADDR = (HOST, PORT)

tcpCliSock = socket(AF_INET, SOCK_STREAM)
tcpCliSock.connect(ADDR)

while True:
    data = raw_input("client>")
    if not data:
        continue
    tcpCliSock.send(data)
    while True:
        data = tcpCliSock.recv(BUFSIZ)
        if not data:
            continue
        else:
            print "server>", data
            break
tcpCliSock.close()
程序输出:

服务端:

>>> 
waiting for connection...
connected from: ('192.168.37.197', 2089)
client> hello server
server>hello client
client> my name is client
server>my name is server
client> ok
server>ok
客户端:

>>> 
client>hello server
server> hello client
client>my name is client
server> my name is server
client>ok
server> ok
client>

16-8. 全双工聊天。修改你刚才的程序,改成全双工,即两个人可以独立地发送和接收消息。

貌似要用到线程,刚才弄了一下,虽然用到了非阻塞,但是感觉好乱,界面显示的好乱。。。。

我发现需要有人带,否则太慢了,而且遇到问题也不知道怎么解决。。。

真心郁闷。。。。。

算了,网络编程这章先不看吧,真心郁闷。

求人带,一个人摸索太累了。



转载于:https://my.oschina.net/voler/blog/139088

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值