网络编程与socket套接字

网络编程与socket套接字

传输层

PORT协议

port是一种接口,数据通过它在计算机和其他设备(比如打印机,鼠标,键盘或监视器)之间,网络之间和其他直接连接的计算机之间传递

TCP协议

​ 传输控制协议TCP是一种面向连接的、可靠的、基于字节流的传输层通信协议,由IETF的RFC定义

image

三次握手过程

​ TCP连接的建立时,双方需要经过三次握手,具体过程如下:

​ (1)第一次握手:Client进入SYN_SENT状态,发送一个SYN帧来主动打开传输通道,该帧的SYN标志位被设置为1,同时会带上Client分配好的SN序列号,该SN是根据时间产生的一个随机值,通常情况下每间隔4ms会加1。除此之外,SYN帧还会带一个MSS(最大报文段长度)可选项的值,表示客户端发送出去的最大数据块的长度。

​ (2)第二次握手:Server端在收到SYN帧之后,会进入SYN_RCVD状态,同时返回SYN+ACK帧给Client,主要目的在于通知Client,Server端已经收到SYN消息,现在需要进行确认。Server端发出的SYN+ACK帧的ACK标志位被设置为1,其确认序号AN(Acknowledgment
Number)值被设置为Client的SN+1;SYN+ACK帧的SYN标志位被设置为1,SN值为Server端生成的SN序号;SYN+ACK帧的MSS(最大报文段长度)表示的是Server端的最大数据块长度。

​ (3)第三次握手:Client在收到Server的第二次握手SYN+ACK确认帧之后,首先将自己的状态会从SYN_SENT变成ESTABLISHED,表示自己方向的连接通道已经建立成功,Client可以发送数据给Server端了。然后,Client发ACK帧给Server端,该ACK帧的ACK标志位被设置为1,其确认序号AN(Acknowledgment
Number)值被设置为Server端的SN序列号+1。还有一种情况,Client可能会将ACK帧和第一帧要发送的数据,合并到一起发送给Server端。

​ (4)Server端在收到Client的ACK帧之后,会从SYN_RCVD状态会进入ESTABLISHED状态,至此,Server方向的通道连接建立成功,Server可以发送数据给Client,TCP的全双工连接建立完成。

image

​ 四次挥手:

​ (1)第一次挥手:主动断开方(可以是客户端,也可以是服务器端),向对方发送一个FIN结束请求报文,此报文的FIN位被设置为1,并且正确设置SequenceNumber(序列号)和Acknowledgment
Number(确认号)。发送完成后,主动断开方进入FIN_WAIT_1状态,这表示主动断开方没有业务数据要发送给对方,准备关闭SOCKET连接了。

​ (2)第二次挥手:正常情况下,在收到了主动断开方发送的FIN断开请求报文后,被动断开方会发送一个ACK响应报文,报文的Acknowledgment
Number(确认号)值为断开请求报文的Sequence Number
(序列号)加1,该ACK确认报文的含义是:“我同意你的连接断开请求”。之后,被动断开方就进入了CLOSE-WAIT(关闭等待)状态,TCP协议服务会通知高层的应用进程,对方向本地方向的连接已经关闭,对方已经没有数据要发送了,若本地还要发送数据给对方,对方依然会接受。被动断开方的CLOSE-WAIT(关闭等待)还要持续一段时间,也就是整个CLOSE-WAIT状态持续的时间。

主动断开方在收到了ACK报文后,由FIN_WAIT_1转换成FIN_WAIT_2状态。

​ (3)第三次挥手:在发送完成ACK报文后,被动断开方还可以继续完成业务数据的发送,待剩余数据发送完成后,或者CLOSE-WAIT(关闭等待)截止后,被动断开方会向主动断开方发送一个FIN+ACK结束响应报文,表示被动断开方的数据都发送完了,然后,被动断开方进入LAST_ACK状态。

​ (4)第四次挥手:主动断开方收在到FIN+ACK断开响应报文后,还需要进行最后的确认,向被动断开方发送一个ACK确认报文,然后,自己就进入TIME_WAIT状态,等待超时后最终关闭连接。处于TIME_WAIT状态的主动断开方,在等待完成2MSL的时间后,如果期间没有收到其他报文,则证明对方已正常关闭,主动断开方的连接最终关闭。

被动断开方在收到主动断开方的最后的ACK报文以后,最终关闭了连接,自己啥也不管了。

image

UDP协议

​ internet协议集支持一个无连接的传输协议,该协议成为用户数据报协议(UDP)为应用程序提供了一种无需建立连接就可以发送等装的ip数据报的方法就是UDP。

​ UDP协议的报头比TCP协议的报头简单很多。它的报头里面只有一个源端口,还有一个目标端口。这个和TCP里面一样,TCP里面也有一个源端口和目标端口。

​ 然后这个端口肯定也是给应用层去寻找对应的应用程序来进行数据的处理。所以,这个源目端口跟TCP是一样的。

​ 剩下的就是16位UDP长度和16位UDP校验和,这个看下就行,不需要太去纠结。

​ 那么这个里面有没有序号、确认号、标志位等?很明显,它没有。它的报头只有这四个部分,后面是数据部分了。

​ 既然没有序号、确认号、标志位,它是怎么建立连接的呢?没有这些东西是没办法建立连接的,所以UDP协议和TCP协议有很大的不一样。

​ UDP协议不会建立连接。客户端要去服务器那边去访问数据,基于UDP协议的话,是不会先去建立连接的。所以UDP是一个无连接的传输协议。这是和TCP协议最大的区别。

​ TCP的序号和确认号是为了确保数据包没有被丢,确保数据的完整性和正确性。既然UDP协议没有确认号和序号这2个部分,那它肯定是一个不可靠的协议。

​ 我发了一个包过去,你也不会给我回一个确认消息,所以我不知道你有没有收到。既然是不可靠的协议,用UDP去发送数据,可能会出现 丢包

​ 因为我发过去之后,你也没有给我发确认消息。我不知道你有没有收到,那我也不知道要不要给你重传。

​ 像TCP,如果它没有收到对方的确认号,等一段时间后,它会进行一个重传。这就是为什么要确认号的原因啊,没有收到对方的确认号,我就认为你没有收到前面的包。所以我给你重新发一个。

​ 那对于我们的UDP协议来说,你既然不会给我发确认,那你也没有连接可以依靠,所以你就是不可靠的。

​ 缺点:

​ UDP里面出现了丢包、出错,这些都不管,这些都是被允许的。

image

应用层

常见的策略和协议

FTP协议:(File Transfer Protocol,文件传输协议)

是 TCP/IP 协议组中的协议之一,FTP协议包括两个组成部分,其一为FTP服务器,其二为FTP客户端。其中FTP服务器用来存储文件,用户可以使用FTP客户端通过FTP协议访问位于FTP服务器上的资源。

HTTP协议:(超文本传输协议)

是联网上应用最为广泛的一种网络协议。

HTTPS 协议:(Secure Hypertext Transfer Protocol安全超文本传输协议)

是一个安全通信通道,它基于HTTP开发用于在客户计算机和服务器之间交换信息。它使用安全套接字层(SSL)进行信息交换,简单来说它是HTTP的安全版,是使用TLS/SSL加密的HTTP协议。

TCP/IP协议:

TCP/IP 是基于 TCP 和 IP 这两个最初的协议之上的不同的通信协议的大的集合。

TCP:传输控制协议,传输效率低,可靠性强

UDP协议:

​ UDP:用户数据报协议,适用于传输可靠性要求不高,数据量小的数据

DHCP协议:(动态主机配置协议):

发现协议中的引导文件名、空终止符、属名或者空,DHCP供应协议中的受限目录路径名 Options –可选参数字段,参考定义选择列表中的选择文件。

POP3协议:(全名为"Post Office Protocol - Version 3",即"邮局协议版本3")

是TCP/IP协议族中的一员,本协议主要用于支持使用客户端远程管理在服务器上的电子邮件。提供了ssl加密的POP3协议被称为POP3S。

DNS协议:(域名解析协议)

DNS是一种用以将域名转换为IP地址的Internet服务

SMTP协议:(简单邮件传送协议)

Client/Server模式,面向连接

SNMP协议:(简单网络管理协议)

SNMP模型的4个组件:被管理结点、管理站、管理信息、管理协议

SNMP代理:运行SNMP管理进程的被管理结点

对象:描述设备的变量

管理信息库(MIB):保存所有对象的数据结构

Telnet协议:(远程登录协议)

客户服务器模式,能适应许多计算机和操作系统的差异,网络虚拟终端NVT的意义

TFTP:(简单文件传送协议)

客户服务器模式,使用UDP数据报,只支持文件传输,不支持交互,TFTP代码占内存小

image

socket套接字

​ 所谓套接字(socket),就是网络中不同主机上的应用进程之间进行双向通信的端点的抽象。一个套接字就是网络上进程通信的一端,提供了应用层利用网络协议交换数据的机制。从所处的地位来讲。套接字上联应用进程,下联网络协议栈,是应用程序通过网络协议进行通信的接口,使应用程序与网络协议栈进行交互的接口。

​ 基于文件类型的套接字家族:AF_UNIX

​ 基于网络类型的套接字家族:AF_INETN

服务器端代码:
import socket  # 导入网络编程模块
# 创建一个socket对象
server = socket.socket()  # 括号内什么都不写 其实就是默认基于互联网的TCP套接字
# 绑定一个固定的地址
server.bind(('127.0.0.1',8080))  # 127.0.0.1本地回环地址,只允许自己访问自己的机器
# 为什么要两个括号,因为只要元组
# 半连接池
server.listen(5)
# 开业大酬宾,上门送福利,白开水管饱
sock,address = server.accept()
print(sock,address)  # sock是双向通道 address是客户端地址
# 数据交互
sock.send(b'hei my girl')  # 向客户端发送消息
data = sock.recv(1024)  # 接受客户端所返回的数据 1024个bytes
print(data)  # 打印出来
# 断开连接
sock.close()  # 断开连接
server.close()  # 关闭服务器

客户端:
import socket  # 导入网络编程模块
# 产生一个网络编程对象
client = socket.socket()
# 连接服务器(拼接服务器的ip和port)
client.connect(('127.0.0.1',8080))
# 进行数据交互
data = client.recv(1024)  # 接收服务器的数据
print(data)  # 打印数据
client.send(b'hei my father')  # 向服务器发送数据
client.close()  # 关闭

image

代码优化

send与recv

​ 客户端与服务端不能同时执行只有有一个执行另一个接受

消息自定义

​ input获取用户所输入的数据即可但是获得的时候必须要进行编码和解码

循环通道

​ 给程序添加循环功能就可以实现两方互相循环沟通

服务端的持续提供服务

​ 服务端不会因为客户端的断链而报错,如果客户端有报错的可能的话那么我们阔以尝试通过异常捕获的方式来预防报错,等待下一个会话

消息不能为空

​ 判断是否为空,如果是则需要重新输入

无限畅聊版,服务端:
import socket  # 导入网络编程模块
# 创建一个socket对象
server = socket.socket()  # 括号内什么都不写 其实就是默认基于互联网的TCP套接字
# 绑定一个固定的地址
server.bind(('127.0.0.1',8080))  # 127.0.0.1本地回环地址,只允许自己访问自己的机器
# 为什么要两个括号,因为只要元组
# 半连接池
server.listen(5)
# 开业大酬宾,上门送福利,白开水管饱
while True:
    sock,address = server.accept()
    print(sock,address)  # sock是双向通道 address是客户端地址
    while True:
        try:
            # 数据交互
            res = input('请输入您需要对话>>>:').strip()
            if len(res) == 0:
                continue
            sock.send(res.encode('utf-8'))  # 向客户端发送消息
            data = sock.recv(1024)  # 接受客户端所返回的数据 1024个bytes
            if len(data) ==0:
                break
            print(data.decode('utf-8'))  # 打印出来
        except ConnectionResetError:
            sock.close()
            break
无限畅聊版,客户端:
import socket  # 导入网络编程模块
# 产生一个网络编程对象
client = socket.socket()
# 连接服务器(拼接服务器的ip和port)
client.connect(('127.0.0.1',8080))
# 进行数据交互

while True:
    res = input('请输入您需要对话>>>:').strip()
    if len(res) == 0:
        continue
    client.send(res.encode('utf-8'))  # 向服务器发送数据
    data = client.recv(1024)  # 接收服务器的数据
    if len(data) == 0:
        break
    print(data.decode('utf-8'))  # 打印数据

image

半连接池

半连接的两种状况

​ 客户端无法返回ack信息

​ 服务器来不及处理客户端的连接请求

server.listen(5)

​ 主要是为了做缓冲,当服务器在响应了客户端的第一次请求后会进入等待状态,会等客户端发送的ack信息,这时候这个连接池就被称为半连接池。

​ 搬来你劫持其实就是一个容器,系统会自动将半连接放入容器中,阔以避免半连接多而保证资源耗光

image

黏包问题

什么是黏包问题

​ 发送方发送的若干包数据到接收方时粘成一包,从接受缓冲区来看就是后一包数据的头紧接着前一包数据的尾。

黏包问题出现的原因

​ 若连续几次发送的数据都很小tcp就会根据优化算法把这些数据整合成一包后一次性发送,这样接收方就收到了黏包数据。

​ 接收方会先把收到的数据放在系统接受缓冲区,用户进程从该缓冲区读取数据,若下一包数据到达前时一包数据尚未被用户进程取走,则下一包数据进入缓冲区时就会到钱一包数据之后,而用户进程根据预先设定的缓冲区大小从系统中读取数据这样便一辞去了很多包。

解决方法--封包和解包

​ 发送方在发送数据的包前,加入数据长度,将数据包的结构变成[dataLen|data]的结构再进行发送。而接收方通过解析这种结构,从而将多个数据包聚合形成的黏包分解成一个个独立的数据包。

​ struct模块无论数据长度是多少,都可以帮你打包成固定的长度,然后基于该固定长度还可以反向解析出数据真是的长度,但是struct模块在遇到数据量特别大的数字时那么就没有办法打包

思路:

​ 1.先将真实数据的长度制作成固定的长度 4

​ 2.先发送固定长度的报头

​ 3.再发送真实的数据

​ 1.先接受固定长度报头 4

​ 2.再跟还有报头解压出真实长度

​ 3.根据真实长度接收即可

import struct  # 导入模块

# 设置需要查看或者获取需要查看的数据
data = '这不是瞎搞嘛,选课系统都还没整明白'  # 设置
print(len(data))  # 获取这个数据的长度  17
res = struct.pack('i',len(data))  # 将原来那个数据打包  第一个值默认使用i
print(len(res))  # 打印打包好后的值是什么长度  4
res = struct.unpack('i',res)  # 将打包好的数据进行解压
print(res[0])  # 打印长度  17

image

posted @ 2022-08-05 20:11  Joseph-bright  阅读(87)  评论(0编辑  收藏  举报