socket相关

socket是操作系统中I/O系统的延伸部分,它可以使进程和机器之间的通信成为可能。建立socket,对于一个客户端程序来说,建立一个socket需要两个步骤。首先,您需要建立一个实际的socket对象。其次,您需要把它链接到远程服务器上。
在建立socket对象的时候,您需要告诉系统两件事:通信类型和协议家族。通信类型指明了用什么协议传输数据。协议的例子包括IPv4、IPv6、IPX/SPX和AFP。到目前为止最通用的是IPv4,洗衣家族则定义数据如何传输通信类型基本上都是AF_INET(和IPv4对应)。协议家族一般是表示TCP通信的SOCK_STREAM或表示UDP通信的SOCK_DGRAM。对于TCP通信,建立一个socket连接,一般用类似这样的代码:

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

连接socket,您一般需要提供一个tuple,它包含远程主机名或IP地址和远程端口。连接一个socket一般用类似这样的代码:

s.connect(("www.example.com", 80))

简单实例:

#!/usr/bin/env python
# -*- coding:utf-8 -*-

import socket

host = ''
port = 51444

# 建立一个socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 把socket设置成可复用的(reusable)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 绑定一个端口
s.bind((host, port))
# 调用listen()函数,这表明已经开始等候来自客户端的连接了,同时设定每次最多
# 只有一个等候处理的链接.真正的服务器会允许一个很高的数字
s.listen(1)

print "Server is running on port %d; press Ctrl-C to terminate." % port

while 1:
    """
    主循环从对accept()函数调用开始.程序会在连接一个客户端后马上停止.当某个客户端
    链接的时候,accept()返回两个信息:一个新的链接客户端的socket和客户端的IP地址,
    端口号.在这个例子中,使用了文件类对象,所以它的工作方式和前面的例子类似.服务器接
    着显示出一些介绍性信息,从客户端读一个字符串,显示一个应答,最后关闭客户端socket.
    这里,关闭socket时很重要的,否则客户端将不知道服务器已经结束通信,而在服务器上会
    堆积很多旧的链接.当使用文件类对象的时候,必须关闭文件对象和socket对象.

    尽管这个基本服务程序可以运行,但它没有什么用处.
    """
    clientsock, clientaddr = s.accept()
    clientfile = clientsock.makefile('rw', 0)
    clientfile.write("Welcome, " + str(clientaddr) + "\n")
    clientfile.write("Please enter a string:")
    line = clientfile.readline().strip()
    clientfile.write("You entered %d characters. \n" % len(line))
    clientfile.close()
    clientsock.close()

在终端中使用 $ telnet localhost 51444 就可以直接连接到51444端口

752372-20160908154900629-671850067.png

寻找端口号:大多说操作系统都会附带提供一份这样的列表,你可以查询一下。Python的socket库包含一个getservbyname()的函数,它可以自动地查询。在UNIX系统中,您总是可以在/etc/services目录下找到这个列表。

为了查询这个列表,您需要两个参数:协议名和端口名。端口名是一个字符串,例如:http可以被转换为一个端口号。下面是一个对前面程序的修改,它使用端口名而不是端口号。

#!/usr/bin/env python
# -*- coding:utf-8 -*-

import socket

print "Creating socket ..."
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print "done"

print "Looking up port number ..."
# 根据端口名,直接获取端口号进行使用
port = socket.getservbyname('http', 'tcp')
print "done"

print "Connecting to romote host on port %d..." % port
s.connect(("www.google.com",port))
print "done"

从socket获取信息

一旦建立了一个socket链接,您就可以从它哪里得到一些有用的信息。

#!/usr/bin/env python
# -*- coding:utf-8 -*-

import socket

print "Creating socket..."
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print "done"

print "Looking up port number..."
port = socket.getservbyname('http', 'tcp')
print "done"

print "Connecting to remote host on port %d..." % port
s.connect(("www.gonghuizhudi.com", port))
print "done"

print "Connected from", s.getsockname()
print "Connected to", s.getpeername()

当运行这个程序的时候,您将看奥两条新的信息。第一条将显示您本身的IP地址和端口;第二条将显示远程机器的IP地址和端口号。还记得在第一章中介绍的,对于客户端来说,端口号是由操作系统分配的(也许是随机的),所以,您也许会发现每次运行这个程序的时候,端口号都不一样。

752372-20160908160918535-629348896.png

利用socket通信

现在和socket的通信建立起来了,是利用它发送和接收数据的时候了。Python提供了两种方法:

socket对象和文件类对象
socket对象提供了操作系统的send()、sendto、recv()和recvfrom()调用的接口。文件类提供了read()、write()和readline()这些更典型的Pythone接口。当您有一些特殊需求的时候,socket对象特别有用。例如:读写数据时、您需要协议可以详细地控制是、使用二进制协议传送固定大小数据时、数据超时需要特殊处理时,socket对象同样是很好的选择。

socket异常
不同的网络调用会产生不同的异常。下面的例子演示了当处理socket对象时,如何捕获每一个普通的异常。这个例子需要3个命令行的参数:一个是想要链接的主机名,一个是服务器上的端口号或名字,一个是想从服务器请求的文件。程序将链接上服务器,针对所请求文件的名字发送一个简单的HTTP请求,显示结果。在整个过程中,它将尝试处理各种类型潜在的错误。

#!/usr/bin/env python
# -*- coding:utf-8 -*-

import socket, sys

# 下面的参数都可从终端中导入参数获得
host = sys.argv[1] # 第一个参数服务器名
textport = sys.argv[2] # 第二个参数,文档内容?
filename = sys.argv[3] # 第三个参数,文件内容

try:
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
except socket.error, e:
    print "Strange error creating socket: %s" % e
    sys.exit(1)

# Try parsing it as a numeric prot number

'''
    4种常见的异常:
    * 与一般I/O和通信问题有关的socket.error
    * 与查询地址信息有关的socket.gaierror
    * 与其他地址错误有关的socket.herror(和C语言中的h_errno相关)
    * 与在一个socket上调用settimeout()后,处理超时有关的socket.timeout

    在例子中,您应该特别注意对connect()的调用.既然程序可以解决把主机名转换成
    IP地址的问题,您实际上会看到两种错误:如果主机名不对则会产生socket.gaierror,
    如果连接远程主机有问题则会产生socket.error.

'''

try:
    port = int(textport)
except ValueError:
    try:
        port = socket.getservbyname(textport, 'tcp')
    except socket.error, e:
        print "Couldn`t find your port: %s" % e
        sys.exit(1)

try:
    s.connect((host, port))
except socket.gaierror, e: # 与查询地址信息有关的socket.gaierror
    print "Address-related error connecting to server: %s" % e
    sys.exit(1)
except socket.error, e: # 与一般I/O和通信问题有关的socket.error
    print "Connection error: %s" % e
    sys.exit(1)

try:
    s.sendall("GET %s HTTP/1.0\r\n\r\n" % filename)
except socket.error, e:
    print "Error sending data: %s" % e
    sys.exit(1)

while 1:
    try:
        buf = s.recv(2048)
    except socket.error, e:
        print "Error receiving data: %s" % e
        sys.exit(1)
    if not len(buf):
        break
    sys.stdout.write(buf)

使用UDP

迄今为止,这一章的内容主要是几种在TCP通信上,除此之外,您还可以使用UDP。UDP通信几乎不使用文件类对象,因为它们往往不能为数据如何发送和接收提供足够的控制。让我们来先介绍一个基本的UDP客户端:

#!/usr/bin/env python
# -*- coding:utf-8 -*-

"""
    这个例子需要两个命令行参数:一个主机名和一个服务器的端口号.它将连接服务器,接着提示您
    输入一行要发送的文字.数据被发送后,它就进入了一个无线循环来等待回复,所以您获取需要用
    Ctrl-C或Ctrl-Break来终止程序.
"""

import socket, sys

host = sys.argv[1]
textport = sys.argv[2]

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
    port = int(textport)
except ValueError:
    # That didn`t work. Look it up instead.
    port = socket.getservbyname(textport, "udp")

s.connect((host, port))
print "Enter data to transmit: "
data = sys.stdin.readline().strip()
s.sendall(data)
print "Looking for replies; press Ctrl-C or Ctrl-Break to stop."
while 1:
    buf = s.recv(2048)
    if not len(buf):
        break
    sys.stdout.write(buf)

这段程序发送一个UDP信息包,接收一个UDP信息包,并继续等候其他的(其他的永远也不会到达)。最后,它被Ctrl-C终止,这导致了keyborardInterrupt。

让我们来看看它和TCP客户端的区别。
首先,请注意当socket被建立的时候,程序调用的是SOCK_DGRAM,而不是SOCK_STREAM;这就会向操作系统提示socket将使用UDP通信,而不是TCP。
其次,对socket.getservbyname()的调用寻找的是UDP端口号,而不是TCP的。一个端口号对协议来说是特殊的,所以即使TCP使用119端口,一个完全不同的UDP应用程序也可以使用同一个端口。
第三,程序没有办法探测到服务器什么时候发送完数据。这是因为其实这里没有实际的链接。对connect()的调用知识初始化了一些内在参数。同时,服务器也许不会返回任何数据,或者数据也许在传输过程中丢失,程序并没有智能地判断出这个问题。因此,当您结束等待传来的信息包时,您必须按下Ctrl-C.

您可以运行第三章中的udpechoserver.py来测试前面代码。就这执行./udp.py localhost 51423 来链接您自己机器上的UDP应答服务器。

有时,使用UDP可以根本就不调用connect()。这里有个示范:

#!/usr/bin/env python
# -*- coding:utf-8 -*-

import socket, sys, struct, time

"""
        这个程序是一个RFC868上定义的简单时间协议的示范.通过调用sendto(),程序
    向tie.nist.gov上的服务器发送一个空字符串.注意这里并没有调用connect().
        尽管这里使用的是recvfrom()而不是recv(),收到的回复也会和平常一样.通常
    来说,当使用不是长链接的UDP通信时,recv()不能提供足够的信息用于通信.实际上,
    对recvfrom()的调用返回一个touple,其中包括两个数据:实际接收数据和发送数据
    的机器地址.在本例中,我们并不关心发送数据的机器地址,所以我们只保存了回复中的
    字符串.服务器则比较关心它,并可以利用这个机制同时处理多个客户端的请求.使用
    recvfrom(),程序收到的回复是从1900年1月1日开始到现在二进制编码的秒数(关于
    这类数据解码的详细介绍,请见地5章).接着程序解开它,通过减去从1900到1970年之
    间的秒数转换成UNIX的时间格式,最后在您的屏幕上打印出来.如果您运行这个程序,
    它将显示出正确的时间.因为UDP并不能保证一个信息包能够成功地传送,同时这个程序
    没有做任何检查的尝试,您或许需要运行好几次才能收到一个回答.此外,由于有些防火
    墙会阻止UDP通信,所以您也许根本收不到任何答复.
"""

hostname = 'time.nist.gov'
port = 37

host = socket.gethostbyname(hostname)

s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.sendto('', (host, port))

print "Looking for replies; press Ctrl-C to stop."
buf = s.recvfrom(2048)[0]
if len(buf) != 4:
    print "Wrong-sized reply %d: %s" % (len(buf), buf)
secs = struct.unpack("!I",buf)[0]
secs -= 2208988800
print time.ctime(int(secs))

总结

网络通信的基本接口是socket,它扩展了操作系统的I/O到网络通信。socket可以通过socket()函数来建立,通过connect()函数来连接。得到了socket,您可以确定本地和远程端点的IP地址和端口号。socket对不同的协议来说都是一种通用的接口,它可以处理TCP和UDP通信。
当和千里之外的机器通信的时候,很多不同的事情会发生错误,所以错误检查是很重要的。绝大多数与网络相关的调用都会产生异常,虽然有时候这些异常不是马上出现。使用shutdown()可以确保当有写错误放生是,您能获得提醒。

Python提供了两种和socket工作的接口:用于UDP和高级TCP目的的标准socket接口,以及用于简单TCP通信的文件类接口。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值