本文主要参考 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 之间的交流规则。

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

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

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

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

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

所以信息

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

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

信息长度指 sendrecv 的信息的长度。比如 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 之后做这个。

机制的主要区别是 sendrecvconnectaccpet 没有做任何事就会返回。你有很多选择。比如检查返回码和错误码,但这会使你的应用变大、容易出 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. UrlUtils工具类,Java URL工具类,Java URL链接工具类

    UrlUtils工具类,Java URL工具类,Java URL链接工具类 >>>>>>>>>>>>>>>&g ...

  2. Python系统编程笔记

    01. 进程与程序 编写完毕的代码,在没有运行的时候,称之为程序 正在运行着的代码,就称为进程 进程是系统分配资源的最小单位. 进程资源包括: 中间变量 代码 计数器 02. 通过os.fork()函 ...

  3. Android学习之SeekBar(控制wav音频的声音)

    使用SeekBar调节声音 SeekBar控件其实就是一个高级点的进度条,就像我们在听歌,看电影用的播放器上的进度条一样,是可以拖动的,可以改变进度的一个进度条控件! SeekBar常用属性: and ...

  4. Objective-c官方文档 怎么使用对象

    版权声明:原创作品,谢绝转载!否则将追究法律责任.   对象发送和接受消息 尽管有不同的方法来发送消息在对象之间,到目前位置是想中括号那样[obj doSomeThing]:左边是接受消息的接收器,右 ...

  5. Ubuntu 14.04 DNS 配置

    最近得到一个比较好用的DNS,每次重启后都修改DNS配置文件 /etc/resolv.conf 重启就会失效 从网上得知 /etc/resolv.conf中的DNS配置是从/etc/resolvcon ...

  6. 【ORACLE 】查询被锁住的对象,并结束其会话

    使用Oracle时,发现有表被锁,又不知道是谁(或者哪个程序)锁的,怎么办 ? 两步走: 1.查找出被锁对象的会话ID和序列号 执行如下SQL: -- 查询出被锁对象,并提供 kill 脚本 SELE ...

  7. 告知你不为人知的UDP-连接性和负载均衡

    版权声明:本文由黄日成原创文章,转载请注明出处: 文章原文链接:https://www.qcloud.com/community/article/812444001486438028 来源:腾云阁 h ...

  8. 线程同步之ManualResetEvent类的用法

    笔者的一台激光测厚设备的软件, 它有一个运动线程, 一个激光数据处理线程. 运动线程做的事就是由A点移动到B点, 然后再由B点移动回A点. 激光处理线程要做的事就是采集指定数量点的激光数据, 随着采集 ...

  9. Firefox --- 火狐浏览器下载

    http://www.firefox.com.cn/download/

  10. sencha touch 常见问题解答(1-25)

    欢迎留言补充,持续更新中... 1.sencha touch 是什么? 答:Sencha touch框架是世界上第一个基于HTML 5的移动应用框架.它可以让你的Web应用看起来像网络应用.美丽的用户 ...