近期学习python基础知识,到网络编程部分以为颇有意思,写个博客记录一下,首先可先了解一下计算机网络的基础知识,参考相关书籍便可,本文只作简单知识介绍!python
1.OSI七层协议,TCP、UDP协议程序员
首先, 咱们今天使用的计算机都是要联网使用的. 不多有那种单机走天下的状况了. 那么咱们的计算机是如何经过网络实现通讯的. 咱们先了解一些关于网络的基础知识. 而后再开始学习一些关于网络编程的内容, 第一个要解释的名词叫协议. 咱们只有明白协议是什么, 后面再看各类各样的通讯规则就容易的多了.
官话: 网络协议是通讯计算机双方必须共同听从的一组约定。如怎么样创建链接、怎么样互相识别等。只有遵照这个约定,计算机之间才能相互通讯交流
普通话: 两台计算机之间约定好. 我发送的数据格式是什么. 你接收到数据以后. 使用相同的格式来拿到数据
例子: 你和一个韩国人交流. 你说中文, 他说韩文. 你俩是不能明白对方说的是什么的. 怎么办. 你俩约定好, 都说英语.实现交流. 这个就叫协议.
网络协议: 互联网之间互相传递消息的时候使用统一的一系列约定
在今天的互联网数据传输中通常使用的是OSI七层协议. 也有简称为五层, 四层协议. 只是对不同网络层的定义不同.
内部原理和做用是同样的.编程
下面简单介绍一下OSI七层协议的基本功能
首先是物理层
: 该层是网络通讯的数据传输介质,由链接不一样结点的电缆与设备共同构成。主要跟功能是:利用传输介质为数据链路层提供物理链接,负责处理数据传输并监控数据出错率,以便数据流的透明传输数据链路层:
这一层负责装配本身和对方主机的MAC地址. MAC地址: 每一个网络设备的惟一编码. 全球惟一. 由不同厂商直接烧录在网卡上.
做用: 在庞大的网络系统中, 你要发送的数据究竟是要给谁的. 由谁发送出来的. 这就至关于你写信的时候的信封. 上面得写清楚收货人地址.网络层:
在有了MAC地址其实咱们的电脑就能够开始通讯了. 可是. 此时的通讯方式是广播. 至关于通讯基本靠吼. 你发送一个数据出去. 会自动的发给当前网络下的全部计算机. 而后每一个计算机的网卡会看一眼这个数据是不是发给本身的. 像这样的通讯方式, 若是计算机的数据量少. 是没有问题的. 可是. 若是全球全部计算机都按照这样的方式来传输消息. 那不只是效率的问题了. 绝对是灾难性的. 那怎么办. 你们就想到了一个新的方案, 这个方案叫IP协议. 使用IP协议就把不同区域的计算机划分红一个一个的子网. 子网内的通讯使用广播来传递消息. 广播外经过路由进行传递消息. 你能够理解为不同快递公司的分拨中心. 我给你邮寄一个快递. 先看一下是不是本身区域的. 是自⼰己区域直接挨家挨户找就OK了. 可是若是不是我这个区域的. 就经过分拨中心(路由器网关)找到你所在的区域的分拨中心(路由器网关), 再经过你的分拨中心下发给你. 这里IP协议的做用就体现出来了. 专门用来划分子网的.那么在传输数据的时候就必需要把对方的ip地址带着. 有了这个ip再加上子网掩码就能够判断出该数据究竟是属于哪一个子网下的数据.
IP地址: 由4位点分⼗十进制表示. 每位最⼤大255. 故IP地址的范围: 0.0.0.0~255.255.255.255. 为什什么是255, 答:
28 每一位用8位2进制表示, 合起来32位就能够表示一个计算机的ip地址
子网掩码: 用来划分子网的一个4位点分十进制.
网关: 路由器在子网内的ip. 不同局域网进行数据传输的接口(分拨中心)
计算子网的过程:服务器
1 ip1: 192.168.123.16 2 ip2: 192.168.123.45 3 子网掩码: 255.255.255.0 4 所有转化成二进制 4 ip1: 11000000 10101000 01111011 00010000 5 ip2: 11000000 10101000 01111011 00101101 6 子网: 11111111 11111111 11111111 00000000 7 让ip1和ip2分别和子网进行"与"运算 8 ip1 & 子网: 11000000 10101000 01111011 00000000 9 ip2 & 子网: 11000000 10101000 01111011 00000000 10 相等. OK 这两个IP就是同一个子网
传输层
: 咱们如今解决了外界的数据传输问题. 使用MAC地址和IP地址能够惟一的定位到一台计算机了. 那么还有一个问题没有解决. 咱们知道一台计算机内是颇有可能运行着多个网络应用程序的. 好比, 你开着LOL, 挂着DNF, 聊着QQ, 还看着快播. 那么此时你的计算机网卡接收到了来自远方的一条数据. 那么这一条数据到底给那个应用呢? 说白了, 快递送到你公司了. 地址没毛病了. 但是你公司那么多人. 这个快递到底给谁? 不能乱给啊. 怎么办呢? 互联网大佬们想到了一个新词叫端口.
传输层规定: 给每⼀一个应⽤用程序分配一个惟一的端口号. 当有数据发送过来以后. 经过端口号来决定该数据发送的具体应用程序.可是根据不同应用程序对网络的需求的不同(有的要求快, 有的要求可靠) 又把传输层划分红两个协议. 一个叫TCP, 一个叫UDP. 因此, 咱们常说的TCP/IP协议中最重要, 也是咱们最关注的其实就是IP和端口了了. 由于有了这两个, 咱们其实就能够定位到某一台计算机上的某个网络应用程序了了. 也就能够给他发送消息了
32位计算机上的端口数:
TCP : 65536个
UDP: 65536个
TCP和UDP的区别:网络
应用层
: TCP+IP能够定位到计算机上的某个应用了了. 可是不同用传输的数据格式多是不同样的. 就比如快递. 有的是大包裹. 有的是小文件. 一个要用大快递袋装, 一个要用小快递袋装. 到了了应用层. 咱们通常是根据不同类型的应用程序进行的再一次封装. 好比, HTTP协议, SMTP协议, FTP协议. 等等.2.初识Socket-TCP编程
在几乎全部的编程语言中, 咱们在编写网络程序的时候都要使用到socket. socket翻译过来叫套接字. 咱们上面也了解到了一次网络通讯的数据须要包裹着mac, ip, port等信息. 可是若是每次咱们开发都要程序员去⼀个一个的去准备数据, 那工做量绝对是绝望的. 因此, 计算机提出了了socket. socket帮助咱们完成了网络通讯中的绝大多数操做. 咱们只须要告诉socket. 我要向哪台计算机(ip, port)发送数据. 剩下的全部东西都由socket帮咱们完成. 因此使用socket完成数据传输是很是方便的.
话很少说,练起来吧~~socket
server端:编程语言
import socket #建立socket通道 sk = socket.socket() sk.bind(("127.0.0.1", 8088)) # 绑定ip和端口 sk.listen() # 开始监听 print("服务器就绪,等待链接") conn, address = sk.accept() # 程序阻塞,等待链接 print("有客户端链接,ip是:", address) # address是客户端的ip和端口 while 1: #能持续向客户端传递消息 conn.send(input(">>>").encode("utf-8")) # 发送的内容只能是bytes print(conn.recv(1024).decode("utf-8")) #展现接收到的消息
client端:ide
import socket sk = socket.socket() # 建立通道 print("客户端初始化完成") sk.connect(("127.0.0.1", 8088)) # 创建链接 print("客户端链接成功") while 1: #持续向服务端发送消息 print(sk.recv(1024).decode("utf-8")) # 最大接受1024字节的内容 sk.send(input(">>>").encode("utf-8")) #展现接收到的消息
3.初识Socket-UDP编程
server端:学习
import socket sk = socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM) sk.bind(("127.0.0.1", 8089)) msg, address = sk.recvfrom(1024) print(msg) sk.sendto(b'hi', address)
client端:编码
import socket sk = socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM) sk.sendto(b'hello', ("127.0.0.1", 8089)) msg, address = sk.recvfrom(1024) print(msg)
跟TCP编程原理差很少,只是UDP不用长时间创建链接,发送一个包,不用等回复,也不须要理会对方收到仍是没收到,没有严格意义上的服务端和客户端
4.黏包现象
在使用TCP协议进行数据传输的时候, 会有如下问题出现.
client端:
import socket sk = socket.socket() # 建立通道 print("客户端初始化完成") sk.connect(("127.0.0.1", 8088)) # 创建链接 print("客户端链接成功") sk.send("哈哈".encode("utf-8")) sk.send("哈哈".encode("utf-8")) #发送两次 print("发送完毕") sk.close()
server端:
import socket # 建立socket通道 sk = socket.socket() sk.bind(("127.0.0.1", 8088)) # 绑定ip和端口 sk.listen() # 开始监听 print("服务器就绪,等待链接") conn, address = sk.accept() # 程序阻塞,等待链接 print("有客户端链接,ip是:", address) # address是客户端的ip和端口 msg1 = conn.recv(1024) print(msg1.decode("utf-8")) msg2 = conn.recv(1024) print(msg2.decode("utf-8")) sk.close()
server端收到的内容:
能够看到,两次发送的内容,黏在一块儿,变成了一个包,就是典型的黏包现象。
那么如何解决黏包问题呢? 很简单. 之因此出现黏包就是由于数据没有边界. 直接把两个包混合成了一个包. 那么我能够在发送数据的时候. 指定边界. 告诉对方. 我接下来这个数据包有多大. 对面接收数据的时候呢, 先读取该数据包的大小.而后再读取数据. 就不会产生黏包了.
普通话: 发送数据的时候制定数据的格式: 长度+数据 接收的时候就知道有多少是当前这个数据包的大小了. 也就至关于定义了分隔边界了.
展现一波操做:
client端:
import socket sk = socket.socket() # 建立通道 print("客户端初始化完成") sk.connect(("127.0.0.1", 8088)) # 创建链接 print("客户端链接成功") s = "哈哈" bs = sk.send(s.encode("utf-8")) # 计算数据长度.格式化成四位数字 bs_len = format(len(bs), "04d").encode("utf-8") # 发送数据以前,先发送数据的长度 sk.send(bs_len) sk.send(bs) # 发送第二次 sk.send(bs_len) sk.send(bs) print("发送完毕") sk.close()
server端:
import socket # 建立socket通道 sk = socket.socket() sk.bind(("127.0.0.1", 8088)) # 绑定ip和端口 sk.listen() # 开始监听 print("服务器就绪,等待链接") conn, address = sk.accept() # 程序阻塞,等待链接 print("有客户端链接,ip是:", address) # address是客户端的ip和端口 # 接收数据长度4个字节,转化成数字 bs_len = int(conn.recv(4).decode("utf-8")) # 读取数据 msg1 = conn.recv(1024) print(msg1.decode("utf-8")) # 再接收数据长度,读取数据 bs_len = int(conn.recv(4).decode("utf-8")) msg2 = conn.recv(1024) print(msg2.decode("utf-8")) sk.close()
可是,你应该发现了,这种传输的方式虽然能解决黏包问题,可是每次接收都要先定义接收的长度,喵的岂不是太累了??这个问题,python也有本身的态度,代码坚定要给你搞简单,就引入了一个新的模块struct
,这是重点,拿小本本记下来,要考!!
展现一下struct的用法:
import struct ret = struct.pack("i", 123456789) # 把数字打包成字节 print(ret) print(len(ret)) # 4 不论数字大小,定死了4个字节 # 把字节还原回数字 ds = b'\x15\xcd[\x07' num = struct.unpack("i", ds)[0] # num返回的是一个元祖,取索引为0的元素,也就是第一个元素,就是咱们传输的数字 print(num) # 123456789
好的,回归到黏包的问题上来,论如何优雅的解决黏包的问题:
client端:
import socket import struct sk = socket.socket() # 建立通道 print("客户端初始化完成") sk.connect(("127.0.0.1", 8088)) # 创建链接 print("客户端链接成功") msg_bs = "你好呀".encode("utf-8") msg_struct_len = struct.pack("i", len(msg_bs)) sk.send(msg_struct_len) sk.send(msg_bs) print("发送完毕") # 发送第二次 sk.send(msg_struct_len) sk.send(msg_bs) print("发送完毕") sk.close()
server端:
import socket import struct # 建立socket通道 sk = socket.socket() sk.bind(("127.0.0.1", 8088)) # 绑定ip和端口 sk.listen() # 开始监听 print("服务器就绪,等待链接") conn, address = sk.accept() # 程序阻塞,等待链接 print("有客户端链接,ip是:", address) # address是客户端的ip和端口 msg_struct_len = conn.recv(4) msg_len = struct.unpack("i", msg_struct_len)[0] data = conn.recv(msg_len) print(data.decode("utf-8")) print("接收完毕") # 接收第二个包 msg_struct_len = conn.recv(4) msg_len = struct.unpack("i", msg_struct_len)[0] data = conn.recv(msg_len) print(data.decode("utf-8")) print("接收完毕") sk.close()
可是吧,这样也是稍显繁琐了,若是屡次使用,也可将用到的struct模块,封装起来,须要用到的时候,就导入这个自定义的模块:
封装的模块my_struct_util.py:
import struct def my_send(sk, msg): msg_len = msg.encode("utf-8") msg_struct_len = struct.pack("i", len(msg_len)) sk.send(msg_struct_len) sk.send(msg_len) def my_recv(sk): msg_struct_len = sk.recv(4) msg_len = struct.unpack("i", msg_struct_len)[0] data = sk.recv(msg_len) print(data.decode("utf-8"))
client端:
import socket import my_socket_util as msu sk = socket.socket() # 建立通道 print("客户端初始化完成") sk.connect(("127.0.0.1", 8088)) # 创建链接 print("客户端链接成功") msu.my_send(sk, "你好吗") msu.my_send(sk, "你好吗") sk.close()
server端:
import socket import my_socket_util as msu # 建立socket通道 sk = socket.socket() sk.bind(("127.0.0.1", 8088)) # 绑定ip和端口 sk.listen() # 开始监听 print("服务器就绪,等待链接") conn, address = sk.accept() # 程序阻塞,等待链接 print("有客户端链接,ip是:", address) # address是客户端的ip和端口 msu.my_recv(conn) msu.my_recv(conn) sk.close()
这样,是否是就简便了不少呢~~