本文主要参考 https://docs.python.org/3/howto/sockets.html 。

本文只讨论 STREAME(比如 TCP) INET(比如 IPv4) socket。

在多种跨进程通信方式中,sockets 是最受欢迎的。对于任意给定的平台,有可能存在其他更快的跨进程通信方式,但对于跨平台交流,sockets 应该是唯一的一种。

创建 Socket

客户端 Socket

通俗的讲,当你点击一个链接,你的浏览器会做以下事情:

# create an INET, STREAMing socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 连接到服务器,如果 URL 中没有指明端口,那么端口为默认的 80
s.connect(("www.python.org", 80))

建立连接后,可以用 socket s 来发送请求。然后 s 会读取回复,然后被销毁。在一次请求-接收过程(或者一系列连续的小的过程)中,客户端 sockets 通常只会被使用一次。

服务端 Socket

对于 web 服务器来说:

# create an INET, STREAMing socket
serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# bind the socket to a public host, and a well-known port
serversocket.bind((socket.gethostname(), 80))
# become a server socket
serversocket.listen(5)

socket.gethostname() 的地址可以被外部看到。

listen 告知 socket 库,监听队列中最多能有 5 个连接请求,队列满了之后的请求会被拒绝。

主循环

while True:
# accept connections from outside
(clientsocket, address) = serversocket.accept()
# now do something with the clientsocket
# in this case, we'll pretend this is a threaded server
ct = client_thread(clientsocket)
ct.run()

没有连接时 accept 会一直阻塞。

主循环通常有三种工作方式:

  • 分派一个线程去处理 clientsocket
  • 创建一个进程去处理 clientsocket
  • 重构以使用非阻塞 sockets 并使用 select 在我们的服务器 socket 和 clientsocket 多路传输(multiplex)

上面的代码就是服务端 socket 所做的。它不发送、接收任何数据。它只是生产 clientsocket。每个 clientsocket 被创建出,用来响应 connect() 来的 “client” sockets(比如浏览器)。

服务端 socket 在创建 clientsocket 后,又重新返回去监听更多的连接。那两个客户端 sockets 在自由地交谈 -- 使用动态分配的并在谈话结束后会被回收的端口。

使用 Socket

作为设计者,你必须决定客户端 sockets 之间的交流规则。

send 和 recv 操作网络 buffers,它们不一定会处理所有你传递给它们的 bytes,因为它们集中于处理网络 buffers。当网络 buffers 被 send 或 recv 时,它们会返回它们处理的 bytes 数目。调用它们以确保所有数据已被处理是你的责任。

当 recv 返回 b"" 或者 send 返回 0 意味着另一边已经关闭了(或正在关闭)连接。如果是 recv ,那么你将不会从这个连接再收到任何数据,但你可能可以成功的发送数据,在下文会谈到。如果是 send,那你不能再向这个 socket 发送任何数据。

类似 HTTP 协议在一次交谈中只使用一个 socket。客户端 socket 发送请求,读取回复,然后客户端 socket 被遗弃。所以客户端可以通过接受到 0 bytes 的回复来发现交谈结束了。

如果你打算为了将来的传输复用你的 socket,你需要知道 socket 中没有传输结束(EOT)这个标识。

总结一下:如果 send 或 recv 0 bytes,那么这个连接已经被关闭了。如果一个连接没有被关闭,你可能永远在等 recv,因为 socket 不会告诉你现在并没有更多消息了。

所以信息

  • 必须是固定长度的
  • 或者被划定了界限
  • 或者指出信息有多长
  • 或者以关闭连接来结束

完全由你来选择使用何种方法。

信息长度指 send 和 recv 的信息的长度。比如 send 发送 bytes,那么是 str 转换为 bytes 后的信息的长度而不是 str 的表示的信息的长度。

最简单的方法是固定长度的消息:

class MySocket:
"""demonstration class only
- coded for clarity, not efficiency
""" def __init__(self, sock=None):
if sock is None:
self.sock = socket.socket(
socket.AF_INET, socket.SOCK_STREAM)
else:
self.sock = sock def connect(self, host, port):
self.sock.connect((host, port)) def mysend(self, msg):
totalsent = 0
while totalsent < MSGLEN:
sent = self.sock.send(msg[totalsent:])
if sent == 0:
raise RuntimeError("socket connection broken")
totalsent = totalsent + sent def myreceive(self):
chunks = []
bytes_recd = 0
while bytes_recd < MSGLEN:
chunk = self.sock.recv(min(MSGLEN - bytes_recd, 2048))
if chunk == b'':
raise RuntimeError("socket connection broken")
chunks.append(chunk)
bytes_recd = bytes_recd + len(chunk)
return b''.join(chunks)

长度的选择是要发送的信息的最大长度,如果信息长度不足,那么按照约定补充信息直到长度符合,约定的字符也是由你决定。

上面的代码是确保发送、接收的代码不小于定义的长度。

在发送时,由于发送的长度不固定,所以每次要从之前发送的信息之后开始发送。

接收时,要准确地指定需要接收的消息长度。如果指定长度小于实际长度,那么信息就不完整;反之,会一直等待信息发送。又由于最多接收 2048 bytes,所以要 min(MSGLEN - bytes_recd, 2048)。

Python 的 len() 可以计算含有 \0 的消息的长度,而在 C 语言中不能使用 strlen 计算含有 \0 的消息的长度。

使用信息长度作为前缀

假设使用 5 个字符作为信息前缀来表示信息长度,那么你可能不能获取所有的 5 个字符在一个 recv 中,可能出现在网络负载高的情况下。所以你可以调用两次 recv -- 第一个决定长度,第二个获取剩余的信息。

二进制数据

可以使用 socket 发送二进制数据。主要的问题是并不是所有的机器都是用同样的二进制数据格式。比如摩托罗拉芯片使用两个十六进制 bytes 00 01 表示16进制整数 1。然而 Intel 和 DEC 是 byte-reversed -- 使用 01 00 表示 1。

在现在的 32 位机器上, 用 ascii 表现的二进制数据通常比二进制表示的数据更小。因为在很多时候,数据中含有 0 或 1.字符串 “0” 是 2 bytes 而二进制是 4。所以,不适合用固定长度的信息。所以需要你选择合适的传递信息的策略当你想要能够使用 socket 传递字符串和二进制数据。

Python Struct 操纵二进制数据

>>> from struct import *
>>> pack('hhl', 1, 2, 3)
b'\x00\x01\x00\x02\x00\x00\x00\x03'
>>> unpack('hhl', b'\x00\x01\x00\x02\x00\x00\x00\x03')
(1, 2, 3)
>>> calcsize('hhl')
8

以上参考 https://docs.python.org/3/library/struct.html

断开连接

严格的说,在 close socket 之前,你应该调用 shutdown。根据传递给 shutdown 的参数,可以表示 “我不会再从这个 socket 读或向这个 socket 写数据”。大部分 socket 库,由于程序员老是忘记调用 shutdown,所以 close 相当于 shutdown(); close()。所以在大部分情况下,不需要显示调用 shutdown。

类似 HTTP 传输是可以有效地使用 shutdown。客户端在发送请求后调用 shutdown(1)。这告诉服务器 “这个客户端发送完了,但仍然会接收信息”。服务器可以通过收到 0 bytes 来知道这是 EOF(文档的结束)。

在 Python 中,如果 socket 被垃圾回收了,它会在需要时自动执行 close()。但依靠这个是非常糟糕的习惯。如果你的 socket 在消失前没有执行 close,那么另一边的 socket 会一直挂起。

什么时候该清除 Sockets

使用阻塞 sockets 时最糟糕的事可能是另一边挂了(而没有调用 close)。你的 socket 会一直挂起。TCP 是一个可靠的协议,所以在关闭连接之前,它会等待很久。如果你使用了线程,那么整个线程就挂了。对此你不能做什么。只要你不做一些愚蠢的事情,比如在做阻塞操作时加锁了,线程不会消耗很多资源。不要尝试取杀死这个线程 -- 线程比进程更高效的部分原因就是线程避免了自动资源回收。换句话说,如果你杀死了这个线程,那么你的整个进程很可能会挂掉。

非阻塞 Sockets

在 Python 中,你使用 socket.setblocking(0) 令 socket 非阻塞。在 C 语言中会更加复杂,但思想是相同的。你要在创建 socket 之后做这个。

机制的主要区别是 send、recv、connect、accpet 没有做任何事就会返回。你有很多选择。比如检查返回码和错误码,但这会使你的应用变大、容易出 bug 并且消耗大量 CPU。

使用 select。

在 C 语言中,使用 select 很复杂。在 Python 中,它十分容易,但它也足够接近 C 中的概念,如果你理解了 Python 中的 select,那么你理解 C 中的不会有很大问题:

ready_to_read, ready_to_write, in_error = \
select.select(
potential_readers,
potential_writers,
potential_errs,
timeout)

传递给 select 三个参数:

  • 所有你想读的 sockets 列表
  • 所有你想写的 sockets 列表
  • 所有你想检查错误的 sockets 列表

你应该注意一个 socket 可以出现在多个列表中。调用 select 是阻塞的,但你可以给它一个超时时间。

返回 3 个列表。分别包含可读的、可写的和错误的 sockets。

如果一个 socket 在可读列表中,那么调用对其 recv 一定会返回一些东西。对可写的也是同理。然后你可以对其使用上文阻塞操作中用到的读写方法。

  • 创建 server socket,将其设置为非阻塞
  • 将 server socket 放入 potential_readers
  • 以 potential_readers 为参数调用 select
  • 检查 ready_to_read,如果是 server socket,对其调用 accept,获取 client socket,将 client socket 设置为非阻塞
  • 将 client socket 添加到 potential_writers 和 potential_readers 中
  • 以 potential_writers 和 potential_readers 作为参数调用 select
  • 从 potential_readers 的 client socket 中读取信息,并储存到 msg[client socket] 中
  • 从 potential_writers 中获取 client socket,并向其发送 msg[client socket] 如果 msg[client socket] 存在

以上参考 https://pymotw.com/2/select/ 。

可移植性警告:在 Unix 中,select 对 sockets 和 files 都有效。而在 Windows 中,select 只对 sockets 有效。并且在 C 中,很多 socket 的高级特性在 Windows 中都不同。因此推荐在 Windows 中使用 thread。

具体代码

见 https://github.com/Jay54520/python_socket/ 。

参考

  1. https://gist.github.com/owainlewis/3217710
  2. https://docs.python.org/3/library/struct.html
  3. https://pymotw.com/2/select/

使用 Python 进行 socket 编程的更多相关文章

  1. 转:Python 的 Socket 编程教程

    这是用来快速学习 Python Socket 套接字编程的指南和教程.Python 的 Socket 编程跟 C 语言很像. Python 官方关于 Socket 的函数请看 http://docs. ...

  2. Python 3 socket 编程

    Python 3 socket编程 一 客户端/服务器架构 互联网中处处是C/S架构 1.C/S结构,即Client/Server(客户端/服务器)结构 2.在互联网中处处可见c/s架构 比如说浏览器 ...

  3. 最基础的Python的socket编程入门教程

    最基础的Python的socket编程入门教程 本文介绍使用Python进行Socket网络编程,假设读者已经具备了基本的网络编程知识和Python的基本语法知识,本文中的代码如果没有说明则都是运行在 ...

  4. python之socket编程(一)

    socket之前我们先来熟悉回忆几个知识点. OSI七层模型 OSI(Open System Interconnection)参考模型是国际标准化组织(ISO)制定的一个用于计算机或通信系统间互联的标 ...

  5. Python:socket编程教程

    ocket是基于C/S架构的,也就是说进行socket网络编程,通常需要编写两个py文件,一个服务端,一个客户端. 首先,导入Python中的socket模块: import socket Pytho ...

  6. python学习------socket编程

    一 客户端/服务器架构 1.硬件C/S架构(打印机) 2.软件C/S架构 互联网中处处是C/S架构 如黄色网站是服务端,你的浏览器是客户端(B/S架构也是C/S架构的一种) 腾讯作为服务端为你提供视频 ...

  7. Python基础socket编程

    Python 提供了两个基本的 socket 模块. 第一个是 Socket,它提供了标准的 BSD Sockets API. 第二个是 SocketServer, 它提供了服务器中心类,可以简化网络 ...

  8. Python基础-socket编程

    一.网络编程 自从互联网诞生以来,现在基本上所有的程序都是网络程序,很少有单机版的程序了. 计算机网络就是把各个计算机连接到一起,让网络中的计算机可以互相通信.网络编程就是如何在程序中实现两台计算机的 ...

  9. Python菜鸟之路:Python基础-Socket编程-2

    在上节socket编程中,我们介绍了一些TCP/IP方面的必备知识,以及如何通过Python实现一个简单的socket服务端和客户端,并用它来解决“粘包”的问题.本章介绍网络编程中的几个概念:多线程. ...

  10. Python 006- python socket编程详细介绍

    转自https://blog.csdn.net/rebelqsp/article/details/22109925 Python 提供了两个基本的 socket 模块. 第一个是 Socket,它提供 ...

随机推荐

  1. 多线程 TCP 连接

    TCP的Java支持 协议相当于相互通信的程序间达成的一种约定,它规定了分组报文的结构.交换方式.包含的意义以及怎样对报文所包含的信息进行解析,TCP/IP协议族有IP协议.TCP协议和UDP协议.现 ...

  2. httpClient创建对象、设置超时

    从老版本和新版本进行比较说明: 1.创建HttpClient对象 3.X: HttpClient httpClient = new DefaultHttpClient(); 4.3: Closeabl ...

  3. 转 Mysql性能优化教程

    Mysql性能优化教程 背景及目标 厦门游家公司(4399.com)用于员工培训和分享. 针对用户群为已经使用过mysql环境,并有一定开发经验的工程师 针对高并发,海量数据的互联网环境. 本文语言为 ...

  4. Material Design系列第三篇——Using the Material Theme

    Using the Material Theme This lesson teaches you to Customize the Color Palette Customize the Status ...

  5. python之SQLAlchemy ORM

    前言: 这篇博客主要介绍下SQLAlchemy及基本操作,写完后有空做个堡垒机小项目.有兴趣可看下python之数据库(mysql)操作.下篇博客整理写篇关于Web框架和django基础~~ 一.OR ...

  6. Eclipse中如何在指定工程中搜索指定的字符串

    1.点击Search: 2.在下拉框中先择Search. 3. 4. 5.选择Java 6.

  7. Mavan学习之pom聚合

    所有用Maven管理的真实的项目都应该是分模块的,每个模块都对应着一个pom.xml.它们之间通过继承和聚合(也称作多模块,multi-module)相互关联.那么,为什么要这么做呢?我们明明在开发一 ...

  8. Mysql 的事务隔离级别

    SQL标准定义了4类隔离级别,包括了一些具体规则,用来限定事务内外的哪些改变是可见的,哪些是不可见的.低级别的隔离级一般支持更高的并发处理,并拥有更低的系统开销.Read Uncommitted(读取 ...

  9. shell中的环境变量:local,global,export

     1.local一般用于局部变量声明,多在在函数内部使用.实例如下:      echo_start() { local STR="$1" echo "...... ${ ...

  10. 记录一下gitlab通过CAS登录慢的问题

    测试反应说gitlab通过CAS登录比较慢,第一次登录的时候需要大概30秒才能登录进去 gitlab的日志中有处理每一个请求所用的时间,看了一下日志,每个有记录的请求都是在50毫秒内返回的,所以应该不 ...