什么是 socket?
可以简单地认为,socket 就是操作系统对于 TCP 连接的封装。通过 socket,我们可以进行简单可靠的网络传输。
目标
我们打算编写这么一个简单的程序:
- 客户端与服务端建立连接,端口为6666。
- 服务端发送信息,询问名字。
- 客户端发送名字信息。
- 服务端发送欢迎信息。
- 断开连接,客户端退出。
编写客户端
客户端的代码比较简单。
client.py
import socket
# AF_INET是Address Family: Internet的缩写,如果你要使用IPv6,就传入socket.AF_INET6
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect(('127.0.0.1', 6666))
# 一次只接受1kb的数据,这个值是比较合理的
b = sock.recv(1024)
# 注意,网络传输的数据都是bytes类型
print(b.decode('utf-8'))
name = input('')
sock.send(name.encode('utf-8'))
b = sock.recv(1024)
print(b.decode('utf-8'))
sock.close()
要进行 socket 编程,首先要初始化一个 socket 对象,如果你要用 IPv4 进行通信,就传入 socket.AF_INET 和 socket.SOCK_STREAM,这是最常用的配置。接着,我们用 connect 方法连接上服务端,然后用 recv 和 send 方法进行数据的往来。最后一定要记得用 close 方法关闭 socket。
编写服务端
服务端的代码稍微复杂一些,要用到多线程。
server.py
import socket
import threading
def processLink(sock):
sock.send(b"What's your name?")
r = sock.recv(1024)
greeting = b'Hello, ' + r + b'!'
sock.send(greeting)
sock.close()
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(('127.0.0.1', 6666))
# 5表示正在“排队”的连接个数最多是5,由于我们这个示例程序比较简单,
# 所以基本上不可能出现处理不过来排队的现象,因而这个值不太重要,5是最常见的取值
sock.listen(5)
while True:
(s, addr) = sock.accept()
thread = threading.Thread(target=processLink, args=(s,))
thread.start()
与客户端不同,服务端要调用 bind 方法绑定端口,然后用 listen 方法开始监听。接着我们用一个无限循环来等待连接,如果有客户端连进来,accpet 方法就会返回一个 tuple,包含客户端的 socket 和网络地址,然后我们创建一个新的线程来处理这个连接。如果不用多线程,那么一次只能同时处理一个连接了!
测试程序
编写完之后,我们首先打开 server.py,然后打开 client.py,就会看到以下结果:
总结
socket 编程就是围绕着 socket 对象的,我们一般用 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 来初始化一个 socket 对象。
下面的表格列举了 socket 对象的常用方法。
通用
方法名称 | 参数说明 | 作用 |
s.recv(size) | size:最多接受的字节数 | 接受信息 |
s.send(data) | data:要发送的数据 | 发送信息,返回发送成功的字节数,也就是说,不一定能全部发送 |
s.sendall(data) | data:要发送的数据 | 发送信息,尽量保证全部发送,如果做不到,抛出一个异常 |
s.settimeout(timeout) | timeout:操作阻塞的最大时间 | 如果 timeout 是 None,表示无限阻塞,如果是 0,表示不阻塞,如果是非零的值,超过该时间(单位:秒)后会抛出 socket.timeout 异常 |
s.close() | | 关闭 socket |
客户端
方法名称 | 参数说明 | 作用 |
s.connect(addr) | addr:一个 tuple,第一个元素是 IP 地址,第二个元素是端口 | 连接服务端 |
服务端
方法名称 | 参数作用 | 作用 |
sock.bind(addr) | addr:一个 tuple,第一个元素是 IP 地址,第二个元素是端口 | 绑定到要监听的端口 |
sock.listen(max) | max:排队(未处理)的连接数的最大值 | 开始监听 |
sock.accept() | | 与客户端建立连接,返回值是一个 tuple,第一个元素是客户端的 socket,第二个元素是客户端的地址 |