目录
- 导入
- Server端
- 创建基于网络的TCP
- 端口占用问题
- 起因
- 解决
- 绑定ip和port
- 监听
- 接收连接
- 收与发数据
- 收数据
- 发数据
- 什么时候使用send()什么时候使用sendall()
- 断开与client的连接
- 断开socket的连接
- 说明
- server.py整体代码
- Client端
- 创建基于网络的TCP
- 发起请求连接
- 收与发数据
- 发数据
- 收数据
- 关闭socket连接
- client.py整体代码
导入
import socket
Server端
创建基于网络的TCP
socket.socket()
# 基于网络的TCP
sk = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
端口占用问题
端口占用会抛出 OSError
起因
这是因为服务端仍然存在四次挥手的time_wait
状态在占用地址
解决
sk.setsockopt()
sk.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
绑定ip和port
ip:port
,用来唯一标识一台主机上的一个应用程序
注意点:以元组形式传入sk.bind((ip, port))
以下四种形式都可以:
- 绑定回环地址
sk.bind(('localhost', 8080))
sk.bind(('', 8080))
sk.bind(('127.0.0.1', 8080))
- 绑定本机地址
-
sk.bind(('192.168.1.103', 8080))
如果绑定了本机地址,那么就表示只要能ping通这个地址的主机,就能够访问该server
监听
sk.listen(backlog)
sk.listen(5)
标识最大等待连接数。注意:是客户端的等待连接,正在通信的不算。
换句话说:排队的人数(就是那个n) + 正在就餐的人数(服务器正在处理的socket连接数) = 允许接待的总人数(socket允许的最大连接数)
深入理解:
accept()
方法一次只能接受一个Client的连接申请,但Client是多个的,于是socket
设计了一个队列来存储Client的连接申请,从而accept()
方法就从这个队列中提取首位
成员处理即可。
所以,参数backlog
指的就是这个队列的最大值,也就是同时受理连接申请的最大值,如果满了,就需要Client重新Connect。
关于backlog
该设置多少,从Skynet
得到的参考为32
具体可以参考以下两篇博文:
接收连接
sk.accept()
accept()
,返回一个元祖
-
[0]
是一个与Client通信的双向连接 -
[1]
是Client的地址
conn, addr = sk.accept()
收与发数据
收数据
conn.recv()
recv
两个阶段
- 等待数据到达内核态
- 从内核态copy数据到用户态
data = conn.recv(1024) # 参数1024表示,最多接收1024个字节
print(data.decode(encoding='utf-8'))
发数据
conn.send()
返回值:发送数据的字节数
conn.send(bytes('received', encoding='utf-8'))
说明send
一个阶段
- copy数据到内核态
使用send()
进行发送的时候,Python将内容传递给系统底层的send接口,也就是说,Python并不知道这次调用是否会全部发送完成,比如MTU是1500,但是此次发送的内容是2000,那么除了包头等等其他信息占用,发送的量可能在1000左右,还有1000未发送。
但是,send()
不会继续发送剩下的包,因为它只会发送一次,发送成功之后会返回此次发送的字节数,如上例,会返回数字1000给用户,然后就结束了
如果需要将剩下的1000发送完毕,需要用户自行获取返回结果,然后将内容剩下的部分继续调用send()
进行发送,比如:
sended = 0
while 1:
sended = conn.send(result[sended:])
if not sended:
break
conn.sendall()
sendall()
是对send()
的包装,完成了用户需要手动完成的部分,它会自动判断每次发送的内容量,然后从总内容中删除已发送的部分,将剩下的继续传给send()
进行发送。
什么时候使用send()什么时候使用sendall()
一般情况下,我们都应该使用sendall()
,除非自己弄懂了他们的原理,并且有必要在每次包发送之后进行一些必要的处理,不然我们都不需要去使用send()
,而应该使用已经包装好的sendall()。
断开与client的连接
conn.close()
conn.close()
断开socket的连接
sk.close()
sk.close()
说明
Server端有两个socket
套接字
conn
sk
基于TCP
的socket
,得先启动Server
Server可以先收也可以先发,Client也一样
TCP
是面向连接的、安全的、可靠的、面向流的无消息保护边界
server.py整体代码
#!/usr/bin/python3
# -*- coding: utf-8 -*-
"""简单的socket通信
Server端
"""
import socket
# 基于网络的TCP
sk = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
# 端口占用会抛出 OSError
# 端口占用的起因
# 这是因为服务端仍然存在四次挥手的time_wait状态在占用地址
# 解决端口占用问题
sk.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 绑定ip和port,用来唯一标识一台主机上的一个应用程序
# 注意点:以元组形式传入
# 以下四种形式都可以:
# 绑定回环地址
sk.bind(('localhost', 8080))
# sk.bind(('', 8080))
# sk.bind(('127.0.0.1', 8080))
# 绑定本机地址
# sk.bind(('192.168.1.103', 8080))
# 如果绑定了本机地址,那么就表示只要能ping通这个地址的主机,就能够访问该server
# 监听
# listen(backlog)
# 标识最大等待连接数。注意:是客户端的等待连接,正在通信的不算
# 换句话说:排队的人数(就是那个n) + 正在就餐的人数(服务器正在处理的socket连接数) = 允许接待的总人数(socket允许的最大连接数)
# 深入理解:
# accept()方法一次只能接受一个Client的连接申请,但Client是多个的,于是socket
# 设计了一个队列来存储Client的连接申请,从而accept()方法就从这个队列中提取首位
# 成员处理即可。
# 所以,参数backlog指的就是这个队列的最大值,也就是同时受理连接申请的最大值,
# 如果满了,就需要Client重新Connect。
# 关于backlog该设置多少,从Skynet得到的参考为32
# 具体可以参考以下两篇博文:
#
#
sk.listen(5)
# 接收连接
# accept(),返回一个元祖,[0]是一个与Client通信的双向连接;[1]是Client的地址
conn, addr = sk.accept()
# 收、发数据
# 收
# recv两个阶段
# 1. 等待数据到达内核态
# 2. 从内核态copy数据到用户态
data = conn.recv(1024) # 参数1024表示,最多接收1024个字节
print(data.decode(encoding='utf-8'))
# 发
# 返回值:发送数据的字节数
# send一个阶段
# 1. copy数据到内核态
conn.send(bytes('received', encoding='utf-8'))
# 断开与Client的连接
conn.close()
# 断开socket的连接
sk.close()
# 说明:
# Server端有两个socket套接字
# 1. conn
# 2. sk
# 基于TCP的socket,得先启动Server
# Server可以先收也可以先发,Client也一样
# TCP是面向连接的、安全的、可靠的、面向流的无消息保护边界
Client端
创建基于网络的TCP
socket.socket()
# 基于网络的TCP
sk = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
发起请求连接
向ip:port发起连接请求
客户端connect
的时候ip不能写空,跟服务器端稍微有点区别sk.connect((ip, port))
sk.connect(('localhost', 8080))
当目标主机不存在时,connect
抛出ConnectionRefusedError
异常connect()
函数的扩展版本connect_ex()
,出错时返回出错码,而不是抛出异常;连接正常时,返回0
print(sk.connect_ex(('localhost', 8080)))
收与发数据
发数据
conn.send()
sk.send(bytes('我要连你...', encoding='utf-8'))
收数据
conn.recv()
data = sk.recv(1024)
print(data.decode(encoding='utf-8'))
关闭socket连接
sk.close()
sk.close()
client.py整体代码
#!/usr/bin/python3
# -*- coding: utf-8 -*-
"""简单的socket通信
Client端
"""
import socket
# 基于网络的TCP
sk = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
# 向ip:port发起连接请求
sk.connect(('localhost', 8080))
# 发
sk.send(bytes('我要连你...', encoding='utf-8'))
# 收
data = sk.recv(1024)
print(data.decode(encoding='utf-8'))
# 关闭socket连接
sk.close()