Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。
我们经常把Socket翻译为套接字,Socket是在应用层和传输层之间的一个抽象层,它不属于七层协议的任何一层,它把TCP/IP层复杂的操作抽象为几个简单的接口供应用层调用已实现进程在网络中通信。如下图示:
套接字的工作流程,就类似于我们现实生活中两人拨打电话,一人先拨号,然后等待,等另一人听到铃声后提起电话,这时两人建立了连接,就可以进行通话了,通话结束后,挂掉电话结束此次交流。流程图如下所示:
我们从服务器端开始说起。服务器端先初始化Socket,然后与端口进行绑定(bind),接着就是对端口进行监听(listen),调用accept阻塞,等待客户端连接。在这时如果有个客户端初始化一个Socket,然后连接服务器(connect),如果连接成功,这时客户端与服务器端的连接就建立了。客户端发送数据请求,服务器端接收请求并处理请求,然后把回应数据发送给客户端,客户端读取数据,最后关闭连接,这样就结束了一次交互。
下面我们通过python来实现基于TCP协议的套接字编程。本次我们先实现服务端与客户端可以完成一次收发信息,详细代码如下:
**********************************服务端**************************************
# 1. 实例化对象socket
import socket
# 2. 得到对象
# 2.1. 如果不传参数,代表的是TCP协议,即默认为type = socket.SOCK_STREAM 代表TCP协议
# 2.2 type=socket.SOCK_DGRAM => UDP协议
server = socket.socket()
# 等价于server = socket.socket(type=socket.SOCK_STREAM)
# 3. 绑定
server.bind(('127.0.0.1', 8001))
# 4. 监听, 数字代表的是半连接池
server.listen(3)
print('服务端开始接收客户端消息了:')
# 5.sock、addr
# sock: 当前连接的客户端对象
# addr: 客户端的地址
sock, addr = server.accept()
# 6. 接收客户段发送的消息
# 1024代表的是字节数,接收数据的最大字节数
data = sock.recv(1024)
print('客户端数据:', data)
# 7. 给客户端返回数据
sock.send(data.upper())
# 8. 断开连接
sock.close()
# 9. 关闭服务端
server.close()
**********************************客户端**************************************
# 1. 实例化对象
import socket
client = socket.socket()
# 2. 连接
client.connect(('127.0.0.1', 8002))
# 3. 给服务端发送数据,发送的数据类型必须是字节类型
client.send('hello'.encode('utf-8'))
# 4. 接收服务端发送过来的数据
data = client.recv(1024)
print('服务端的数据:', data)
# 5. 断开连接
client.close()
上面的代码只能实现一次信息的收发,完成之后就会断开连接,那么我们来想想,我们难道只服务一个客户端吗?答案是否定的,那么我们如何实现服务多个客户端呢?就需要添加链接循环,这时就可以接收不同客户端的信息。代码如下:
**********************************服务端**************************************
import socket
# 1. 实例化对象socket
server = socket.socket()
# 2. 绑定
server.bind(('127.0.0.1', 8002))
# 3. 监听
server.listen(3)
print('服务端开始接收客户端消息了:')
while True:
# 4. 阻塞
sock, addr = server.accept()
# 5. 接收客户段发送的消息
data = sock.recv(1024)
print('客户端数据:', data)
# 6. 给客户端返回数据
sock.send(data.upper())
# 7. 断开连接
sock.close()
# 8. 关闭服务
server.close()
**********************************客户端**************************************
# 1. 实例化对象
import socket
client = socket.socket()
# 2. 连接
client.connect(('127.0.0.1', 8002))
# 3. 给服务端发送数据,发送的数据类型必须是字节类型
client.send('hello1111'.encode('utf-8'))
# 4. 接收服务端发送过来的数据
data = client.recv(1024)
print('服务端的数据:', data)
# 5. 断开连接
client.close()
上面的代码虽然实现了可以接收不同客户端通信,但是又有一个问题,一个客户端发送信息会一次性发完吗?答案是不一定的,每次建立链接发送的信息都是没有关联的,这样肯定是不行的,因此我们就需要对一个客户端服务完成后,再去服务另一个客户端,这就需要我们加上通信循环,实现代码如下:
**********************************服务端**************************************
# 1. 实例化对象socket
import socket
server = socket.socket()
# 2. 绑定
server.bind(('127.0.0.1', 8002))
# 3. 监听
server.listen(3)
print('服务端开始接收客户端消息了:')
while True:
# 4. 阻塞
sock, addr = server.accept()
while True:
try:
# 5. 接收客户段发送的消息
data = sock.recv(1024) # hello
if len(data) == 0:
continue
print('客户端数据:', data)
# 6. 给客户端返回数据
sock.send(data.upper()) # HELLO
except Exception as e:
print(e)
break
# 7. 断开连接
sock.close()
# 8. 关闭服务
server.close()
**********************************客户端**************************************
# 1. 实例化对象
import socket
client = socket.socket()
# 2. 连接
client.connect(('127.0.0.1', 8002))
while True:
input_data = input('请输入要发送的数据:')
# 3. 给服务端发送数据,发送的数据类型必须是字节类型
client.send(input_data.encode('utf-8'))
# 4. 接收服务端发送过来的数据
data = client.recv(1024)
print('服务端的数据:', data)
# 5. 断开连接
client.close()
类似于上面的过程,只是遵循的协议不同而已,具体代码如下:
**********************************服务端**************************************
import socket
server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
server.bind(('127.0.0.1', 8080))
while True:
data, client_addr = server.recvfrom(1024)
print('===>', data, client_addr)
server.sendto(data.upper(), client_addr)
server.close()
**********************************客户端**************************************
import socket
client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # 数据报协议-》UDP
while True:
msg = input('>>: ').strip()
client.sendto(msg.encode('utf-8'), ('127.0.0.1', 8080))
data, server_addr = client.recvfrom(1024)
print(data)
client.close()
方法 | 用途 |
---|---|
s.bind() | 绑定(主机,端口号)到套接字 |
s.listen() | 开始TCP监听 |
s.accept() | 被动接受TCP客户的连接,(阻塞式)等待连接的到来 |
方法 | 用途 |
---|---|
s.connect() | 主动初始化TCP服务器连接 |
s.connect_ex() | connect()函数的扩展版本,出错时返回出错码,而不是抛出异常 |
方法 | 用途 |
---|---|
s.recv() | 接收TCP数据 |
s.send() | 发送TCP数据(send在待发送数据量大于己端缓存区剩余空间时,数据丢失,不会发完) |
s.sendall() | 发送完整的TCP数据(本质就是循环调用send,sendall在待发送数据量大于己端缓存区剩余空间时,数据不丢失,循环调用send直到发完) |
s.recvfrom() | 接收UDP数据 |
s.sendto() | 发送UDP数据 |
s.getpeername() | 连接到当前套接字的远端的地址 |
s.getsockname() | 当前套接字的地址 |
s.getsockopt() | 返回指定套接字的参数 |
s.setsockopt() | 设置指定套接字的参数 |
s.close() | 关闭套接字 |