python网络编程

1、 互联网协议

  功能:定义计算机如何接入Internet,以及接入Internet的计算机通信的标准。

  OSI七层协议如图:

  物理层

    主要时基于电器特性发送高低电压(电信号),高电压对应数字1,低电压对应数字0

  数据链路层

    定义了电信号的分组方式, 使用统一的标准以太网协议

     以太网协议(ethernet):

      ehernet规定:

        1. 一组电信号构成一个数据包,叫做帧

        2. 每一组数据帧分成:报头head和data两部分

    源地址和目标地址:指的是网卡的地址,即mac地址,ethernet规定接入internet的设备都必须具备网卡

     通信方式:广播

  网络层

    功能:引入一套新的地址用来区分不同的广播域/子网,这套地址即网络地址

    IP协议:ipv4范围0.0.0.0~255.255.255.255

    ip地址分成两部分:

      网络部分:标识子网

      主机部分:标识主机

      注:单纯的ip地址段只是标识ip地址的种类,无法辨识ip所处的子网

    子网掩码:

      表示子网络特征的一个参数,它的网络部分全是1,主机部分全是0

    如何判断两个IP在同一子网中:

      方法是将两个IP地址与子网掩码分别进行AND运算,如果结果相同,则表示它们再统一子网络中。

    IP数据包分为head和打他部分,直接放入以太网包的data部分

    ARP协议

      功能:广播的方式发送数据包,获取莫表主机的mac地址

      工作方式:已知每台主机的IP地址

        1. 通过IP地址和子网掩码分出是否处于相同的子网

        2. 如果不再同一子网,发数据到网关(里面的目标地址是网关的ip),通过arp获取的是网关的mac地址,

         然后发数据发给网关(里面的目标地址是目标ip)

        3. 如果在同一子网中,所有主机接收后会拆开包,发现目标ip是自己的,就响应,返回自己的mac地址

        (交换机内有个mac地址表,先查表,有就直接发过去,没有就群发)

  传输层

     功能:建立端口到端口的通信,端口的范围0-65535,0-1023为系统占用端口

    tcp协议:

      可靠传输;TCP数据包没有长度限制;理论上可以无限长,但是为了保证网络的效率,

      通常TCP数据包的长度不会超过IP数据包的长度,确保单个TCP数据包不会再被分割

    udp协议:

      不可靠传输,head部分只有8个字节,总长不超过65535字节,正好放进一个ip数据包

    TCP的三次握手四次挥手

     为什么tcp是可靠传输?

      因为每次传输数据包时,都会有一个返回一个收到信号,如果没收到信号,则重发。

    为什么是四次挥手?

      这里有数据传输的问题,如果单方面直接断开连接,就可能造成数据丢失

2.、socket

  套接字,ip+port,  位于传输层和应用层之间的软件抽象层,就是一组接口,封装了TCP/IP协议。

# 服务端
import socket
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print(server)
server.bind(('127.0.0.1', 8080))
server.listen(5)

print("start.....")
conn, client_addr = server.accept()
# conn代表双向链接,用来收发消息
print(conn, client_addr)
data = conn.recv(1024) #1024接收的最大字节数bytes
print("客户端数据",data)
conn.send(data.title())

conn.close()
server.close()

# 客户端
import socket
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(("127.0.0.1", 8080))

client.send("hi man.".encode("UTF-8"))
data = client.recv(1024)
print(data.decode("UTF-8"))

client.close()
简单的通信
# 服务端
import socket
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(('127.0.0.1', 8080))
server.listen(5)    # 限制的是请求数,超过时,客户端抛异常

print("start.....")
conn, client_addr = server.accept()

# 加上通讯循环
while True:
    data = conn.recv(1024)
    print("客户端数据",data)
    conn.send(data.title())

conn.close()
server.close()

# 客户端
import socket
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(("127.0.0.1", 8080))

# 加上通讯循环
while True:
    msg = input(">>: ").strip()
    client.send(msg.encode("UTF-8"))
    data = client.recv(1024)
    print(data.decode("UTF-8"))

client.close()
加入了通信循环
# 解决一些小bug
# 端口占用问题
# 服务端不能收到空字符,但客户端发空,造成了双方都在相互等待
# 客户端不正常关闭,造成服务端也终止了

# 服务端
import socket
server= socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)#修复端口占用
server.bind(('127.0.0.1', 8080))
server.listen(5)

print("start.....")
#链路循环
while True:
    conn, client_addr =server.accept()
    print(client_addr)
    # 通讯循环
    while True:
        try:
            data = conn.recv(1024)
            #适用于linux,或window下有代码错误造成的关闭
            if not data: break    
            print("客户端数据",data)
            conn.send(data.title())
        #适用于window系统非正常关闭
        except ConnectionResetError as e:    
            break

    conn.close()
server.close()

# 客户端
import socket

client= socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(("127.0.0.1", 8080))

while True:
    msg = input(">>: ").strip()
    if not msg: continue
    phone.send(msg.encode("UTF-8"))
    data = phone.recv(1024)
    print(data.decode("UTF-8"))

client.close()
解决一些bug
# 服务端
import socket
import subprocess

server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
#修复端口占用
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server.bind(('127.0.0.1', 8080))
server.listen(5)

print("start.....")
while True:#链路循环
    conn, client_addr = server.accept()
    print(client_addr)

    while True:
        try:
            cmd = conn.recv(1024)
            if not cmd: break#适用于linux

            obj = subprocess.Popen(cmd.decode("UTF-8"), shell=True,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE)
            stdout = obj.stdout.read()
            stderr = obj.stderr.read()
            conn.send(stdout+stderr)#“+”可改进
            # 改进
            # conn.send(stdout)
            # conn.send(stderr)

        except ConnectionResetError as e:#适用于window系统
            break
    conn.close()
server.close()

# 客户端
import socket
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(("127.0.0.1", 8080))

while True:
    cmd = input(">>: ").strip()
    if not cmd: continue
    client.send(cmd.encode("UTF-8"))

    data = client.recv(1024)
    print(data.decode("GBK"))

client.close()
模拟ssh远程执行命令

3、粘包问题

  什么时粘包问题

    主要还是因为接收方不知道消息的界限,不知道一次可以提取多少字节数据造成的

  什么原因造成的粘包现象

    TCP传输时,使用了Nagle优化算法,会把多次间隔较小且数据量小的数据,合成一个大的数据包发送出去。

  什么情况下发生粘包

    1. 发送端会把多个时间间隔短,数据量小的数据放到一起发送到接收端

    2. 接收端不及时接收缓冲区的包,造成多个包放到一起,下次接收时,取到了上次遗留下的数据。

  如何解决粘包问题

    1. 第一步,使用自定义报头,把数据的长度还有其它的一些信息,放到报头里,

    2. 然后就用到了struct模块,这个模块可以把数据长度变成固定长度的字符,比如"i"模式是4个字符,

    3. 先接收报头长度的固定字符,得到报头长度,从报头里得到数据的长度,然后再接收数据

# 服务端
import socket
import subprocess
import struct
import json

server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)#修复端口占用
server.bind(('127.0.0.1', 8080))
server.listen(5)

print("start.....")
while True:#链路循环
    conn, client_addr = server.accept()

    while True:
        try:
            cmd = conn.recv(1024)
            if not cmd:
                break#适用于linux
            obj = subprocess.Popen(cmd.decode("UTF-8"), shell=True,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE)
            stdout = obj.stdout.read()
            stderr = obj.stderr.read()
            #制作报头
            header_dic = {
                "filename": "a.txt",
                "md5": "xxxxx",
                "total_size": len(stdout) + len(stderr)
            }
            header_str = json.dumps(header_dic)
            header_bytes = header_str.encode("UTF-8")

            #先发送报头长度
            conn.send(struct.pack("i",len(header_bytes)))
            #发报头
            conn.send(header_bytes)
            #发数据
            conn.send(stdout)#“+”可改进
            conn.send(stderr)

        except ConnectionResetError as e:#适用于window系统
            break

    conn.close()
server.close()
服务端
import socket
import struct
import json

client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(("127.0.0.1", 8080))

while True:
    cmd = input(">>: ").strip()
    if not cmd: continue
    client.send(cmd.encode("UTF-8"))

    #先收报头长度
    header_size = struct.unpack("i", client.recv(4))[0]
    #收报头,解析数据的长度
    header_bytes = client.recv(header_size)
    header_str = header_bytes.decode("UTF-8")
    header_dic = json.loads(header_str)
    print(header_dic)
    total_size = header_dic["total_size"]

    #接受数据
    recv_size = 0
    recv_data = ''
    while recv_size < total_size:
        if total_size-recv_size < 1024:
            res = client.recv(total_size-recv_size)
        else:
            res = client.recv(1024)
        # 不能直接接收1024个,
        # 原因:如果在接收文件时,服务端又发送了数据容易造成粘包问题
        # res = client.recv(1024)

        recv_data += res
        recv_size += len(res)

    print(recv_data.decode("GBK"))
client.close()
客户端

4、UDP传输

# 服务端
from socket import *

server = socket(AF_INET, SOCK_DGRAM)
server.bind(("127.0.0.1", 8080))
print("start......")
while True:
    data, client_addr = server.recvfrom(1024)
    server.sendto(data.upper(), client_addr)

server.close()

# 客户端
from socket import *

client = socket(AF_INET, SOCK_DGRAM)

while True:
    msg = input(">>: ").strip()
    client.sendto(msg.encode("UTF-8"),("127.0.0.1", 8080))

    data = client.recvfrom(1024)[0]
    print(data.decode("UTF-8"))

# 特点:
# 1. 可以先启动客户端,直接发数据,只不过数据发送出去就丢失了
# 2. 可以发空消息
# 3. 不会发生粘包问题
View Code

5、 socketserver模块

# 服务端
import socketserver

class MyTCPServer(socketserver.BaseRequestHandler):
    def handle(self):
        print("from %s"%self.client_address)
        while True:
            #通讯循环
            try:
                data = self.request.recv(1024)
                if not data:
                    break
                print("client's message: %s"%data)
                self.request.send(data.upper())
            except ConnectionResetError:
                break

if __name__ == '__main__':
    server = socketserver.ThreadingTCPServer(("127.0.0.1", 8080), MyTCPServer)
    server.serve_forever()
基于TCP传输
# 服务端
import socketserver

class MyUdphandler(socketserver.BaseRequestHandler):
    def handle(self):
        data,sock=self.request
        sock.sendto(data.upper(),self.client_address)

if __name__ == '__main__':
    server=socketserver.ThreadingUDPServer(('127.0.0.1',8081),MyUdphandler)
    server.serve_forever()

# 客户端
from socket import *
client=socket(AF_INET,SOCK_DGRAM)

while True:
    client.sendto(b'hello',('127.0.0.1',8081))
    data,server_addr=client.recvfrom(1024)
    print(data)
基于UDP传输

 

posted @ 2019-04-06 17:59  yw_sun  阅读(166)  评论(0)    收藏  举报