Day 36 网络编程总结

一. 软件开发的架构

c/s架构 client-server 用户层面的架构,程序需要先安装后,才能运行在用户的电脑上,对用户的电脑操作系统环境依赖较大,现在不流行了

   几乎包括了所有网络开发的架构形态

b/s架构 browser - server 也是c/s架构,大势所趋

   其实也是一种client客户端,不需要安装应用程序,可以在网页上操作,比较方便,统一了所有应用的入口

二. 网络基础

ip地址和ip协议:

规定网络地址的协议就叫做ip协议,32位2进制表示,范围0.0.0.0-255.255.255.255

IP协议的作用主要有两个,一个是为每一台计算机分配IP地址,另一个是确定哪些地址在同一个子网络。

mac地址:网卡

以太网规定接入internet的设备都必须具备网卡,发送端和接收端的地址便是指网卡的地址,即mac地址,是唯一的,通常由12位16进制数表示

# mac地址唯一的,为什么要有ip地址?
# 192.168.11.***
# 256台 0-255
# 192.168.***.***
# 256^2
# 192.***.***.***
# 256^3

arp协议:查询ip地址和mac地址的对应关系

交换机:同一个局域网内的机器之间的交流

            局域网与交换机

              广域网与路由器

路由器:跨局域网机器之间的交流。 是连接因特网中各局域网,广域网的设备,又称网关设备是用于连接多个逻辑上分开的网络,网络层的互联设备。

局域网:是指在某一局域内由多台计算机互联成的计算机组。一般是方圆几千米以内。局域网是封闭性的,可以由办公室内的两台计算器组成,也可以由公司内的上千台计算器组成。

子网掩码:就是表示子网络特征的一个参数。形式上等同于ip地址,也是一个32位二进制数字,网络部分全部为1,主机部分全部为0.

      比如,IP地址是172.16.10.1,如果已知网络部分是前24位,主机部分是后8位,那么子网掩码就是11111111,11111111,11111111,00000000,

      写成十进制就是255.255.255.0

      知道”子网掩码”,我们就能判断,任意两个IP地址是否处在同一个子网络。方法是将两个IP地址与子网掩码分别进行AND运算

      (两个数位都为1,运算结果为1,否则为0),然后比较结果是否相同,如果是的话,就表明它们在同一个子网络中,否则就不是。 

192.168.12.62
11000000.10101000.00001011.00111110
11111111.11111111.11111111.00000000
11000000.10101000.00001011.00000000   == 192.168.0.0
255.255.0.0

192.168.11.94
255.255.0.0
11000000.10101000.00001011.01011110
11111111.11111111.11111111.00000000  == 192.168.0.0

网管ip:跨局域网的机器之间不能直接通信,只能通过网管ip通信

TCP协议和UDP协议

端口:

通过ip地址+端口来区分不同的服务的

TCP协议:三次握手,四次挥手

当应用程序希望通过 TCP 与另一个应用程序通信时,它会发送一个通信请求。这个请求必须被送到一个确切的地址。在双方“握手”之后,TCP 将在两个应用程序之间建立一个全双工 (full-duplex) 的通信。

  这个全双工的通信将占用两个计算机之间的通信线路,直到它被一方或双方关闭为止。

UDP协议:

当应用程序希望通过UDP与一个应用程序通信时,传输数据之前源端和终端不建立连接。

  当它想传送时就简单地去抓取来自应用程序的数据,并尽可能快地把它扔到网络上。

tcp和udp的对比:

tcp-传输控制协议

相当于打电话:独占一个通道,在断开连接钱不能建立另一个连接,即两人在通话过程中第三方不能打入电话

是面向连接的,可靠的字节流服务,

当客户与服务器彼此交换数据前,必须先在双方之间建立一个tcp连接,之后才能传输数据。

tcp提供超时重发,丢弃重复数据,检验数据,流量控制的功能,保证数据已定从一端传到另一端

这种优化机制,容易出现黏包现象

udp-用户数据包协议 相当于写信

是一个简单的面相数据包的运输层协议

udp不提供可靠性,由于udp在传输数据包前不用在客户和服务器之间建立一个连接,且没有超时重发等机制,所以传输速度很快,所以不会出现黏包现象

互联网协议与osi模型

每层运行常见物理设备

每层运行常见的协议

socket概念-网络编程和python相关

Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。

基于tcp协议的socket

tcp是基于连接的,必须先启动服务器,然后再启动客户端去连接服务器

server端

import socket
from socket import SOL_SOCKET,SO_REUSEADDR #加入一条socket配置,重用ip和端口
sk= socket.socket()
sk.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) #在bind前加
sk.bind(('127.0.0.1',8000)) #把地址绑定到套接字
sk.listen() #监听链接

conn,addr = sk.accept() #接收客户端链接
ret = conn.recv(1024) # 接收客户端信息
print(ret) #打印客户端信息
conn.send(b'hi') #向客户端发送信息
conn.close() #关闭客户端套接字
sk.close() #关闭服务器套接字(可选)

client端

import socket
sk = socket.socket() #创建客户套接字
sk.connect(('127.0.0.1',8000)) #尝试连接服务器
sk.send(b'hello')
ret = sk.recv(1024) #对话(发送/接收)
print(ret)
sk.close() #关闭客户套接字

udp是无连接的,启动服务之后可以直接接受消息,不需要提前建立连接

server端

import socket
udp_sk = socket.socket(type=socket.SOCK_DGRAM) # 创建一个服务器的套接字
udp_sk.bind(('127.0.0.1',9000)) # 绑定服务器套接字
msg,addr = udp_sk.recvfrom(1024)
print(msg)
udp_sk.sendto(b'hi',addr) # 对话(接收与发送)
udp_sk.close() # 关闭服务器套接字

client端

import socket
ip_port = ('127.0.0.1',9000)
udp_sk = socket.socket(type=socket.SOCK_DGRAM)
udp_sk.sendto(b'hello',ip_port)
back_msg,addr = udp_sk.recvfrom(1024)
print(back_msg.decode('utf-8'),addr)

qq聊天

server端

import socket
ip_port = ('127.0.0.1',8888)
udp_server_socket = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
udp_server_socket.bind(ip_port)

while True:
    qq_msg,addr = udp_server_socket.recvfrom(1024)
    print('来自[%s:%s]的一条消息:33[1;44m%s33[0m'%(addr[0],addr[1],qq_msg.decode('utf-8')))
    reply_msg = input('请回复:').strip().encode('utf-8')

    udp_server_socket.sendto(reply_msg,addr)

client端

import socket

BUFSIZE = 1024

udp_client_socket = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)

qq_name_dic = {'taibai':('127.0.0.1',8888),
               'nero':('127.0.0.1',8888),
               'egg':('127.0.0.1',8888),
               'yuan':('127.0.0.1',8888)}

while True:
    qq_name = input('请输入聊天对象:').strip()
    while True:
        msg = input('请输入消息:q退出')
        if msg =='q':break
        if not msg or not qq_name or qq_name not in qq_name_dic:continue
        udp_client_socket.sendto(msg.encode('utf-8'),qq_name_dic[qq_name])

        reply_msg,addr = udp_client_socket.recvfrom(BUFSIZE)

        print('来自[%s:%s]的一条消息:33[1;44m%s33[0m' %(addr[0],addr[1],reply_msg.decode('utf-8')))

udp_client_socket.close()

黏包

res=subprocess.Popen(cmd.decode('utf-8'),
shell=True,
stderr=subprocess.PIPE,
stdout=subprocess.PIPE)

的结果的编码是以当前所在的系统为准的,如果是windows,那么res.stdout.read()读出的就是GBK编码的,在接收端需要用GBK解码

且只能从管道里读一次结果

同时执行多条命令之后,得到的结果很可能只有一部分,在执行其他命令的时候又接收到之前执行的另外一部分结果,这种显现就是黏包。

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

tcp协议的拆包机制

当发送端缓冲区的长度大于网卡的MTU时,tcp会将这次发送的数据拆成几个数据包发送出去。 
MTU是Maximum Transmission Unit的缩写。意思是网络上传送的最大数据包。MTU的单位是字节。 大部分网络设备的MTU都是1500。如果本机的MTU比网关的MTU大,大的数据包就会被拆开来传送,这样会产生很多数据包碎片,增加丢包率,降低网络速度。

即面向流的通信是无消息保护边界的。
可靠黏包的tcp协议:tcp的协议数据不会丢,没有收完包,下次接收,会继续上次继续接收,己端总是在收到ack时才会清除缓冲区内容。数据是可靠的,但是会粘包。

发送方引起的粘包是由TCP协议本身造成的,TCP为提高传输效率,发送方往往要收集到足够多的数据后才发送一个TCP段。若连续几次需要send的数据都很少,通常TCP会根据优化算法把这些数据合成一个TCP段后一次发送出去,这样接收方就收到了粘包数据。

UDP不会发生黏包:

udp不会使用块的合并优化算法,面对消息的童心是有消息保护边界的。

发生黏包的两种情况:

情况一:发送方的缓存机制

发送端需要等缓冲区满才发送出去,造成黏包(发送时间间隔很短,数据很小,会何在一起,产生黏包

服务端

from socket import *
ip_port=('127.0.0.1',8080)

tcp_socket_server=socket(AF_INET,SOCK_STREAM)
tcp_socket_server.bind(ip_port)
tcp_socket_server.listen(5)


conn,addr=tcp_socket_server.accept()


data1=conn.recv(10)
data2=conn.recv(10)

print('----->',data1.decode('utf-8'))
print('----->',data2.decode('utf-8'))

conn.close()

客户端

import socket
BUFSIZE=1024
ip_port=('127.0.0.1',8080)

s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
res=s.connect_ex(ip_port)


s.send('hello'.encode('utf-8'))
s.send('egg'.encode('utf-8'))

结果

-----> helloegg
----->

情况二:接收方的缓存机制 

接收方不及时接收缓冲区的包,造成多个包接收(客户端发送了一段数据,服务端只收了一小部分,服务端下次再收的时候还是从缓冲区拿上次遗留的数据,产生粘包) 

服务端

from socket import *
ip_port=('127.0.0.1',8080)

tcp_socket_server=socket(AF_INET,SOCK_STREAM)
tcp_socket_server.bind(ip_port)
tcp_socket_server.listen(5)


conn,addr=tcp_socket_server.accept()


data1=conn.recv(2) #一次没有收完整
data2=conn.recv(10)#下次收的时候,会先取旧的数据,然后取新的

print('----->',data1.decode('utf-8'))
print('----->',data2.decode('utf-8'))

conn.close()

客户端

import socket
BUFSIZE=1024
ip_port=('127.0.0.1',8080)

s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
res=s.connect_ex(ip_port)


s.send('hello egg'.encode('utf-8'))

结果:

-----> he
-----> llo egg

黏包现象只发生在tcp协议中:

1.从表面上看,黏包问题主要是因为发送方和接收方的缓存机制、tcp协议面向流通信的特点。

2.实际上,主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的

黏包的解决方案

解决方案进阶 struct模块

server端

import os,json,struct,socket

filepath = r'/Users/lixinwei/PycharmProjects/untitled1/xxglxt'
filename = os.path.basename(filepath)
filesize = os.path.getsize(filepath)

sk=socket.socket()
sk.bind(('127.0.0.1',8300))
sk.listen()

conn,addr = sk.accept()
print('客户端',addr)
dic = {'filename':filename,
       'filesize':filesize}

str_dic = json.dumps(dic).encode('utf-8')
dic_len = len(str_dic)
length = struct.pack('i',dic_len)

conn.send(length)
conn.send(str_dic)

with open(filename,'rb') as f:
       while filesize:
              content = f.read(4096)
              conn.send(content)
              filesize -= len(content)

conn.close()
sk.close()

client端

import json,socket,struct

sk = socket.socket()
sk.connect(('127.0.0.1',8300))

dic_len = sk.recv(4)
dic_len = struct.unpack('i',dic_len)[0]  # 字典真正的长度50
dic = sk.recv(dic_len)
str_dic = dic.decode('utf-8')
dic = json.loads(str_dic)

with open(dic['filename'],'wb') as f:
    while dic['filesize']:
        content = sk.recv(4096)
        dic['filesize'] -= len(content)
        f.write(content)

sk.close()

七.验证客户端链接的合法性

原文地址:https://www.cnblogs.com/kateli/p/9011078.html