编程语言
首页 > 编程语言> > 12、网络编程之网络通信

12、网络编程之网络通信

作者:互联网

CS与BS架构

CS:Client ===》Server 客户端与服务端

比如:腾讯视频

BS:Browser===》Server 浏览器与服务端

12.1网络通信

网络存在的意义就是跨地域数据传输,称之为通信

网络=物理链接介质+互联网通信协议

物理链接介质,比如打电话需要电话线链接两地

互联网通信协议,比如普通话,可以将说不同方言的中国人通过普通话来交流,普通话就相当于一个标准,大家都懂的

1、OSI七层协议

七层:“应 表 会 传 网 数 物”

开发程序掌握五层即可:应用层,传输层,网络层,数据链路层,物理层

协议:规定数据的组织格式

格式:头部+数据部分

物理层功能(位):主要是基于电器特性发送高低电压(电信号),高电压对应数字1,低电压对应数字0

数据链路层:帧

单纯的电信号0和1没有任何意义,必须规定电信号多少位一组,每组什么意思

规定1:一组数据称为一个数据帧

规定2:每一个数据帧分为两部分,报头head和数据data

​ head:源地址与目标地址 ,改地址是mac地址

规定3:但凡接入互联网的主机必须要有一块网卡,每块网卡在出厂时都烧制好一个全世界独一无二的地址,该地址称为mac地址

以太网的工作方式是广播

网络层:

划分广播域,每一个广播但凡要接通外部,一定要有一个网关帮内部的计算机转发包到公网

网关于外界通信走的是路由协议

规定1:一组数据称为一个数据包

规定2:数据包分为两部分:头+数据

​ 头包含:源地址与目标地址,该地址是IP地址

​ 数据包含的:传输层发过来的整体的内容

一个合法的ipv4地址组成部分=ip地址/子网掩码地址

172.16.10.1/255.255.255.0

172.16.10.1/24 表示子网掩码从左往右有24位都是1

172.16.10.1 不叫子网掩码表示同172.16.10.1/24一样

计算:同为1则为1,得到网络地址

计算机1:
172.16.10.1: 10101100.00010000.00001010.000000001
255255.255.255.0: 11111111.11111111.11111111.000000000
172.16.10.0: 10101100.00010000.00001010.000000000

计算机2:
172.16.10.2: 10101100.00010000.00001010.000000010
255.255.255.255.0: 11111111.11111111.11111111.000000000
172.16.10.0: 10101100.00010000.00001010.000000000

1、先判断网络地址在不在一个局域网内

2、不在同一个局域网内通过网关发送

(源mac地址,xxxx)(源ip地址,目标ip地址)数据
(源mac地址,网关的mac地址)(172.16.10.10/24,101.100.200.11/10)数据

事先知道的是对方的ip地址
但是计算机的底层通信是基于ethernet以太网协议的mac地址通信
所以必须能够将ip地址解析成mac地址,即ARP

两台计算机在同一个局域网内

ARP拿到两台计算机的ip地址,计算网络地址,如果一样,拿到计算机2的mac地址就可以了,如何在发送广播包

发送端mac FF:FF:FF:FF:FF:FF 172.16.10.10/24 172.16.10.11/24 数据

两台计算机不在同一个局域网内

ARP拿到两台计算机的ip地址,计算网络地址,两者网络地址不一样,即不在同一个局域网,应该拿到网关的mac地址,然后再发送广播包

发送端mac FF:FF:FF:FF:FF:FF 172.16.10.10/24 172.16.10.1/24 数据

ip地址+mac地址=》标识全世界范围内独一无二的一台计算机

或者:
ip地址=》标识全世界范围内独一无二的一台计算机

mac地址用来标识局域网内的独一无二的地址

传输层:tcp\udp =>基于端口 段

端口范围0-65535,0-1023为系统占用端口
ip+port=》标识全世界范围内独一无二的一个基于网络通信的应用程序

tcp协议:

可靠传输,发送数据必须等到对方确认后才算完成,才会将自己内存中的数据清理掉,否则重传 TCP数据包没有长度限制,理论上可以无限长,但是为了保证网络的效率,通常TCP数据包的长度不会超过IP数据包的长度,以确保单个TCP数据包不必再分割。当服务端大量处于TIME_WAIT状态时意味着服务端正在经历高并发

三次握手建立连接

建立连接是为了传数据做准备的,三次握手即可

四次挥手断开连接

断开连接时,由于链接内有数据传输,所以必须分四次断开

客户端与服务端要分别确认才能断开

udp协议:

不可靠传输,”报头”部分一共只有8个字节,总长度不超过65,535字节,正好放进一个IP数据包。

基于tcp协议通信:必须建立一个双向通信的链接

tcp协议的半连接池:

如果服务器收到多个数据请求发送,服务器会有一个半连接池暂时存放数据。

[链接请求1,链接请求2,链接请求3,链接请求5]

应用层:

可以自定义协议=》头部+数据部分
自定义协议需要注意的问题:
1、两大组成部分=头部+数据部分
头部:放对数据的描述信息
比如:数据要发给谁,数据的类型,数据的长度
数据部分:想要发的数据

​ 2、头部的长度必须固定
​ 因为接收端要通过头部获取所接接收数据的详细信息
http https ftp

www.163.com

12.2Socket抽象层

经常把socket翻译为套接字,socket是在应用层和传输层之间的一个抽象层,它把TCP/IP层复杂的操作抽象为几个简单的接口供应用层调用已实现进程在网络中通信。

传输层,网络层,数据链路层,物理层都可以封装成socket

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

套接字家族的名字:AF_UNIX

unix一切皆文件,基于文件的套接字调用的就是底层的文件系统来取数据,两个套接字进程运行在同一机器,可以通过访问同一个文件系统间接完成通信

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

套接字家族的名字:AF_INET

(还有AF_INET6被用于ipv6,还有一些其他的地址家族,不过,他们要么是只用于某个平台,要么就是已经被废弃,或者是很少被使用,或者是根本没有实现,所有地址家族中,AF_INET是使用最广泛的一个,python支持很多种地址家族,但是由于我们只关心网络编程,所以大部分时候我么只使用AF_INET)

套接字的工作流程

套接字封装的是传输层以下层

1、基于tcp协议的套接字通信流程

服务端套接字函数
s.bind() 绑定(主机,端口号)到套接字
s.listen() 开始TCP监听
s.accept() 被动接受TCP客户的连接,(阻塞式)等待连接的到来

客户端套接字函数
s.connect() 主动初始化TCP服务器连接
s.connect_ex() connect()函数的扩展版本,出错时返回出错码,而不是抛出异常

公共用途的套接字函数
s.recv() 接收TCP数据
s.send() 发送TCP数据(send在待发送数据量大于己端缓存区剩余空间时,数据丢失,不会发完)
s.sendall() 发送完整的TCP数据(本质就是循环调用send,sendall在待发送数据量大于己端缓存区剩余空间时,数据不丢失,循环调用send直到发完)
s.recvfrom() 接收UDP数据
s.sendto() 发送UDP数据
s.getpeername() 连接到当前套接字的远端的地址
s.getsockname() 当前套接字的地址
s.getsockopt() 返回指定套接字的参数
s.setsockopt() 设置指定套接字的参数
s.close() 关闭套接字

面向锁的套接字方法
s.setblocking() 设置套接字的阻塞与非阻塞模式
s.settimeout() 设置阻塞套接字操作的超时时间
s.gettimeout() 得到阻塞套接字操作的超时时间

面向文件的套接字的函数
s.fileno() 套接字的文件描述符
s.makefile() 创建一个与该套接字相关的文件

打电话模拟案例

服务端

'''
基于TCP套接字的工作流程
打电话为例子
'''
import socket

# 1、买手机
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) # 流失协议即tcp协议

# 2、绑定手机卡 ip127.0.0.1 可以自己测试使用 服务端自己的ip和端口
phone.bind('127.0.0.1',8080)  # 0-65535,1024以前的都被系统保留使用

# 3、手机开机
# backlog=5 指的是半连接池的大小
phone.listen(5)

# 4、等待电话连接请求
# accept() 没有参数 里面包含三次握手的连接
# 链接循环
while True:
    conn,client_addr = phone.accept()

    # 5、通信:收、发消息
    # 最大接收的数据量为1024Bytes,收到的是bytes
    while True:
        try:
            data = conn.recv(1024)
            if len(data)==0:
                break
            print('客户端发来的信息:',data.decode('utf-8'))
            conn.send(data.upper())
        except Exception:
            break

    # 6、关闭电话连接conn
    conn.close()

# 7、可选 关机
phone.close()

客户端

'''
基于TCP套接字的工作流程
打电话为例子
'''
import socket

# 1.买手机
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) # 流失协议即tcp协议

# 2、拨通电话 绑定的是服务端的ip和端口
phone.connect(("127.0.0.1",8080))

# 3、通信 发信息也是bytes类型
while True:
    msg = input('输入要发送的信息').strip()
    if len(msg) == 0:
        continue
    phone.send(msg.encode('utf-8'))
    data = phone.recv(1024)
    print(data.decode('utf-8'))

# 4、关闭连接 必须关闭
phone.close()

bug1:

如果客户端发送一个空字符给服务端,服务端会收不到信息,会阻塞在客户端收服务端发信息这步。

解决方式:不让客户端发送空字符串

if len(msg)==0:
    continue

客户端可以发送空字符串,但是接收不到服务端发回的信息,因为客户端收发信息都是在客户端里面完成的,收信息是在自己的缓存里面收信息

bug2:

客户端非法断开连接,服务端会抛出异常,不会结束通信

解决方式:可以使用try····except的方式结束通信,等待下一次通信即链接循环

2、基于udp协议的套接字通信

服务端

import socket

server = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) # 数据协议 udp协议

server.bind('127.0.0.1',8081)

while True:
    data,client_addr = server.recvfrom(1024)
    server.sendto(data.upper(),client_addr)

server.close()

客户端

import socket

client = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) # 数据协议 udp协议

while True:
    msg = input('请输入要发送的信息').strip()
    client.sendto('hhhh'.encode('utf-8'),('127.0.0.1',8081))
    data, server_addr =client.recvfrom(1024)
    print(data.decode('utf-8'))

client.close()

udp可以发空的文件,但是udp协议传的时候不是传的空文件,因为有个数据报可以发出去。

12.3粘包问题

1、什么是粘包问题

接收方不知道消息与消息之间的界限,不知道怎样正确的接收消息,可能会出现上一次消息还没有接收完的情况,则会影响下一次接收信息

2、粘包问题出现的原因

1、tcp是流式协议,数据像水流一样粘在一起,没有任何边界区分

2、收数据没有收干净,有残留,就会和下一次结果混淆在一起

3、怎样解决粘包问题

拿到数据的总大小total_size,循环接收recv_size=0,每接收一次,recv_size+=接收的长度,直到total_size=recv_size

注意: 只有TCP有粘包现象,UDP永远不会粘包

1、TCP(transport control protocol,传输控制协议)是面向连接的,面向流的,提供高可靠性服务。收发两端(客户端和服务器端)都要有一一成对的socket,因此,发送端为了将多个发往接收端的包,更有效的发到对方,使用了优化方法(Nagle算法),将多次间隔较小且数据量小的数据,合并成一个大的数据块,然后进行封包。这样,接收端,就难于分辨出来了,必须提供科学的拆包机制。 即面向流的通信是无消息保护边界的。

2、UDP(user datagram protocol,用户数据报协议)是无连接的,面向消息的,提供高效率服务。不会使用块的合并优化算法,, 由于UDP支持的是一对多的模式,所以接收端的skbuff(套接字缓冲区)采用了链式结构来记录每一个到达的UDP包,在每个UDP包中就有了消息头(消息来源地址,端口等信息),这样,对于接收端来说,就容易进行区分处理了。 即面向消息的通信是有消息保护边界的。

3、tcp是基于数据流的,于是收发的消息不能为空,这就需要在客户端和服务端都添加空消息的处理机制,防止程序卡住,而udp是基于数据报的,即便是你输入的是空内容(直接回车),那也不是空消息,udp协议会帮你封装上消息头,

其他协议:

DHCP: DHCP协议采用客户端/服务器模型,主机地址的动态分配任务由网络主机驱动。当DHCP服务器接收到来自网络主机申请地址的信息时,才会向网络主机发送相关的地址配置等信息,以实现网络主机地址信息的动态配置。

DNS:域名解析。要访问一个网站需要ip+port才能访问到,但是ip+port记起来太麻烦了,通过DNS可以为ip+port取一个名字,port默认为80

DNS端口号53 DHCP端口号57

DNS的查询过程是udp协议

12.4socketserver模块

tcp端

import socketserver

class MyRequestHandle(socketserver.BaseRequestHandler):
    def handle(self):
        # 如果tcp协议,self.request=>conn
        print(self.client_address)
        while True:
            try:
                msg = self.request.recv(1024)
                if len(msg) == 0: break
                self.request.send(msg.upper())
            except Exception:
                break
        self.request.close()



#  服务端应该做两件事
# 第一件事:循环地从半连接池中取出链接请求与其建立双向链接,拿到链接对象
s=socketserver.ThreadingTCPServer(('127.0.0.1',8889),MyRequestHandle)
s.serve_forever()
# 等同于
# while True:
#     conn,client_addr=server.accept()
#     启动一个线程(conn,client_addr)

# 第二件事:拿到链接对象,与其进行通信循环===>handle

标签:网络通信,12,socket,ip,编程,地址,接字,数据,服务端
来源: https://www.cnblogs.com/xionghuan01/p/16542434.html