python(socket模块)

今日内容概要

  • 传输层之TCP与UDP协议
  • 应用层
  • socket模块简介
  • socket模块基本使用
  • 代码优化处理
  • 半连接池

传输层之TCP与UDP协议

tcp与udp都是用来规定通信方式的
	通信的时候是可以随心所欲的发送消息,也可以遵循一些协议符合要求的聊
    随意:文字,图片,视频
    协议:发送的数据开头带的有尊称、首行空两格、、、、
    
PS:不遵守上述的协议也是可以通信的,只不过遵循了协议会更和规合法合理!!

TCP协议

流式协议 可靠协议
当应用程序想通过TCP协议实现远程通信时,彼此之间必须先先建立双向通信通道,基于该双向通道实现数据的远程交互,该双向通道直到任意一方主动断开才会失效

三次握手	建立链接
	重要状态
    	listen监听态:等待对方发请求
        syn_rcvd态:忙于恢复确认建立请求
            # 洪水攻击:服务端在同一时间接收到了大量的要求建立链接的请求
 1.TCP协议称为可靠协议(数据不容易丢失)
    造成数据不容易丢失的原因不是因为有双向通道,而是因为有反馈机制
    给对方发送消息之后会保留一个副本,直到对方对方回应已经收到消息了才会删除
    否则会在一定的时间内反复发送
 2.洪水攻击
	同一时间被大量的客户端请求建立链接,会导致服务端一致处于SYN_RCVD状态
3.服务端如何区分客户端建立链接的请求
	可以对请求做唯一标识
    

四次挥手	断开链接
不能合并成三次:因为中间需要确认消息是否已经发完(TIMS_WAIT)
建立一个连接需要三次握手,而终止一个连接要经过四次握手

当服务端或者客户端不想再与对方进行通信之后,双方任意一方都可以主动发起断开链接的请求,我们还是以客户端主动发起为例

总结:挥手必须是四次,中间的两次不能合并成一次,原因就在于需要检查是否还有数据需要给对方发送

image

三次握手:

建立双向通道的过程称之为三次握手,建立通道的发起者可以是客户端也可以是服务端,下面我们就以客户端先主动发起为例

  • 客户端会朝服务端发送一个请求询问服务端:"我能不能挖一条通往你家的地道"

  • 服务端收到请求,回复说:"好吧 你挖吧",由于TCP是双向通道,客户端挖向服务端的通道只能给客户端朝服务端发消息使用,服务端要向给客户端发消息是没办法走这一条通道的,需要自己挖一条通往客户端的通道

    所以服务端在回复同意客户端挖通道的同时还会问一句:"那我能不能也挖一条通往你家的通道"

  • 客户端收到服务端请求后客户端到服务端的通道就挖成功了,然后也会同意服务端的请求,服务端挖向客户端的通道也会成功

image

UDP协议

被称为数据报协议,不可靠协议
当应用程序希望通过UDP与一个应用程序通信时,传输数据之前源端和终端不建立连接。
当它想传送时就简单地去抓取来自应用程序的数据,并尽可能快地把它扔到网络上。
数据发送没有通道的概念 发送出去了就不管了

无连接:通信不需要建立连接,只要知道对方地址就可以发送数据
不可靠:不能保证数据是否可以安全、有序的到达对端
面向数据报:一个数据报的最大大小为64k,加上udp数据报头部信息一共64位还要占8个字节,因此数据大小不能超过64k-8。

优点:
	快,比 TCP 稍安全。

	UDP 没有 TCP 的握手、确认、窗口、重传、拥塞控制等机制,UDP 是一个无状态的传输协议,所以它在传递数据时非常快。没有 TCP 的这些机制,UDP 较 TCP 被攻击者利用的漏洞就要少一些。但 UDP 也是无法避免攻击的,比如:UDP Flood 攻击。

缺点:
	不可靠,不稳定。
	因为 UDP 没有 TCP 那些可靠的机制,在数据传递时,如果网络质量不好,就会很容易丢包。

image

应用层

应用层相当于是程序员自己写的应用程序,里面的协议非常的多
应用程序收到"传输层"的数据,接下来就要对数据进行解包。由于互联网是开放架构,数据来源五花八门,必须事先规定好通信的数据格式,否则接收方根本无法获得真正发送的数据内容。“应用层"的作用就是规定应用程序使用的数据格式,例如我们TCP协议之上常见的Email、HTTP、FTP等协议,这些协议就组成了互联网协议的应用层。

应用层提供了诸多的协议,比如HTTP、FTP、POP3、IMAP等协议,用于约定特殊应用在应用层封装与解包通信的格式。保证统一,避免出现类似于外国人听不懂粤语,俄罗斯人听不懂德语等语言不互通问题。

image

socket模块简介

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

socket 也叫套接字
	基于文件类型的套接字家族(单机)
    	AF_UNIX
    基于网络类型的套接字家族(联网)
    	AF_INET

image

image

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()
	关闭双向通道

image

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()

image

代码优化

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

错误:
image
解决方法:

解决报错的代码
#加入一条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获取消息 直接把消息写死即可 
posted @ 2022-11-16 19:01  亓官扶苏  阅读(287)  评论(0)    收藏  举报