网络编程

网络基础相关知识:

1.架构

(1)C/S 架构:client客户端和server服务器端       优点:能充分发挥pc机的性能

(2)B/S架构:browser浏览器和server服务器端  隶属于C/S架构      优点:统一了应用的接口

2.通信

(1)同一台电脑上两个py程序通信: 打开一个文件

(2)两个电脑如何通信: 连一根网线

(3)多个电脑通信: 借助于交换机  

多个电脑通信过程:电脑1首先发送一个请求帧(我的ip地址是:....,我的mac地址是...;我要找ip地址是....的主机),将此请求发送给交换机,交换机广播这条消息给其他所有主机,目标主机接收到消息后.对比发现自己就是被找的主机后,回复给交换机信息(我的ip是....,我的mac地址是......;请回复给ip地址是....,mac地址是:...主机)交换机以单播的形式返回给源主机.

网卡有mac地址(物理地址):mac地址全球唯一的    mac地址是网卡制造商给的    mac地址是十六进制    类似身份证

http协议:超文本传输协议,基于tcp之上建立,制定一套数据传输规范:

              (1)数据传输方式包括:请求头请求体和响应头响应体。

              (2)无状态短连接(请求来一次断开一次)

https协议:超文本传输安全协议,是http的安全版。即http下加入ssl,https的安全基础是ssl。https存在不同于http的默认端口及一个加密/身份验证层。采用                  https协议的服务器必须从CA申请一个用于证明服务器用途类型的证书。该证书只有用于对应的服务器的时候,客户端才信任此主机。

ip地址:是一个四位点十进制,它标识了计算机在网络中的位置  

ipv4协议:点分十进制

ipv6协议:冒分十六进制

rarp协议:通过mac地址找到ip地址

arp协议:通过目标ip地址获取目标mac地址的一个协议(过程)

端口:操作系统(os)为本机上每一个运行程序者随机分配的一个端口,其他电脑上的程序可以通过端口获取到这个程序.

ip地址+端口 = 找到某台电脑的端口    ip找到主机   端口找到某一服务

交换机通信方式:1.广播;2.单播:一对一;3.组播:一对多

路由器:连接不同网段(ip地址),路由(里面含有一个路由表)

交换机的主要功能:是组织局域网经过交换机内部处理解析信息之后将信息已点对点,点对多的形式发送给固定端。

路由器的主要功能:进行跨网段数据传输路由出网络传输的最佳路径。

网关:类似于一个局域网的出口和入口

网段:一个局域网内的ip地址范围

子网掩码:子网掩码&ip地址==网段

例:

ip地址:  192.168.12.104   ==>    1100 0000. 1010 1000. 0000 1010. 0110 1000
&
子网掩码: 255.255.255.0 ==> 1111 1111. 1111 1111. 1111 1111. 0000 0000
网段: 1100 0000. 1010 1000. 0000 1010. 0000 0000

196. 168. 12. 0

标准输出:屏幕(stdout)    标准输入:键盘(stdin)

osi七层模型:

        协议                    物理设备

应用层:       http/https/ftp/snmp/pop3/dns/stmp      七层防火墙,四层路由器,四层交换机

表示层:                    数据表示,安全,压缩 。格式:jpeg,ascll,加密格式等

会话层:                    建立,管理,终止会话。  

传输层:            tcp/udp                         四层交换机,四层路由器,四层防火墙

网络层:            ip/ipv4/ipv6                        路由器,三层交换机,三层防火墙

数据链路层:     arp/rarp                        以太网交换机(二层交换机),网卡,网桥(mac地址相关)

物理层:                                                   集线器,网线,光纤,一层交换机

二层交换机:组织局域网,不具备跨网段传输

三层交换机:具有跨网段传输

四层交换机:帮用户采取tcp/udp协议,找到对应ip端口。

网桥:可以实现跨网段的传输数据

tcp协议:比较安全   udp协议:传输速度快

socket模块(套接字):

socket的两种类型:

(1).AF_UNIX:基于文件类型的套接字(早期socket是源于unix系统而研发的一个功能,主要为同一台电脑上多个编程直接通信),unix系统中心思想是:一切皆文件.

(2)AF_INET:基于网络的套接字

tcp协议(SOCK_STREAM):可靠的,面向连接的,面向无边界数据流形式的传输方式,占用操作系统的链接资源,传输速度慢。

udp协议(SOCK_DGRAM):不可靠的,不面向连接的,面向数据报的传输方式,但是传输速度块.

可靠不是安全,更多的是保证数据的完整性

面向连接:三次握手,四次挥手,全双工通信

无边界字节流:多条tcp数据之间是没有边界的处理粘包是在应用层

占用操作系统资源:在不使用任何异步机制进程/线程/协程在阻塞IO模型中,一个server端只能和一个clent端相连。

tcp通信:建立/断开连接保证数据完整性的机制。

tcp协议:客户端和服务端两端都可以先发送

tcp协议中服务端accept()和客户端的connect()发生三次握手 在服务端的close()和客户端的close()反生四次挥手

 

ACK;回复一个确认接收到信息的标识
  ① 键连接三次握手:三次挥手的第一次请求一定是client先发送
    a.客户端发起请求连接服务器
    b.服务器返回:接受到请求,并要求连接客户端
    c.客户端回复:可以连接
  ② 断链接四次挥手:第一次请求,谁先发送都可以
    a.客户端发起断开连接的请求(我想和你断开连接我没有数据要继续发送了,但是如果你还有数据没有发完,你就继续发就可以了)
    b.服务器回复:我接收到你的请求了
    c.服务器发送:我已经准备好断开连接了
    d.客户端回复:收到信息,断开连接
 
 

例:

服务端代码:
import socket
import time
sk = socket.socket() #实例化对象 不传参数默认使用基于网络类型套接字,tcp协议
sk.bind(("192.168.12.93",45612)) #bind()接收元组类型元组第一个元素是ip地址,第二个元素是端口 端口范围0-65535 但是0-1023这些不能用
sk.listen() #同时能接受的连接
conn,addr = sk.accept()#接受客户端的连接阻塞等待conn == <socket.socket fd=472, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('192.168.12.93', 45612), raddr=('192.168.12.93', 64343)> addr == ('192.168.12.93', 64343)
msg = conn.recv(1024) #接受数据 recv()参数时数据的大小
print(msg.decode("utf-8"))
conn.close() #关闭客户端的连接
sk.close() #关闭服务端
==>hello

客户端代码:
import socket
sk = socket.socket()           #实例化对象
sk.connect(("192.168.12.93",45612)) #连接地址
sk.send("hello".encode("utf-8"))    #发送信息
sk.close()                #关闭客户端

 udp协议:只能客户端先发送消息

udp协议的通信优势:允许一个服务器同时和多个客户端通信

例:

服务端代码:
import socket
sk = socket.socket(type=socket.SOCK_DGRAM)
sk.bind(("127.0.0.1",45612))
while 1:
msg,addr = sk.recvfrom(1024)
print(msg.decode("utf-8"),addr)
msg = input(">>>")
sk.sendto(msg.encode("utf-8"),addr)
sk.close()

用户端代码:
import socket
sk = socket.socket(type=socket.SOCK_DGRAM)
while 1:
msg = input(">>>")
sk.sendto(msg.encode("utf-8"),("127.0.0.1",45612))
msg,addr = sk.recvfrom(1024)
print(msg.decode("utf-8"),addr)
sk.close

打印出带颜色的结果语法:"\033[字体颜色;背景颜色m   数据    \033[0m"

例:

服务端代码:
import socket
sk = socket.socket(type=socket.SOCK_DGRAM)
sk.bind(("127.0.0.1",45612))
dic = {"aa":"\033[33m","bb":"\033[32m","cc":"\033[35m"}
while 1:
msg,addr = sk.recvfrom(1024)
msg = msg.decode("utf-8")
name = msg.split(":")[0].strip()
color = dic.get(name,"")
print("%s %s \033[0m" % (color,msg))
msg = input(">>>")
sk.sendto(msg.encode("utf-8"),addr)
sk.close()

用户端代码:
import socket
sk = socket.socket(type=socket.SOCK_DGRAM)
name = input("请输入你的名字:")
while 1:
msg = input(">>>")
info = name + ":" + msg
sk.sendto(info.encode("utf-8"),("127.0.0.1",45612))
msg,addr = sk.recvfrom(1024)
print("\033[32m %s,%s \033[0m"% (msg.decode("utf-8"),addr))
sk.close

解决编码问题

例:

新建一个my_udp.py文件
import socket
class My_socket(socket.socket):
def __init__(self,encoding = "utf-8"):
super(My_socket,self).__init__(type= socket.SOCK_DGRAM) #调用父类的__init__方法
self.encoding = encoding
def my_sendto(self,msg,addr):
msg = msg.encode(self.encoding)
return self.sendto(msg,addr) #调用父类的sendto方法
def my_recvfrom(self,size):
msg,addr = self.recvfrom(size) #调用父类的recvfrom方法
return msg.decode(self.encoding),addr

服务端代码:
from my_udp import My_socket
sk = My_socket()
sk.bind(("127.0.0.1",45612))
msg,addr = sk.my_recvfrom(1024)
print(msg)
# My_socket.my_sendto("123中国",("127.0.0.1",45612))
sk.close()

客服端代码:
from my_udp import My_socket
sk = My_socket()
sk.my_sendto("aa中国",("127.0.0.1",45612))
sk.close

 粘包:

subprocess模块的Popen功能:在py代码中如何去调用操作系统的命令

例:服务器端代码:

import socket
import subprocess

sk = socket.socket()
sk.bind(("127.0.0.1",8080))
sk.listen()
conn,addr = sk.accept()
while 1:
cmd = conn.recv(1024).decode("utf-8")
r = subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
stdout = r.stdout.read()
stderr = r.stderr.read()
if stderr:
conn.send(stderr)
if stdout:
conn.send(stdout)

conn.close()
sk.close()

subprocess.Popen(cmd,shell = True,subprocess.stdout,subprocess.stderr)

cmd:表示系统命令

shell = True:代表这条命令是系统命令,告诉OS操作系统,将cmd当成系统命令去执行

stdout:是执行完系统命令之后,用于保存结果的一个管道

srderr:是执行系统命令之后,用于保存错误结果的一个管道

客户端代码:

import socket

sk = socket.socket()
sk.connect_ex(("127.0.0.1",8080))
while 1:
cmd = input("请输入一个命令:")
sk.send(cmd.encode("utf-8"))
result = sk.recv(1024).decode("gbk")
print(result)

sk.close()

粘包问题就是数据混乱的问题

粘包的成因:操作系统有一个缓冲区

    发送方:两条连续发出的短消息会根据tcp协议的合包机制被合在一起发送,一条过长的数据也会根据tcp协议的拆包机制被分成多段数据发送

    接收端:所有的数据都会在接收端的缓冲区被合在一起,如果没有及时获取信息,那么信息也会黏在一起。

发送端发送数据接收端不知道应该如何去接收,造成一种数据混乱的现象.

只有tcp协议才会发生粘包,udp不会    只有连续发送数据和发送数据过大时会发生粘包.

udp不会发生粘包,udp协议本层对一次收发数据大小的限制是:65535 - ip包头(20) - udp包头(8)  = 65507

站在数据链路层,因为网卡的MTU一般被限制在31500,所以对于数据链路层来说,一次收发数据的大小被限制在1500 - ip包头(20) - udp包头(8) =1472

得到结论:

  如果sendto(num)

    num > 65507   报错

    1472 < num > 65507 会在数据链路层拆包,而udp本身就是不可靠协议,所以一旦拆包之后,造成的多个小数据包在网络传输中,如果丢任何一个,那么此次数据传输失败

    num < 1472 是比较理想状态

在tcp协议中 

 Nagle算法 (合包机制造成的粘包)

有个合包机制(Nagle算法),将多次连续发送且间隔较小的数据,进行打包成一块数据传送

send(num) 好几个都比较小的时候,且间隔时间短

如图:Nagle算法会先接收一个send(2),之后他会继续等待,看还有没有send值

直到没有send的时候,将之前接收到的所有的send在缓存区1中打一个包

一块通过面向字节流发送给缓存区2中

在缓存区2中将这个包进行拆分,传给recv,如果这个包里所有的send值加起来没有recv接收字流大的话,recv会全部接收

如果这个包里send值加起来大于recv接收字流的话,就先接收1024个,剩下的会等待下次发送,

但是第二次发送的时候,还有其他数据,那其他数据有可能与之前剩下的值一起发送过来,这样就造成了粘包现象

 拆包机制造成的粘包现象

 拆包机制:在发送端,因为受到网卡的MTU限制,会将大的超过MTU(一般最大15000)限制的数据进行拆分,拆分成多个小的数据并进行传输,

当传输到目标主机的操作系统层时,会重新将多个小的数据合并成原来的数据.   MTU处于数据链路层,涉及arp协议.

当send(num)太大的时候,在发送到缓冲区1中,会进行拆分

例如缓存区1中一次发送大小设定为1500,则在缓冲区1中会将send进行拆分为四个小船,3个1500,1个500,他们会并不会按照顺序依次到达缓存区2中,如果没有全到,先到的会先等待

直到全部到达之后,进行排序打包到缓存区2中,recv(num)假设,设定的值为1500,recv会一次接收打好包里面的内容,这个recv满了,下一个recv会接着接收,

例如有四个recv(1500),第一个包里的500会等待次下次发送的值,与下次send里面的前1000个字节进行打包,被第四个recv接收,造成粘包现象.

合包机制和拆包机制都发生在发送端

int 变量 在32位操作系统中占4个字节,32位 .在64位操作系统中占4个字节,64位.

struct模块

例:

import struct
a = 2140000000
s = struct.pack("i",a) #pack打包把(-2140000000,2140000000)范围内数字打包成一个四个字节的byte
print(s,len(s)) ==>b'\x00\xcf\x8d\x7f' 4
print(struct.unpack("i",s)) ==>(2140000000,) #unpack解包,把int型的数据解码原数据,结果是元组型原数据在元组的第一个元素

补充内容:

 unsigned 代表无符号

  有符号int 和 无符号int的区别

  有符号表示的是1个字节,8位,最高位是符号位,所以有符号的变量,一个字节表示范围:-128~127

  无符号表示的是1个字节,8位,所有位都是数值,所以无符号的便利,一个字节表示范围:0-255

  float和double

  float表示单精度,一般的操作系统中表示为,将数值准确到小数点后 7~8位

  double表示双精度,一般的操作系统中表示为,将数值准确到小数点后15~16位

  void 指的是无返回值类型,在python中没有这种类型的数据

  *    表示的是一级指针,指针的意思是:指向某一块内存地址

sendall调用send()当数据过大时.sendall尝试把所有数据一块发送过去,而send要把数据拆开发送

getsockname()获取当前套接字的地址

setblocking(False(非阻塞状态)/True(阻塞状态))设置accept和recv两个方法的阻塞与非阻塞状态

settimeout()设置等待超时时间

gettimeout()设置等待超时时间

hmac模块

例:效果相同都是对字符串加密

import hmac
s = "abcd".encode("utf-8")
md5_obj = hmac.new(s)
msg = md5_obj.digest()
print(msg) ==>b'\x85\x03\xe2\x04\xe8\x9c\x94\x87\x1f\x08\x1b[eA-\xbd'

import hashlib
s = "abcd".encode("utf-8")
md5_obj = hashlib.md5()
md5_obj.update(s)
msg = md5_obj.hexdigest()
print(msg) ==>e2fc714c4727ee9395f324cd2e7f331f

socketserver模块: 

主要解决tcp协议中服务器不能同时连接多个客户端问题,是处于socket抽象层和应用层之间的一层,比socket更贴近用户

例:

服务端代码:
import socketserver
class MySocket(socketserver.BaseRequestHandler):
def handle(self):# 这个方法的名字是固定的,必须是这个名字
# 收发的逻辑代码
# self.request == conn
msg = self.request.recv(1024).decode('utf-8')
print(msg)
self.request.send(msg.upper().encode('utf-8'))
server = socketserver.TCPServer(('127.0.0.1',8080),MySocket)# 固定的
server.serve_forever()# 开启一个永久性的服务

\r光标回到行首

例:

print("123\r45")  ==>45
print("123\r4") ==>4
print("123\r") ==>123
原文地址:https://www.cnblogs.com/gxj742/p/9457690.html