python(socket模块)
今日内容概要
- 传输层之TCP与UDP协议
- 应用层
- socket模块简介
- socket模块基本使用
- 代码优化处理
- 半连接池
传输层之TCP与UDP协议
tcp与udp都是用来规定通信方式的
通信的时候是可以随心所欲的发送消息,也可以遵循一些协议符合要求的聊
随意:文字,图片,视频
协议:发送的数据开头带的有尊称、首行空两格、、、、
PS:不遵守上述的协议也是可以通信的,只不过遵循了协议会更和规合法合理!!
TCP协议
流式协议 可靠协议
当应用程序想通过TCP协议实现远程通信时,彼此之间必须先先建立双向通信通道,基于该双向通道实现数据的远程交互,该双向通道直到任意一方主动断开才会失效
三次握手 建立链接
重要状态
listen监听态:等待对方发请求
syn_rcvd态:忙于恢复确认建立请求
# 洪水攻击:服务端在同一时间接收到了大量的要求建立链接的请求
1.TCP协议称为可靠协议(数据不容易丢失)
造成数据不容易丢失的原因不是因为有双向通道,而是因为有反馈机制
给对方发送消息之后会保留一个副本,直到对方对方回应已经收到消息了才会删除
否则会在一定的时间内反复发送
2.洪水攻击
同一时间被大量的客户端请求建立链接,会导致服务端一致处于SYN_RCVD状态
3.服务端如何区分客户端建立链接的请求
可以对请求做唯一标识
四次挥手 断开链接
不能合并成三次:因为中间需要确认消息是否已经发完(TIMS_WAIT)
建立一个连接需要三次握手,而终止一个连接要经过四次握手
当服务端或者客户端不想再与对方进行通信之后,双方任意一方都可以主动发起断开链接的请求,我们还是以客户端主动发起为例
总结:挥手必须是四次,中间的两次不能合并成一次,原因就在于需要检查是否还有数据需要给对方发送

三次握手:
建立双向通道的过程称之为三次握手,建立通道的发起者可以是客户端也可以是服务端,下面我们就以客户端先主动发起为例
-
客户端会朝服务端发送一个请求询问服务端:"我能不能挖一条通往你家的地道"
-
服务端收到请求,回复说:"好吧 你挖吧",由于TCP是双向通道,客户端挖向服务端的通道只能给客户端朝服务端发消息使用,服务端要向给客户端发消息是没办法走这一条通道的,需要自己挖一条通往客户端的通道
所以服务端在回复同意客户端挖通道的同时还会问一句:"那我能不能也挖一条通往你家的通道"
-
客户端收到服务端请求后客户端到服务端的通道就挖成功了,然后也会同意服务端的请求,服务端挖向客户端的通道也会成功

UDP协议
被称为数据报协议,不可靠协议
当应用程序希望通过UDP与一个应用程序通信时,传输数据之前源端和终端不建立连接。
当它想传送时就简单地去抓取来自应用程序的数据,并尽可能快地把它扔到网络上。
数据发送没有通道的概念 发送出去了就不管了
无连接:通信不需要建立连接,只要知道对方地址就可以发送数据
不可靠:不能保证数据是否可以安全、有序的到达对端
面向数据报:一个数据报的最大大小为64k,加上udp数据报头部信息一共64位还要占8个字节,因此数据大小不能超过64k-8。
优点:
快,比 TCP 稍安全。
UDP 没有 TCP 的握手、确认、窗口、重传、拥塞控制等机制,UDP 是一个无状态的传输协议,所以它在传递数据时非常快。没有 TCP 的这些机制,UDP 较 TCP 被攻击者利用的漏洞就要少一些。但 UDP 也是无法避免攻击的,比如:UDP Flood 攻击。
缺点:
不可靠,不稳定。
因为 UDP 没有 TCP 那些可靠的机制,在数据传递时,如果网络质量不好,就会很容易丢包。

应用层
应用层相当于是程序员自己写的应用程序,里面的协议非常的多
应用程序收到"传输层"的数据,接下来就要对数据进行解包。由于互联网是开放架构,数据来源五花八门,必须事先规定好通信的数据格式,否则接收方根本无法获得真正发送的数据内容。“应用层"的作用就是规定应用程序使用的数据格式,例如我们TCP协议之上常见的Email、HTTP、FTP等协议,这些协议就组成了互联网协议的应用层。
应用层提供了诸多的协议,比如HTTP、FTP、POP3、IMAP等协议,用于约定特殊应用在应用层封装与解包通信的格式。保证统一,避免出现类似于外国人听不懂粤语,俄罗斯人听不懂德语等语言不互通问题。

socket模块简介
如果我们需要编写基于网络来进行数据交互的程序,那就意味着我们需要通过自己的代码来控制我们之前所学习的OSI七层协议(太过繁琐、复杂,类似于让我们自己编写操作系统)
这时候就可以用到 socket
socket 类似于操作系统,将哪些繁琐复杂的接口进行封装,提供简单快捷的接口
socket 也叫套接字
基于文件类型的套接字家族(单机)
AF_UNIX
基于网络类型的套接字家族(联网)
AF_INET


socket屏蔽了各个协议的通信细节,使得程序员无需关注协议本身,直接使用socket提供的接口来进行互联的不同主机间的进程的通信。这就好比操作系统给我们提供了使用底层硬件功能的系统调用,通过系统调用我们可以方便的使用磁盘(文件操作),使用内存,而无需自己去进行磁盘读写,内存管理。socket其实也是一样的东西,就是提供了tcp/ip协议的抽象,对外提供了一套接口,同过这个接口就可以统一、方便的使用tcp/ip协议的功能了。百说不如一图,看下面这个图就能明白了:
socket模块基本使用
1.socket 接口
socket结构体关键域有so_type,so_pcb。so_type常见的值有:
SOCK_STREAM 提供有序的、可靠的、双向的和基于连接的字节流服务,当使用Internet地址族时使用TCP。
SOCK_DGRAM 支持无连接的、不可靠的和使用固定大小(通常很小)缓冲区的数据报服务,当使用Internet地址族使用UDP。
SOCK_RAW 原始套接字,允许对底层协议如IP或(ICMP)进行直接访问,可以用于自定义协议的开发。
2.bind 接口(绑定一个固定地址)
bind 函数是给 so_pcd 结构中的地址赋值的接口
sockfd 是调用socket()函数创建的socket描述符
addr 是具体的地址
addrlen 表示addr的长度
3.connect 接口
connect就是拿来建立链接的函数
只有面向连接、提供可靠服务的协议才需要建立连接
4.listen 接口(半连接池)
这个事描述监听是否有链接的到来,并设置同时能完成的最大连接数。
5.accept 接口(等待接客)
在使用listen函数告知内核监听的描述符后,内核就会建立两个队列,一个SYN队列,表示接受到请求,但未完成三次握手的连接;另一个是ACCEPT队列,表示已经完成了三次握手的队列。
而accept函数就是从ACCEPT队列中拿一个连接,并生成一个新的描述符,新的描述符所指向的结构体so_pcb中的请求端ip地址、请求端端口将被初始化。
6.recv(字节数)
接受客户端发送过来的消息
7.send()
向客户端发送消息,(消息必须是bytes类型)
8.close()
关闭双向通道

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)) # 本机的ipv4地址,端口号
# 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()

代码优化
1.聊天内容自定义
针对消息采用input获取
2.让聊天循环起来
将聊天的部分用循环包起来
3.用户输入的消息不能为空
本质其实是两边不能都是recv或者send 一定是一方收一方发
4.服务端多次重启可能会报错
Address already in use 主要是mac电脑会报
方式1:改端口号
方式2:下面面代码拷贝即可
5.当客户端异常断开的情况下 如何让服务端继续服务其他客人
windows服务端会直接报错
mac服务端会有一段时间反复接收空消息延迟报错
异常处理、空消息判断
错误:

解决方法:
解决报错的代码
#加入一条socket配置,重用ip和端口
import socket
from socket import SOL_SOCKET,SO_REUSEADDR
sk = socket.socket()
sk.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) #就是它,在bind前加
sk.bind(('127.0.0.1',8898)) #把地址绑定到套接字
sk.listen() #监听链接
conn,addr = sk.accept() #接受客户端链接
ret = conn.recv(1024) #接收客户端信息
print(ret) #打印客户端信息
conn.send(b'hi') #向客户端发送信息
conn.close() #关闭客户端套接字
sk.close() #关闭服务器套接字(可选)
#加入一条socket配置,重用ip和端口
import socket
from socket import SOL_SOCKET,SO_REUSEADDR
sk = socket.socket()
sk.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) #就是它,在bind前加
sk.bind(('127.0.0.1',8898)) #把地址绑定到套接字
sk.listen() #监听链接
conn,addr = sk.accept() #接受客户端链接
ret = conn.recv(1024) #接收客户端信息
print(ret) #打印客户端信息
conn.send(b'hi') #向客户端发送信息
conn.close() #关闭客户端套接字
sk.close() #关闭服务器套接字(可选)
服务端
import socket
"""以后要养成查看源码写代码的思路"""
#1.产生一个socket 对象并指定采用的通信版本和协议(tcp)
server = socket.socket() # 括号内不写参数,默认就是TCP
"""
family=AF_INET 基于网络的套接字
type = SOCK_STREAM 流式协议 即TCP协议
"""
# 2.绑定一个固定的地址(服务端必备的条件)
server.bind(('127.0.0.1',8080)) # 本机的ipv4地址,端口号
# 3.设立半连接池(防止洪水攻击(人流量的问题))
server.listen(5)
while True: #链接循环
# 4.等待有用户链接 三次握手
sock, addr = server.accept() # 有两个数据值返回 return sock, addr
# sock就是双向通道,addr就是客户端地址
while True:
try:
# 5.服务用户 等待客户端发消息
data=sock.recv(1024) #接受客户端发送过来的信息 1024字节
print('来自客户端的消息',data.decode('utf8'))
if len(data) == 0:
break
msg =input('给客户发送的消息>>>>').strip()
if not msg:
print('不能输入为空')
continue
sock.send(msg.encode('utf8')) # 给客户端发送消息 必须是bytes类型
#6.关闭双向通道 四次挥手
except BlockingIOError:
break
客户端
import socket
# 1.生成socket对象指定类型和协议
client =socket.socket()
#2.通过服务端的地址链接服务端
client.connect(('127.0.0.1',8080)) # 自动做三次握手
# 3.直接给服务端发送消息
while True:
msg=input('向服务端发送的消息>>>>>>').strip()
if len(msg) == 0:
print('输入不能为空')
continue
client.send(msg.encode('utf8'))
# 4.接受服务端发送过来的消息
data = client.recv(1024)
print(data.decode('utf8'))
半连接池的概念
server.listen(5) # 半连接池
当有多个客户端来链接的情况下 我们可以设置等待数量(不考虑并发问题)
假设服务端只有一个人的情况下
在测试半连接池的时候 可以不用input获取消息 直接把消息写死即可

浙公网安备 33010602011771号