网络编程

一.网络基础的相关知识

1.架构

(1)C/S 架构:client客户端 和 server服务器端

优点:能充分发挥PC机(电脑)的性能

(2)B/S 架构:browser浏览器 和 server服务器端   (隶属于C/S架构)

优点:统一了应用的接口

2.通信

(1)同一台电脑上的两个py程序通信  (比如:文件操作)

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

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

  两个电脑通信步骤:

    (1)电脑一(源主机)要找电脑二(目标主机),先发送一个请求帧(源主机的ip地址:(192.168.12.1),mac是xxx,我要找ip地址为:(192,168,12,2)的主机),交给交换机

    (2)交换机以广播的形式群发,除了给电脑一(源主机)之外的所有机器

    (3)电脑二(目标主机)收到消息后,以单播的形式(我的ip地址是:(192.168.2),mac是yyy),请回复给ip为(192.168.1),mac为xxx的主机)回复给交换机,交换机再回复给电脑一(源主机)

3.相关知识点:

1.网卡

  mac地址(物理地址)   也就类似于人的身份证号

  mac地址全球唯一(可以唯一识别某一台电脑)

  mac地址是网卡制造商给的

2.ip地址

  ip地址是一个四位点分十进制,它标识了计算机在网络中的位置     也就类似于学生证号

3.arp协议

  通过目标ip地址获取到目标mac地址的一个协议

4.端口的概念

  操作系统OS为本机上每一个运行的程序都随机分配一个端口,其它电脑上的程序可以通过端口获取到这个程序

  ip地址+端口:能唯一找到某一台电脑上的某一个服务程序

5.交换机的通信方式

  广播的形式:吼一嗓子(类似于大喇叭)

  单播:一对一

  多播:一对多

6.路由器

  连接不同网段(ip地址范围) ,路由(里面有一个路由表:网段和网关)

7.网关

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

8.网段

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

9.子网掩码

  子网掩码 &(and) 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 
 

10.osi 五层模型

 

osi五层模型

        每层运行常见的协议

每层运行常见的物理设备

应用层

http,https,ftp

 

传输层

tcp协议,udp协议

四层交换机,四层路由器

网络层

ip协议

路由器,三层交换机

数据链路层

arp协议(与mac地址相关)

网桥,以太网交换机(二层交换机),网卡

物理层

传输电信号

网线,光纤,集线器

以太网交换机:不能跨网段传输
网桥:可以帮你实现跨网段的传输过程
ftp:上传文件,下载文件的协议
http:上网协议

二.socket模块

1.socket层

 socket:套接字

 2.socket的其中两种类型:

(1)AF_UNIX:

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

(2)AF_INET:

    基于网络类型的套接字
 

三.tcp协议和udp协议 :

  tup协议:可靠的,面向连接的,面向字节流形式的传输方式   (SOCK_STREAM)

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

1.TCP协议通信:

第一条信息谁都可以先发

tcp:只允许一个服务器对一个客户端发消息

(1) 第一次通信的例子

服务器端:

import socket
import time
sk = socket.socket()# 不传参数,默认使用基于网络类型的套接字, 协议 : TCP

sk.bind(('192.168.12.104',18080))# 端口的范围是0-65535 但是 0-1023 这些你别用
sk.listen()# 同时能接受的连接

print(123)
conn,addr = sk.accept()# 等待接受客户端的连接 阻塞等待
print(456)
print('conn:',conn)
print('addr:',type(addr))

time.sleep(20)

conn.close()
sk.close()

 客户端:

import socket
import time
sk = socket.socket()

sk.connect(('192.168.12.104',18080))# 连接

time.sleep(20)

sk.close()

(2) 第二个通信的例子

 服务器端:

import socket
import time
sk = socket.socket()# 我买一个新手机

sk.bind(('192.168.12.104',18080))# 我买一个手机卡

sk.listen()# 开机

print(123)
conn,addr = sk.accept()# 等待朋友给我打电话


msg_r = conn.recv(10)# 接受数据,接受10个字节
print(msg_r.decode('utf-8'),addr)


conn.close()# 挂断电话
sk.close() # 关机

 客户端:

import socket
import time
sk = socket.socket()

sk.connect(('192.168.12.104',18080))# 连接

sk.send('中文'.encode('utf-8'))

sk.close()

(3) tcp 的建链接三次握手 和 断链接四次挥手

  ACK;回复一个确认接收到信息的标识

  ① 键连接三次握手:第一次请求一定是client(客户端)先发送

    a.客户端发起请求连接服务器

    b.服务器返回:接受到请求,并要求连接客户端

    c.客户端回复:可以连接

  ② 断链接四次挥手:第一次请求,谁先发送都可以

    a.客户端发起断开连接的请求(我想和你断开连接我没有数据要继续发送了,但是如果你还有数据没有发完,你就继续发就可以了)

    b.服务器回复:我接收到你的请求了

    c.服务器发送:我已经准备好断开连接了

    d.客户端回复:收到信息,断开连接

2.UDP协议通信

UDP:必须由客户端先发一条消息

UDP:服务器端可以同时跟多个客户端通信(先回复一个之后,再接收另一个客户端信息)

(1)代码一

服务器端代码:

import socket
sk = socket.socket(type=socket.SOCK_DGRAM)
sk.bind(("127.0.0.1",12345))

msg_r ,addr = sk.recvfrom(1024) 接收来自哪里的消息
print(msg_r.decode("utf-8"))
sk.close()

 客户端代码:

import socket
sk = socket.socket(type=socket.SOCK_DGRAM)
msg_s = input(">>>")
sk.sendto(msg_s.encode("utf-8"),("127.0.0.1",12345)) 发给谁消息
sk.close()

(2)代码二 (循环收发消息)

服务器端代码:

import socket
sk = socket.socket(type=socket.SOCK_DGRAM)
sk.bind(("127.0.0.1",8090))
while 1:
msg_r,addr = sk.recvfrom(1024)
print(msg_r.decode("utf-8"),addr)
msg_s = input(">>>")
sk.sendto(msg_s.encode("utf-8"),addr)
sk.close()

客户端代码:

import socket
sk = socket.socket(type=socket.SOCK_DGRAM)
while 1:
msg_s = input(">>>")
sk.sendto(msg_s.encode("utf-8"),("127.0.0.1",8090))
msg_r,addr = sk.recvfrom(1024)
print(msg_r.decode("utf-8"))
sk.close()

(3)代码三(署名的UDP协议)

服务器端代码:

import socket
sk = socket.socket(type=socket.SOCK_DGRAM)
sk.bind(("127.0.01",8090))
while 1:
msg_r,addr = sk.recvfrom(1024)
print(msg_r.decode("utf-8"),addr)
msg_s = input(">>>")
sk.sendto(msg_s.encode("utf-8"),addr)
sk.close()

客户端代码:

import socket
sk = socket.socket(type=socket.SOCK_DGRAM)
name = input("请输入姓名:")
while 1:
msg_s = input(">>>")
msg_s = name + ":" + msg_s
sk.sendto(msg_s.encode("utf-8"),("127.0.01",8090))
msg_r,addr = sk.recvfrom(1024)
print(msg_r.decode("utf-8"))
sk.close()

(4)代码四(根据每个客户端的名字,加颜色)

服务器端代码:

import socket
sk = socket.socket(type=socket.SOCK_DGRAM)
sk.bind(("127.0.0.1",8090))
dic = {"alex":"33[32m","taibai":"33[34m","wusir":"33[35m"}
while 1:
msg_r,addr = sk.recvfrom(1024)
msg_r = msg_r.decode("utf-8")
name = msg_r.split(":")[0].strip()
color = dic.get(name,"")
print("%s %s 33[0m" % (color,msg_r))
msg_s = input(">>>")
sk.sendto(msg_s.encode("utf-8"),addr)
sk.close()

客户端代码:

import socket
sk = socket.socket(type=socket.SOCK_DGRAM)
name = input("请输入姓名:")
while 1:
msg_s = input(">>>")
msg_s = name + ":" + msg_s
sk.sendto(msg_s.encode("utf-8"),("127.0.01",8090))
msg_r,addr = sk.recvfrom(1024)
print(msg_r.decode("utf-8"))
sk.close()

pycharm输出带颜色:print("33[32;4m sbc 33[0m")     33[ 是起始 32 为字体颜色,4为背景颜色 33[0m是结尾

(5)代码五(解决编码解码问题)

模块代码:

import socket 
class Mysocket(socket.socket): ======>继承自socket文件中socket类
def __init__(self,encoding = "utf-8"):
self.encoding = encoding
super().__init__(type=socket.SOCK_DGRAM) =====>执行父类中的__init__的方法
def my_recvfrom(self,num):
msg_r , addr = self.recvfrom(num)
return msg_r.decode(self.encoding),addr =====> 调用了父类中的recvfrom方法
def my_sendto(self,msg,addr):
return self.sendto(msg.encode(self.encoding),addr) ====>调用了父类中sendto方法

服务器端代码:

from MY_DUP import Mysocket

sk = Mysocket()
sk.bind(("127.0.0.1",8090))
dic = {"alex":"33[32m","wusir":"33[33m","taibai":"33[34m"}
while 1:
msg,addr = sk.my_recvfrom(1024)
name = msg.split(":")[0].strip()
color = dic.get(name,"")
print("%s %s 33[0m" % (color,msg))
# if msg.upper() == "Q":
# break
msg_s = input(">>>")
sk.my_sendto(msg_s,addr)
if msg_s.upper() == "Q":
break
sk.close()

 客户端代码:

from MY_DUP import Mysocket

sk = Mysocket()
name = input("请输入名字:")
while 1:
msg_s = input(">>>")
if msg_s.upper() == "Q":
break
msg_s = name + ":" + msg_s
sk.my_sendto(msg_s,("127.0.0.1",8090))
msg_r,addr = sk.my_recvfrom(1024)
print(msg_r)
if msg_r.upper() == "Q":
break

sk.close()

四.粘包

1.subprocess模块

执行命令:在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()

2.粘包问题

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

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

只有tcp协议才会发生粘包,udp不会

(1)针对于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 是比较理想状态

(2)在tcp协议中 

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

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

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

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

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

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

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

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

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

 ②拆包机制造成的粘包现象

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

当传输到目标主机的操作系统层时,会重新将多个小的数据合并成原来的数据

当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接收

原文地址:https://www.cnblogs.com/lhy979/p/9457504.html