网络编程之socket模块
在上一篇博文中,我们介绍了osi的七层协议,从物理层简单介绍到了传输层,说到了传输层。
每一层都有相对要遵循的协议如图:
其中,物理层就是二进制,应用层可能遵循的协议太多了,有html等。
传输层之tcp与udp协议
TCP与UDP都是用来规定通信方式的。
tcp协议
又称可靠协议(数据不容易丢失)
三次握手
客户端发送连接请求,服务端接收到了并反馈接收可以建立从客户端到服务端的通道,与此同时,服务端可以发送连接请求,让客户端同意服务端向客户端建立通道,这样就建立了双向通道。
虽然但是,造成数据不容易丢失的原因不是因为有双向通道,而是因为有反馈机制。
给对方发送数据的时候,会保留一个副本,直到对方回应收到了消息,才会删除本地数据,否则会在一定时间内反复的发送。
-
三次握手后成功建立连接,连接双方就能通过双向通道发送数据了
-
洪水攻击
同一时间有大量的客户端请求建立链接,会导致服务端一致处于SYN_RCVD状态
-
服务端如何区分客户端建立链接的请求
可以对请求做唯一标识,如它的IP地址等
四次挥手
-
某个应用进程首先调用close,称该端执行“主动关闭”(active close)。该端的TCP于是发送一个FIN分节,表示数据发送完毕。
-
接收到这个FIN的对端执行 “被动关闭”(passive close),这个FIN由TCP确认。
注意:FIN的接收也作为一个文件结束符(end-of-file)传递给接收端应用进程,放在已排队等候该应用进程接收的任何其他数据之后,因为,FIN的接收意味着接收端应用进程在相应连接上再无额外数据可接收。
-
一段时间后,接收到这个文件结束符的应用进程将调用close关闭它的套接字。这导致它的TCP也发送一个FIN。
-
接收这个最终FIN的原发送端TCP(即执行主动关闭的那一端)确认这个FIN。
udp协议
也称之为数据报协议和不可靠协议,没有什么反馈机制,一端发送了数据后就不管了,另外一端能不能收到数据不会影响发送端只发一次。
qq最初就是使用最基本的udp协议来传输消息。
而现在还是用udp,只是添加了很多功能来保障数据的安全。
socket模块
Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。
简而言之,socket帮我们把传输层及以下的处理全做了,调这个模块就能帮助我们通过网络完成两个程序间的通讯。
socket代码实现
# 服务端
import socket
# 1.产生一个socket对象并指定采用的通信版本和协议(TCP)
server = socket.socket() # 括号内不写参数 默认就是TCP协议 family=AF_INET基于网络的套接字 type=SOCK_STREAM流式协议即TCP
# 2.绑定一个固定的地址(服务端必备的条件)
server.bind(('127.0.0.1', 8080)) # 127.0.0.1为本地回环地址 只有自己的电脑可以访问
# 3.设立半连接池(暂且忽略)
server.listen(5)
# 4.等待接客
sock, addr = server.accept() # return sock, addr 三次握手
print(sock, addr) # sock就是双向通道 addr就是客户端地址
# 5.服务客人
data = sock.recv(1024) # 接收客户端发送过来的消息 1024字节
print(data.decode('utf8'))
sock.send('我是服务端~诶嘿'.encode('utf8')) # 给客户端发送消息 注意消息必须是bytes类型
# 6.关闭双向通道
sock.close() # 四次挥手
# 7.关闭服务端
server.close() # 关闭服务端(一般不用)
# 客户端
import socket
# 1.生成socket对象指定类型和协议
client = socket.socket()
# 2.通过服务端的地址链接服务端
client.connect(('127.0.0.1', 8080))
# 3.直接给服务端发送消息
client.send('我是客户端~哦吼'.encode('utf8'))
# 4.接收服务端发送过来的消息
data = client.recv(1024)
print(data.decode('utf8'))
# 5.断开与服务端的链接
client.close()
socket代码简要优化
1.聊天内容自定义
针对消息采用input获取
2.让聊天循环起来
将聊天的部分用循环包起来
3.用户输入的消息不能为空
本质其实是两边不能都是recv或者send,一定是一方收一方发
在win中如果发空,会报错
4.服务端多次重启可能会报错
Address already in use 主要是mac电脑会报
我们可以通过改端口号的方式(服务端的bind和客户端connect都要改)来简单解决端口占用的问题
5.当客户端异常断开的情况下 如何让服务端继续服务其他客人
windows服务端会直接报错,mac服务端会有一段时间反复接收空消息延迟报错
处理方式:异常处理、空消息判断
半连接池
server.listen(5) # 半连接池
在服务端的程序中,我们的server对象可以通过这一句代码来监听有哪些程序尝试连接我们的服务端程序,当服务端正和某个客户端建立好通道进行联系时,其他的客户端就得排队了,当服务端断开与之前客户端的联系,新的客户端就可以与服务端进行连接了(前提是服务端还有accept进行回应)
如:
# 服务端
import socket
server = socket.socket()
server.bind(('127.0.0.1', 8888))
server.listen(5)
while True:
# 每次循环开始都可以接收一次连接
sock, addr = server.accept()
print(f'{addr}已接入')
# 模拟对话开始
bytes_msg = sock.recv(1024)
print(bytes_msg.decode('utf8'))
sock.send('服务端向你扔了一只小狗'.encode('utf8'))
# 模拟对话结束
# 客户端
import socket
client = socket.socket()
client.connect(('127.0.0.1', 8888))
anyone = input('扔一只什么给服务端吧:')
client.send(f'扔了只{anyone}'.encode('utf8'))
print(client.recv(1024).decode('utf8'))
在上述程序中,我们通过客户端不断的向服务端发起请求,交流会话。
将这个打开,就可以让客户端程序同时开启多个,当我们发起6个以上的客户端连接请求,第一个正在于服务端对话,而后面五个在半连接池中等待,再由客户端在此时想要向我们发送这个端口发送请求就会连接异常。
所以这个listen中的数字就表示可以容纳的等待连接数量。
标签:socket,编程,sock,TCP,server,模块,服务端,客户端 From: https://www.cnblogs.com/Leethon-lizhilog/p/16897119.html