网络编程

百般思念 提交于 2019-11-26 17:19:57

网络编程

 

软件开发架构

c/s架构(client/server)

​ c:客户端 和 s:服务端

​ 例如: QQ,微信,网盘,这一类都属于c/s架构,我们都需要下载一个客户端才能够运行

​ ps:这里的客户端一般泛指客户端应用程序EXE,程序需要先安装后,才能运行在用户的电脑上,对用户的电脑操作系统环境依赖较大。

 

b/s架构(browser/server)

​ b:浏览器 和 s:服务器

​ 例如:百度,淘宝网页,博客园这类都属于b/s架构,我们可以直接通过浏览器访问直接使用的应用

​ ps:Browser浏览器,其实也是一种Client客户端,只是这个客户端不需要大家去安装什么应用程序,只需在浏览器上通过HTTP请求服务器端相关的资源(网页资源),客户端Browser浏览器就能进行增删改查。

用大白话来总结客户端和服务端的作用基本上可以理解为:
服务端:24小时不间断提供服务
客户端:什么时候想体验服务就去服务端寻求服务
b/s架构的本质其实也是c/s架构,两者都是用于两个程序之间通讯的开发

网络基础

网络编程技术起源

绝大部分先进技术的兴起基本都来自于军事,网络编程这项技术就是来源于美国军事,为了实现数据的远程传输

 

人类实现远程沟通交流的方式

- 插电话线的电话
- 插网线的大屁股电脑
- 插无线网卡的笔记本电脑

综上我们能够总结出第一个规律:要想实现远程通信第一个需要具备的条件就是:**物理连接介质**

再来想人与人之间交流,中国人说中文,外国人说外语,那如果想实现不同国家的人之间无障碍沟通交流是不是得规定一个大家都能听得懂的语言>>>英语

再回来看计算机领域,计算机之间要想实现远程通信除了需要有物理连接介质之外是不是也应该有一套公共的标准?这套标准就是>>>OSI七层协议(也叫OSI七层模型)

 

OSI七层协议(模型)

- 应用层
- 表示层
- 会话层
- 传输层
- 网络层
- 数据链路层
- 物理连接层

也有人将其归纳为五层

- 应用层
- 传输层
- 网络层
- 数据链路层
- 物理连接层

 

 

接下来我们就需要详细的看看每一层都有哪些需要我们了解掌握的知识点

物理连接层

实现计算机之间物理连接,传输的数据都是01010的二进制 电信号工作原理:电只有高低电平

数据链路层("以太网协议")

1.规定了二进制数据的分组方式 2.规定了只要是接入互联网的计算机,都必须有一块网卡! ps:网卡上面刻有世界唯一的编号: 每块网卡出厂时都被烧制上一个世界唯一的mac地址, 长度为48位2进制,通常由12位16进制数表示(前六位是厂商编号,后六位是流水线号) 我们管网卡上刻有的编号叫电脑的>>>mac地址 ----->上面的两个规定其实就是 "以太网协议"!

 

**基于以太网协议通信:**通信基本靠吼!!!弊端:广播风暴

**交换机:**如果没有交换机,你的电脑就变成了马蜂窝,有了交换机之后,所有的电脑只需要有一个网卡连接交换机,即可实现多台电脑之间物理连接

 

网络层(IP协议)

规定了计算机都必须有一个ip地址 ip地址特点:点分十进制 有两个版本ipv4和ipv6 为了能够兼容更多的计算机 最小:0.0.0.0 最大:255.255.255.255 IP协议可以跨局域网传输

ip地址能够唯一标识互联网中独一无二的一台机器!

**例如百度的ip地址:**[http://14.215.177.39](http://14.215.177.39/)/

 

传输层(端口协议)

TCP,UDP基于端口工作的协议! 其实计算机之间通信其实是计算机上面的应用程序于应用之间的通信 端口(port):唯一标识一台计算机上某一个基于网络通信的应用程序 端口范围:0~65535(动态分配) 注意:0~1024通常是归操作系统分配的端口号 通常情况下,我们写的软件端口号建议起在8000之后 flask框架默认端口5000 django框架默认端口8000 mysql数据库默认端口3306 redis数据库默认端口6379**注意:**一台计算机上同一时间一个端口号只能被一个应用程序占用

 

**小总结:** IP地址:唯一标识全世界接入互联网的独一无二的机器 port端口号:唯一标识一台计算机上的某一个应用程序 ip+port :能够唯一标识全世界上独一无二的一台计算机上的某一个应用程序

**补充:** arp协议:根据ip地址解析mac地址

 

TCP协议(流式协议,可靠协议)

**三次握手四次挥手**

- 三次握手建连接
- 四次挥手断连接

 

星轨:

表示一个一线明星出轨所带来的流量,据说微博的服务器现在能同时扛8星轨。

也就是说:8个一线明星同一时间爆出出轨的新闻,微博都能扛得住!

 

应用层(HTTP协议,FTP协议)

套接字scoket概念

scoket层

 

理解socket

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

基于TCP协议的socket

socket基础通信

import socket


server = socket.socket()  # 买手机 不传参数默认用的就是TCP协议
server.bind(('127.0.0.1',8080))  # bind((host,port))  插电话卡  绑定ip和端口
server.listen(5)  # 开机    半连接池


conn, addr = server.accept()  # 接听电话  等着别人给你打电话     阻塞
data = conn.recv(1024)  # 听别人说话 接收1024个字节数据          阻塞
print(data)
conn.send(b'hello baby~')  # 给别人回话



conn.close()  # 挂电话
server.close()  # 关机
服务端
import socket


client = socket.socket()  # 拿电话
client.connect(('127.0.0.1',8080))  # 拨号   写的是对方的ip和port

client.send(b'hello world!')  # 对别人说话
data = client.recv(1024)  # 听别人说话
print(data)



client.close()  # 挂电话
客户端

ps:mac本在重启客户端时可能会遇到这样的问题:

#加入一条socket配置,重用ip和端口
import socket
from socket import SOL_SOCKET,SO_REUSEADDR
sk = socket.socket()
sk.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) #就是它,在bind前加
sk.bind(('127.0.0.1',8898))  #把地址绑定到套接字
sk.listen()          #监听链接
conn,addr = sk.accept() #接受客户端链接
ret = conn.recv(1024)   #接收客户端信息
print(ret)              #打印客户端信息
conn.send(b'hi')        #向客户端发送信息
conn.close()       #关闭客户端套接字
sk.close()        #关闭服务器套接字(可选)

TCP粘包问题

import socket


server = socket.socket()  # 买手机 不传参数默认用的就是TCP协议
server.bind(('127.0.0.1',8080))  # bind((host,port))  插电话卡  绑定ip和端口
server.listen(5)  # 开机    半连接池


conn, addr = server.accept()  # 接听电话  等着别人给你打电话     阻塞
data = conn.recv(1024)  # 听别人说话 接收1024个字节数据          阻塞
print(data)
data = conn.recv(1024)  # 听别人说话 接收1024个字节数据          阻塞
print(data)
data = conn.recv(1024)  # 听别人说话 接收1024个字节数据          阻塞
print(data)
服务端
import socket


client = socket.socket()  # 拿电话
client.connect(('127.0.0.1',8080))  # 拨号   写的是对方的ip和port

client.send(b'hello')
client.send(b'world')
client.send(b'baby')
客户端

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

解决TCP粘包问题

import socket
import subprocess
import struct
import json

server = socket.socket()
server.bind(('127.0.0.1',8080))
server.listen(5)


while True:
    conn, addr = server.accept()
    while True:
        try:
            cmd = conn.recv(1024)
            if len(cmd) == 0:break
            cmd = cmd.decode('utf-8')
            obj = subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
            res = obj.stdout.read() + obj.stderr.read()

            d = {'name':'jason','file_size':len(res),'info':'asdhjkshasdad'}
            json_d = json.dumps(d)
            # 1.先制作一个字典的报头
            header = struct.pack('i',len(json_d))
            # 2.发送字典报头
            conn.send(header)
            # 3.发送字典
            conn.send(json_d.encode('utf-8'))
            # 4.再发真实数据
            conn.send(res)
            # conn.send(obj.stdout.read())
            # conn.send(obj.stderr.read())
        except ConnectionResetError:
            break
    conn.close()
服务端
import socket
import struct
import json


client = socket.socket()
client.connect(('127.0.0.1',8080))

while True:
    msg = input('>>>:').encode('utf-8')
    if len(msg) == 0:continue
    client.send(msg)
    # 1.先接受字典报头
    header_dict = client.recv(4)
    # 2.解析报头 获取字典的长度
    dict_size = struct.unpack('i',header_dict)[0]  # 解包的时候一定要加上索引0
    # 3.接收字典数据
    dict_bytes = client.recv(dict_size)
    dict_json = json.loads(dict_bytes.decode('utf-8'))
    # 4.从字典中获取信息
    print(dict_json)
    recv_size = 0
    real_data = b''
    while recv_size < dict_json.get('file_size'):  # real_size = 102400
        data = client.recv(1024)
        real_data += data
        recv_size += len(data)
    print(real_data.decode('gbk'))


"""
1.如何将对方发送的数据收干净


"""
客户端

在解决粘包问题的时候 我们用到了struct模块来解决接收端不知道发送端将要传送的字节流的长度的问题

struct模块

该模块可以把一个类型,如数字,转成固定长度的bytes

>>> struct.pack('i',1111111111111)

struct.error: 'i' format requires -2147483648 <= number <= 2147483647 #这个是范围

import json,struct
#假设通过客户端上传1T:1073741824000的文件a.txt

#为避免粘包,必须自定制报头
header={'file_size':1073741824000,'file_name':'/a/b/c/d/e/a.txt','md5':'8f6fbf8347faa4924a76856701edb0f3'} #1T数据,文件路径和md5值

#为了该报头能传送,需要序列化并且转为bytes
head_bytes=bytes(json.dumps(header),encoding='utf-8') #序列化并转成bytes,用于传输

#为了让客户端知道报头的长度,用struck将报头长度这个数字转成固定长度:4个字节
head_len_bytes=struct.pack('i',len(head_bytes)) #这4个字节里只包含了一个数字,该数字是报头的长度

#客户端开始发送
conn.send(head_len_bytes) #先发报头的长度,4个bytes
conn.send(head_bytes) #再发报头的字节格式
conn.sendall(文件内容) #然后发真实内容的字节格式

#服务端开始接收
head_len_bytes=s.recv(4) #先收报头4个bytes,得到报头长度的字节格式
x=struct.unpack('i',head_len_bytes)[0] #提取报头的长度

head_bytes=s.recv(x) #按照报头长度x,收取报头的bytes格式
header=json.loads(json.dumps(header)) #提取报头

#最后根据报头的内容提取真实的数据,比如
real_data_len=s.recv(header['file_size'])
s.recv(real_data_len)
#_*_coding:utf-8_*_
#http://www.cnblogs.com/coser/archive/2011/12/17/2291160.html
__author__ = 'Linhaifeng'
import struct
import binascii
import ctypes

values1 = (1, 'abc'.encode('utf-8'), 2.7)
values2 = ('defg'.encode('utf-8'),101)
s1 = struct.Struct('I3sf')
s2 = struct.Struct('4sI')

print(s1.size,s2.size)
prebuffer=ctypes.create_string_buffer(s1.size+s2.size)
print('Before : ',binascii.hexlify(prebuffer))
# t=binascii.hexlify('asdfaf'.encode('utf-8'))
# print(t)


s1.pack_into(prebuffer,0,*values1)
s2.pack_into(prebuffer,s1.size,*values2)

print('After pack',binascii.hexlify(prebuffer))
print(s1.unpack_from(prebuffer,0))
print(s2.unpack_from(prebuffer,s1.size))

s3=struct.Struct('ii')
s3.pack_into(prebuffer,0,123,123)
print('After pack',binascii.hexlify(prebuffer))
print(s3.unpack_from(prebuffer,0))

 关于struct的详细用法
关于struct的详细用法

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!