socket总结
socket使用文档
socket(简称 套接字) 是 进程间通信 的一种方式,它与其他进程间通信的一个主要不同是:
它能实现不同主机间的进程间通信,我们网络上各种各样的服务大多都是基于 Socket 来完成通信的
1 socket的使用
在 python 中如何使用 socket?
import socket
socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
参数说明:
函数 socket.socket 创建一个 socket,该函数带有两个参数:
family:可以选择AF_INET(用于 Internet 进程间通信) 或者AF_UNIX(用于同一台机器进程间通信),实际工作中常用AF_INETtype:套接字类型,可以使用SOCK_STREAM(流式套接字,主要用于 TCP 协议)或者使用SOCK_DGRAM(数据报套接字,主要用于 UDP 协议)
创建一个tcp套接字
import socket
# 创建tcp的套接字
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
"""
这里省略功能代码
"""
# 不用的时候,关闭套接字
s.close()
创建一个udp套接字
import socket
# 创建udp的套接字
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
"""
这里省略功能代码
"""
# 不用的时候,关闭套接字
s.close()
- 套接字使用流程 与 文件的使用流程很类似:
- 创建套接字
- 使用套接字收/发数据
- 关闭套接字
2 udp 套接字
创建一个udp客户端程序,具体步骤如下:
- 创建客户端套接字
- 发送/接收数据
- 关闭套接字
# -*- coding: utf-8 -*-
import socket
# 1. 创建一个 udp 套接字
udp = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 2. 绑定ip和端口 一般ip不用书写, 表示本机上的任何一个ip
address = ('', 7788)
udp.bind(address)
# 3. 发送数据
to_address = ('169.254.252.93', 7788)
udp.sendto('你好'.encode('utf-8'), to_address)
# 4. 等待对方发送请求 这个过程是阻塞的 直到接收到数据 1024表示本次接收的最大字节数
recv_data = udp.recvfrom(1024)
# 4. 显示接收到的数据
print(recv_data[0].decode('utf-8'))
# 5. 关闭套接字
udp.close()
3 tcp 套接字
TCP协议,传输控制协议(英语:Transmission Control Protocol,缩写为 TCP)是一种面向连接的、可靠的、基于字节流的传输层通信协议
TCP通信需要经过创建连接、数据传送、终止连接三个步骤
TCP通信模型中,在通信开始之前,一定要先建立相关的链接,才能发送数据
-
TCP特点
-
面向连接
通信双方必须先建立连接才能进行数据的传输,双方都必须为该连接分配必要的系统内核资源,以管理连接的状态和连接上的传输
-
可靠传输
-
TCP采用发送应答机制
TCP发送的每个报文段都必须得到接收方的应答才认为这个TCP报文段传输成功
-
超时重传
发送端发出一个报文段之后就启动定时器,如果在定时时间内没有收到应答就重新发送这个报文段。
TCP保证了数据不丢包
-
错误校验
TCP用一个校验和函数来检验数据是否有错误;在发送和接收时都要计算校验
-
流量控制和阻塞管理
流量控制用来避免主机发送得过快而使接收方来不及完全收下。
-
-

3.1 tcp客户端
发送数据,要知道tcp服务端的 ip和端口
# -*- coding: utf-8 -*-
import socket
# 1. 创建tcp 套接字
tcp_client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 2. 连接到 tcp服务端
server_address = ('169.254.252.93', 7788)
tcp_client.connect(server_address)
# 3. 发送数据
send_data = "获取服务端数据"
tcp_client.send(send_data.encode('utf-8'))
# 4. 等待服务端数据返回 最大接收1024个字节
recv_data = tcp_client.recv(1024)
print(recv_data.decode('utf-8'))
# 5. 关闭套接字
tcp_client.close()
3.2 tcp服务端
具体流程如下:
- 创建一个socket套接字
- bind绑定ip和port
- listen使套接字变为可以被动链接
- accept等待客户端的链接
- recv/send接收发送数据
# -*- coding: utf-8 -*-
import socket
# 1. 创建tcp套接字
tcp_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 2. 设置端口复用 表示: 服务端关闭,端口释放
tcp_server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 3. 绑定 ip和端口 ip地址不指定,本地上的任何ip都可以使用
address = ('', 7788)
tcp_server.bind(address)
# 4. 设置监听 128表示 指定等待建立连接的客户端个数
tcp_server.listen(128)
while True:
# 5. 等待客户端连接请求 client: 为客户端服务的套接字对象, client_address: 客户端地址
client, client_address = tcp_server.accept()
print(client_address)
# 6. 等待客户端传来的数据
recv_data = client.recv(1024)
print(recv_data.decode('utf-8'))
# 7. 返回数据给客户端 发送的数据是二进制数据
send_data = '我是服务器'
client.send(send_data.encode('utf-8'))
# 8. 关闭为客户端服务的套接字
client.close()
上面的代码案例,服务端只能同时处理一个客户端的请求,不能同时处理多个客户端的请求,怎么改造成适应多客户端呢?
-
采用线程方式实现
# -*- coding: utf-8 -*- import socket import threading def start_tcp_server(ip='', port=7788): # 1. 创建 tcp套接字 tcp_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 2. 设置端口复用 表示: 服务端关闭,端口释放 tcp_server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # 3. 绑定 ip和port tcp_server.bind((ip, port)) # 4. 设置监听 128表示 指定等待建立连接的客户端个数 tcp_server.listen(128) return tcp_server def handle_client_request(client, ip_address): """ 处理客户端请求 :param client: 处理客户端请求的套接字对象 :param ip_address: 客户端的ip和port :return: """ print(f'当前客户端地址:{ip_address}') while True: # 6. 等待客户端传来的数据 该方法是阻塞过程,直到收到客户端发来的请求 recv_data = client.recv(1024) # 如果客户端断开连接时,此时recv_data就是空数据 if recv_data: print(recv_data.decode('utf-8')) # 7. 返回数据给客户端 发送的数据是二进制数据 send_data = '我是服务器' client.send(send_data.encode('utf-8')) else: print('客户端下线了') break # 8. 关闭处理客户端的套接字 client.close() def main(): # 开启tcp服务 tcp_server = start_tcp_server() # 循环等待客户端连接请求 while True: # 5. 等待客户端连接请求 client: 为客户端服务的套接字对象, client_address: 客户端地址 client, client_address = tcp_server.accept() # 创建子线程 t = threading.Thread(target=handle_client_request, args=(client, client_address)) # 开启子线程 t.start() if __name__ == '__main__': main() -
协程实现
其原理是当一个
greenlet遇到IO(指的是input output 输入输出,比如网络、文件操作等)操作时,比如访问网络,就自动切换到其他的greenlet,等到IO操作完成,再在适当的时候切换回来继续执行。由于IO操作非常耗时,经常使程序处于等待状态,有了
gevent为我们自动切换协程,就保证总有greenlet在运行,而不是等待IO# -*- coding: utf-8 -*- import socket import gevent from gevent import monkey monkey.patch_all() def start_tcp_server(ip='', port=7788): # 1. 创建 tcp套接字 tcp_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 2. 设置端口复用 表示: 服务端关闭,端口释放 tcp_server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # 3. 绑定 ip和port tcp_server.bind((ip, port)) # 4. 设置监听 128表示 指定等待建立连接的客户端个数 tcp_server.listen(128) return tcp_server def handle_client_request(client, ip_address): """ 处理客户端请求 :param client: 处理客户端请求的套接字对象 :param ip_address: 客户端的ip和port :return: """ print(f'当前客户端地址:{ip_address}') while True: # 6. 等待客户端传来的数据 该方法是阻塞过程,直到收到客户端发来的请求 recv_data = client.recv(1024) # 如果客户端断开连接时,此时recv_data就是空数据 if recv_data: print(recv_data.decode('utf-8')) # 7. 返回数据给客户端 发送的数据是二进制数据 send_data = '我是服务器' client.send(send_data.encode('utf-8')) else: print('客户端下线了') break # 8. 关闭处理客户端的套接字 client.close() def main(): # 开启tcp服务 tcp_server = start_tcp_server() # 循环等待客户端连接请求 while True: # 5. 等待客户端连接请求 client: 为客户端服务的套接字对象, client_address: 客户端地址 client, client_address = tcp_server.accept() # 准备协程 自动切换执行协程 gevent.spawn(handle_client_request, client, client_address) if __name__ == '__main__': main()
3.3 tcp要点
- tcp服务器需要绑定端口地址,否则客户端找不到这个服务器
- tcp客户端一般不需要绑定,因为是主动链接服务器,所以只要确定好服务器的ip、port等信息就好,本地客户端可以随机
- tcp服务器中通过listen可以将socket创建出来的主动套接字变为被动的,这是做tcp服务器时必须要做的
- 当客户端需要链接服务器时,就需要使用connect进行链接,udp是不需要链接的而是直接发送,但是tcp必须先链接,只有链接成功才能通信
- 当一个tcp客户端连接服务器时,服务器端会有1个新的套接字,这个套接字用来标记这个客户端,单独为这个客户端服务
- listen后的套接字是被动套接字,用来接收新的客户端的链接请求的,而accept返回的新套接字是标记这个新客户端的
- 关闭listen后的套接字意味着被动套接字关闭了,会导致新的客户端不能够链接服务器,但是之前已经链接成功的客户端正常通信。
- 关闭accept返回的套接字意味着这个客户端已经服务完毕
- 当客户端的套接字调用close后,服务器端会recv解堵塞,并且返回的长度为0,因此服务器可以通过返回数据的长度来区别客户端是否已经下线

浙公网安备 33010602011771号