一,理解socket
socket
是操作系统中I/O系统的延伸部分, 它可以使进程和机器之间的通信成为可能。
当前经常使用的socket最早起源于BSD UNIX类的操作系统。 在UNIX系统上, 比如BSD,有一些现有的、 和文件描述符
一起工作的系统调用
, 其中包括 open()
、read()
、write()
和close()
。文件描述符一般是指一个文件或某个类似文件的实体。
把对网络的支持加入操作系统, 是以一种扩展
现有文件描述符结构的方法来实现的。新的系统调用被加入并和socket 一起工作, 而很多现有的系统调用同样能和socket 一起工作。因此,一个socket允许使用标准的操作系统和其他的计算机, 以及自己机器上的不同进程来通信。
socket可以被看成一个标准的文件描述符。在类UNIX的平台上,read()、write()、dup()、dup2()和close()这样的系统调用会像为标准文件描述符那样为socket工作。
很多文件是通过调用open()函数来打开的,但socket是通过调用socket()
函数来建立的, 并且还需要另外的调用来连接
和激活
它们。
Python通过socket模块提供访问操作系统socket库的接口。
二,建立socket
对于一个客户端程序
来说, 建立一个socket需要两个步骤:
- 建立一个实际的socket对象
- 把它连接到远程服务器上
建立socket对象,调用socket.socket(family=AF_INET, type=SOCK_STREAM, proto=0, fileno=None)方法。
family
地址簇:指明用什么地址簇来传输数据。地址簇的选项包括IPv4 对应的AF_INTE
、IPv6 对应的AF_INET6
等选项。type
套接字类型:指明用什么协议来传输数据。协议的选项包括TCP 协议对应的SOCK_STREAM
、UDP 协议对应的SOCK_DGRAM
等选项。
连接socket,调用socket.connect(address)
方法连接到 address 处的远程套接字。
- 需要提供一个包含远程主机名或IP地址和远程端口的元组:
s.connect(("www.baidu.com", 80))
完整的例子:
import socket
print('Creating socket...')
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print('done!')
print('Connecting to remote host...')
s.connect(("www.baidu.com", 80))
print('done!')
1,寻找端口号
Python的socket库包含一个getservbyname()
的函数, 它可以自动地查询已知服务器端口号的列表。
为了查询这个列表, 需要两个参数: 协议名
和端口名
,
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.baidu.com", port))
print('done!')
- 不需要事先知道HTTP使用80端口, 尽管可以查找并直接把端口号写在程序中,但是对于交互式程序,让用户能看到文字形式的端口描述将是非常好的。
2,从socekt获取信息
一旦建立了一个socket连接。就可以从它那里得到一些有用的信息:
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.baidu.com", port))
print('done!')
print('Connected from ' + str(s.getsockname()))
print('Connected to ' + str(s.getpeername()))
更多socket对象方法:socket-objects。
三,利用socket通信
Python提供了两种方法来利用socket发送和接收数据:
socket对象
:提供了操作系统的 send()、 sendto()、 recv() 和 recvfrom() 调用的接口。文件类对象
:提供了 read()、 write() 和 readline() 这些更典型的Python接口。
两种方法的选择:
- 读写数据时、 需要协议可以详细地控制时、 使用二进制协议传送固定大小数据时、 数据超时需要特殊处理时、再或者是任何不止需要简单读写时。 当您编写UDP程序的时候, socket对象同样是很好的选择。
- 文件类对象一般用于面向线性的协议, 因为它能通过提供的 readline() 函数自动地为您处理大多数的解析。 然而,文件类对象一般只对TCP连接工作得很好, 对UDP连接反而不是很好。这是因为TCP连接的行为更像是标准的文件, 它们保证数据接收的精确性, 并且和文件一样是以字节流形式运转的。 而UDP并不像文件那样以字节流形式运转。 相反, 它是一种基于信息包的通信。 文件类对象没有办法操作每个基本的信息包。
四,处理错误
1,socket异常
不同的网络调用会产生不同的异常。
#!/usr/bin/python3.6
import socket
import sys
host = sys.argv[1]
textport = sys.argv[2]
filename = sys.argv[3]
try:
print('Creating socket...')
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print('done!')
except socket.error as e:
print('Strange error creating socket: %s' % e)
sys.exit(1)
# Try parsing it as a numeric port number,
try:
port = int(textport)
except ValueError:
# That didn't work, so it's probably a protocol name.
# Look it up instead.
try:
print('Looking up port number ...')
port = socket.getservbyname(textport, 'tcp')
print('done!')
except socket.error as e:
print("Couldn't find your port: %s" % e)
sys.exit(1)
try:
print('Connecting to remote host %s on port %d' % (host, port))
s.connect((host, port))
print('done!')
except socket.gaierror as e:
print("Address-related error connecting to server: %s" % e)
sys.exit(1)
except socket.error as e:
print("Connection error: %s" % e)
sys.exit(1)
try:
http_content = ("GET %s HTTP/1.1\r\n\r\n" % filename).encode('utf-8')
print('Sending HTTP request %s to remote host on port %d' % (http_content, port))
s.sendall(bytes(http_content))
print('done!')
except socket.error as e:
print("Error sending data: %s", e)
sys.exit(1)
while 1:
try:
buf = s.recv(2048)
except socket.error as e:
print("Error receiving data: %s", e)
sys.exit(1)
if not len(buf):
break
sys.stdout.write(buf.decode('utf-8'))
- Ubuntu查看python路径:whereis python
- 注意编码问题:TypeError: string argument without an encoding
- 确定HTTP请求的正确格式。
- 可先使用curl进行测试:C:\Users\PC>curl –verbose http://book.douban.com/subject/34662170/ curl doc.
Linux环境下连接任意一个本地服务器,运行:
./errors.py localhost 8765 /test-ignore.html
2,一个遗漏的错误
在客户端连接与服务器写客户端请求的这段时间里, 如果远程服务器断开连接, 就会出现通信出了问题, 但是却没有产生异常, 这是因为没有从操作系统传回错误。这里后面对recv()的调用就接收不到数据(因为服务器已经关闭了连接),程序会成功终止。 这是误解最多的地方。
为了解决这个问题, 一旦结束写操作, 应该立刻调用shutdowrx()
函数。 这样就会强制清除缓存里面的内容。
try:
s.shutdown(1)
except socket.error as e:
print('Error sending data (detected by shutdown):%s'% e)
sys.exit(1)
五,使用UDP
UDP通信
几乎不使用文件类对象, 因为它们往往不能为数据如何发送和接收提供足够的控制。
import socket, sys
host = sys.argv[1]
textport = sys.argv[2]
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
try:
port = int(textport)
except ValueError:
# That didn1t 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(bytes(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与TCP在使用上的区别:
- 套接字类型不同。
- socket.getservbyname()寻找的端口号不同,当然也可以使用同一端口号。
- UDP的不确定性与TCP的可靠性在应对传输错误中的不同表现。
六,总结
网络通信的基本接口是socket
,它扩展了操作系统的基本I/O到网络通信。
socket可以通过socket()
函数来建立, 通过connect()
函数来连接。得到 socket对象
后,就可以确定本地和远程端点的IP地址
和端口号
,完成通信。
错误检查是很重要的。绝大多数与网络相关的调用都会产生异常, 虽然有时候这些异常不是马上出现。 使用shutdown()
可以确保当有写错误发生时, 就能获得提醒。
Python提供了两种和socket工作的接口:用于UDP和高级TCP目的的标准socket接口
, 以及用于简单TCP通信的文件类接口
。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/98137.html