python 网络编程

一、软件开发架构

  c/s架构(client/server)

    c:客户端

    s:服务端

  b/s架构(browser/server)

    b:浏览器

    s:服务器

  ps:bs架构本质也是cs架构

  bs架构未来有很大潜力

二、网络编程发展史 :任何先进的技术最早都来源于军事

  想实现远程通信第一个需要具备的条件是:

    第一,物理连接介质

    第二,需要有一套公共的标准、协议

  1、OSI协议

    五层:(一般也称七层)

      应用层(应用层,表示层,会话层)

      传输层

      网络层

      数据链路层

      物理连接层

    

    1.物理连接层:基于电信号传输010101010二进制数据

    2.数据链路层(以下两点合成为以太网协议

      1.规定的电信号的分组方式

      2.规定了任何一台接入互联网的计算机都必须有一块网卡(每块网卡都刻有世界上独一无二的编号)

         编号为12位16进制数:前6位是厂商编号,后6位是流水线编号(管这12数叫MAC地址

    基于以太网协议通信

      通信基本靠吼

        广播、单播、广播风暴

    交换机:让连接交换机的计算机实现彼此之间的互联

    局域网:构成互联网的基本单位

        ps:以太网协议不能跨局域网传输

    路由器:连接不同局域网

    网关:网间连接器、协议转换器

    3.网络层

      IP协议

      规定了只要是接入互联网的计算机都必须有一个IP地址

      ip地址特点:点分十进制    且动态分配

      ip地址最小:0.0.0.0   最大:255.255.255.255

      版本:IPV4和IPV6(由于IPV4已经不够表示目前存在的计算机了,所以推出了IPV6)     

    4.传输层

      TCP/UDP都是基于端口工作的协议:

      端口(port):用来唯一标识一台计算机上的某个应用程序

        计算机与计算机之间其实是计算机上的应用程序与应用程序之间的通信

      端口号的范围:0~65535(端口号也是动态分配,关闭再次启动厚,端口号可能变)

        注意:0~1024这些都是操作系统默认使用的端口

        建议:使用8000之后的端口

        Mysql默认端口:3306;Redis默认端口:6379;django默认端口:8000;flask默认端口:5000

    so:ip+port是唯一标识接入互联网一台计算机上的某个应用程序

   5.应用层

    HTTP协议

    FTP协议 

    

    ps:TCP协议(类似打电话):流式协议,可靠协议,三次握手,四次挥手

      UDP协议(类似发短信):数据报协议,无需建立双向通道,数据传输是不安全,将内存中的数据直接发送出去,不会做保留

三、Socket(套接字)

TCP:

  发送 send ;接收recv

1.解决地址绑定套接字问题:

  from socket import SOL_SOCKET,SO_REUSEADDR         

  sk.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)   #就是它,在bind前加

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()  # 关机
socket通信服务端

ps :127.0.0.1 本机回还地址

  只能自己识别自己 其他人无法访问

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()  # 挂电话
socket通信客户端

注意点:

  send和recv对应,不要出现两边都相同的情况

    recv是跟内存要数据,至于数据的来源无需考虑

  TCP特点:会将数据量比较小的并且时间间隔比较短的数据一次性打包发送给对方

UCP:(type=socket.SOCK_DGRAM)

  发送sendto;接收recvfrom;无listen()

import socket


server = socket.socket(type=socket.SOCK_DGRAM)  # UDP协议
server.bind(('127.0.0.1',8080))
# UDP不需要设置半连接池 它也没有半连接池的概念

# 因为没有双向通道  不需要accept  直接就是通信循环
while True:
    data, addr = server.recvfrom(1024)
    print('数据:',data)  # 客户端发来的消息
    print('地址:',addr)  # 客户端的地址
    server.sendto(data.upper(),addr)
udp服务端
import socket


client = socket.socket(type=socket.SOCK_DGRAM)
# 不需要建立连接  直接进入通信循环
server_address = ('127.0.0.1',8080)
while True:
    client.sendto(b'hello',server_address)
    data, addr = client.recvfrom(1024)
    print('服务端发来的数据',data)
    print('服务端的地址',addr)
udp客户端

  特点:数据报协议(自带报头)

     没有双向通道,通信类似于发短信

  1.udp协议客户端允许发空

  2.udp协议不会粘包

  3.upd协议服务端不存在的情况下,客户端照样不会报错

  4.udp协议支持并发

import socket


server = socket.socket(type=socket.SOCK_DGRAM)
server.bind(('127.0.0.1',8080))

while True:
    data, addr = server.recvfrom(1024)
    print(data.decode('utf-8'))
    msg = input('>>>:')
    server.sendto(msg.encode('utf-8'),addr)
简易版QQ服务端
import socket


client = socket.socket(type=socket.SOCK_DGRAM)
server_address = ('127.0.0.1',8080)

while True:
    msg = input('>>>:')
    msg = '来自客户端1的消息:%s'%msg
    client.sendto(msg.encode('utf-8'),server_address)
    data, server_addr = client.recvfrom(1024)
    print(data.decode('utf-8'))
客户端1
import socket


client = socket.socket(type=socket.SOCK_DGRAM)
server_address = ('127.0.0.1',8080)

while True:
    msg = input('>>>:')
    msg = '来自客户端2的消息:%s'%msg
    client.sendto(msg.encode('utf-8'),server_address)
    data, server_addr = client.recvfrom(1024)
    print(data.decode('utf-8'))
客户端2

四、解决黏包问题:(tcp才会有)  

  先了解下struct模块

import struct


# res = 'akdjsladlkjafkldjfgsdafhjksdfhfdgfdsgdfgssgdglkjdfsfdsadkfjlksdjf;klsdkl;fk;lsfdgklj;sfkldgj;kldfg;lfkd;lgk;lsdkfg;lkfd;glskljdklsajkldsa'
# print('最原始的',len(res))

# 当原始数据特别大的时候 i模式打包不了 需要更换模式?
# 如果遇到数据量特别大的情况 该如何解决?
d = {
    'name':'jason',
    'file_size':3455435435345354524534532456546546565466564366463654543453454353455,
    'info':'为大家的骄傲'
}
import json
json_d = json.dumps(d)
print(len(json_d))


res1 = struct.pack('i',len(json_d))
print(len(res1))

res2 = struct.unpack('i',res1)[0]
print('解包之后的',res2)
struct

  服务端

  1.先制作一个发送给客户端的字典

  2.制作字典的报头

  3.发送字典的报头

  4.发送字典

  5.再发真实数据

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()
服务端

  客户端

  1.先接受字典的报头

  2.解析拿到字典的数据长度

  3.接受字典

  4.从字典中获取真实数据的长度

  5.接受真实数据

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'))
客户端

五、socketserver模块(可实现并发)

tcp:

import socketserver


class MyServer(socketserver.BaseRequestHandler):
    def handle(self):
        # print('来啦 老弟')
        while True:
            data = self.request.recv(1024)
            print(self.client_address)  # 客户端地址
            print(data.decode('utf-8'))
            self.request.send(data.upper())


if __name__ == '__main__':
    """只要有客户端连接  会自动交给自定义类中的handle方法去处理"""
    server = socketserver.ThreadingTCPServer(('127.0.0.1',8080),MyServer)  # 创建一个基于TCP的对象
    server.serve_forever()  # 启动该服务对象
tcp服务端
import socket

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

while True:
    client.send(b'hello')
    data = client.recv(1024)
    print(data.decode('utf-8'))
tcp客户端

udp:

import socketserver


class MyServer(socketserver.BaseRequestHandler):
    def handle(self):
        # print('来啦 老弟')
        while True:
            data,sock = self.request
            print(self.client_address)  # 客户端地址
            print(data.decode('utf-8'))
            sock.sendto(data.upper(),self.client_address)


if __name__ == '__main__':
    """只要有客户端连接  会自动交给自定义类中的handle方法去处理"""
    server = socketserver.ThreadingUDPServer(('127.0.0.1',8080),MyServer)  # 创建一个基于TCP的对象
    server.serve_forever()  # 启动该服务对象
udp服务端
import socket
import time

client = socket.socket(type=socket.SOCK_DGRAM)
server_address = ('127.0.0.1',8080)

while True:
    client.sendto(b'hello',server_address)
    data,addr = client.recvfrom(1024)
    print(data.decode('utf-8'),addr)
    time.sleep(1) # 添加时间,否则udp响应太快,看不到效果
udp客户端
原文地址:https://www.cnblogs.com/xiaowangba9494/p/11311527.html