网络通信

基于TCP网络通信

服务器端先初始化Socket,然后与端口绑定(bind),对端口进行监听(listen),调用accept阻塞,等待客户端连接。在这时如果有个客户端初始化一个Socket,然后连接服务器(connect),如果连接成功,这时客户端与服务器端的连接就建立了。客户端发送数据请求,服务器端接收请求并处理请求,然后把回应数据发送给客户端,客户端读取数据,最后关闭连接,一次交互结束
简易版网络通信模拟xshell
服务端

# 服务端server
import subprocess
import socket
phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)  # 拿到一个socket对象

# 调用sock.setsockopt设置这个socket选项,本例中把socket.SO_REUSEADDR设置为1,表示服务器端进程终止后,
# 操作系统会为它绑定的端口保留一段时间,以防其他进程在它结束后抢占这个端口。
phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)

phone.bind(("127.0.0.1", 8080)) # 绑定唯一一个软件的地址

# 调用sock.listen通知系统开始侦听来自客户端的连接,参数是在队列中的最大连接数。
phone.listen(5)
print("starting...")
while True:           # 服务器循环
    conn,addr=phone.accept() # 卡在这里直到,客户端返回一个元组,里面包含了socket对象以及客户端的地址
    # print("客户端对象", conn)
    print("客户端的ip地址", addr)
    while True:       # 通信循环
        try:
            data = conn.recv(1024)
            print("客户端发送的消息是", data)
            # conn.send(data.upper())
            res = subprocess.Popen(data.decode("gbk"), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
            data1 = res.stderr.read()
            data2 = res.stdout.read()
            conn.send(data1)
            conn.send(data2)
        except Exception:
            break
    conn.close()    #  关闭客户端套接字对象
phone.close()       # 关闭服务端套接字对象

客户端

# 客户端Client
import socket
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  # 创建客户端套接字对象
phone.connect(("127.0.0.1", 8080))     # 尝试连接服务器
while True:                           # 通讯循环,没有监听循环
    msg = input(">>:").strip()
    if not msg: continue
    phone.send(msg.encode("utf-8"))
    data2 = phone.recv(1024)     # 指定从缓存中取数据的最大字节
    print(data2.decode("gbk"))
phone.close()     # 关闭客户端套接字对象

基于UDP网络通信

UDP服务端

# UDP通信之服务端
import socket
udpserver = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# udpserver.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
udpserver.bind(("127.0.0.1",8080))

while True:   # 通信循环
    data, client_addr = udpserver.recvfrom(1024)
    print(data.decode("utf-8"))    # dada
    print(client_addr)             # ('127.0.0.1', 60167)
    udpserver.sendto(data.upper(), client_addr)

UDP客户端

# UDP通信之客户端
import socket
udpclient = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

server_ip_port = ("127.0.0.1", 8080)
while True:
    inp = input(">>:")
    udpclient.sendto(inp.encode("utf-8"), server_ip_port)
    data,server_addr = udpclient.recvfrom(1024)
    print(data.decode("utf-8"))

粘包现象

只有TCP有粘包现象,UDP永远不会粘包
在上述简易通信模型里,在客户端执行ipconfig,在执行cls。发现并未一次取完ipconfig的执行结果,执行cls时,仍打印ipconfig的结果,那是因为ipconfig执行结果大于1024字节,而客户端收数据时,只收取1024个字节。切客户段服务端都是从操作系统的缓存中拿数据。

粘包问题主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的
用json和struct解决方案
struct模块
该模块可以把一个类型,如数字,转成固定长度的bytes

# 把数字转换成四个字节的bytes,数字须小于int最大长度
struct.pack('i',12345678)   # b'Na\xbc\x00'

解决粘包
服务端

# 服务端(解决粘包问题)
import subprocess
import socket
import struct
import json
phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)  # 买手机
phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
phone.bind(("127.0.0.1", 8080)) # 绑定手机卡
phone.listen(5)
print("starting...")
while True:
    conn,addr=phone.accept() # c端连接成功返回一个元组,里面包含了socket对象以及客户端的地址
    # print("电话线路是", conn)
    print("客户端的手机号是", addr)
    while True:
        try:
            data = conn.recv(1024)
            print("客户端发送的消息是", data)
            # conn.send(data.upper())
            res = subprocess.Popen(data.decode("utf-8"), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
            data1 = res.stderr.read()  # dos错误的运行结果
            data2 = res.stdout.read()  # dos正确的运行结果

            data_size = len(data1)+len(data2)   # 得到原始数据总大小
            print(data_size)
            data_dic = {"size": data_size}   # 为避免粘包,自定制报头

            data_json = json.dumps(data_dic)   # 序列号报头
            data_json_bytes = data_json.encode("utf-8")    # 序列化并转成bytes,用于传输
            print(data_json)
            data_len = struct.pack("i", len(data_json_bytes)) # 打包得到报头长度
            # part1: 先发送报头的长度
            conn.send(data_len)
            # part2: 发送报头
            conn.send(data_json_bytes)
            # part3: 发送原始数据
            conn.send(data1)
            conn.send(data2)
        except Exception:
            break
    conn.close()
phone.close()

客户端

# 客户端(解决粘包问题)
import socket
import struct
import json
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
phone.connect(("127.0.0.1", 8080))
while True:
    msg = input(">>:").strip()
    if not msg: continue
    phone.send(msg.encode("utf-8"))
    head_dic_len = phone.recv(4)          # part1: 接受报头的长度
    head_dic_size = struct.unpack("i", head_dic_len)[0]  # 取出报头长度


    head_json = phone.recv(head_dic_size)   # part2: 接受报头
    head_dic = json.loads(head_json.decode("utf-8"))  # 反序列化拿到报头数据
    data_size = head_dic["size"]   # 拿出原始数据大小


    recv_size = 0
    recv_data = b""
    while data_size > recv_size:
        data = phone.recv(1024)   # part3: 接受原始数据
        recv_size += len(data)
        recv_data += data

    print(recv_data.decode("gbk"))
phone.close()

socketserver实现并发

TCP服务端

# 基于socketserver并发TCP通信
# 服务端
import  socketserver
class FtpServer(socketserver.BaseRequestHandler):
    def handle(self):
        # TCP下的request就是conn,客户端套接字
        print(self.request)   #  return self.socket.accept()
        while True:
            data=self.request.recv(1024)
            print(data.decode("utf-8"))
            self.request.send(data.upper())


if __name__ == '__main__':
    obj = socketserver.ThreadingTCPServer(("127.0.0.1",8080),FtpServer)
    print(obj.server_address)
    # print(obj.RequestHandlerClass)
    # print(obj.socket)
    obj.serve_forever()  # 链接循环

TCP客户端

# 于socketserver并发TCP通信
# 客户端
import  socket
server_addr = ("127.0.0.1",8080)
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
phone.connect(server_addr)
while True:  # 通讯循环
    inp = input(">>:")
    phone.send(inp.encode("utf-8"))
    data = phone.recv(1024)
    print(data)

并发UDP服务端

# UDP通信并发
# 服务端
import socketserver
class UdpServer(socketserver.BaseRequestHandler):
    def handle(self):
        # UDP协议下的request=(rec_data, self.socket)
        # data, client_addr = self.socket.recvfrom(self.max_packet_size)
        # return (data, self.socket), client_addr
        print(self.request[0])   # (data, self.socket)
        self.request[1].sendto(self.request[0].upper(), self.client_address)


if __name__ == '__main__':
    server_ip_port = ("127.0.0.1", 8080)
    obj = socketserver.ThreadingUDPServer(server_ip_port, UdpServer)
    print(obj.socket)
    obj.serve_forever()

并发UDP客户端

# UDP通信并发
# 客户端
import socket
udpclient = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

server_ip_port = ("127.0.0.1", 8080)
while True:
    inp = input(">>:")
    udpclient.sendto(inp.encode("utf-8"), server_ip_port)
    data,server_addr = udpclient.recvfrom(1024)
    print(data.decode("utf-8"))

作业

简易FTP实现
服务端

# ftp上传和下载(面向对象)
# 服务端
import socket
import json
import os
import struct
class FtpServer:
    address_family = socket.AF_INET
    socket_type = socket.SOCK_STREAM
    allow_reuse_address = False
    max_packet_size = 8192
    coding = "utf-8"
    request_queue_size = 5
    server_dir = r"C:\\Users\\Zou\\PycharmProjects\\py_fullstack_s4\\day37\\粘包"

    def __init__(self, server_address, bind_and_activate=True):
        """构造函数"""
        self.server_address = server_address
        self.phone = socket.socket(self.address_family,self.socket_type)

        if bind_and_activate:
            try:
                self.server_bind()
                self.server_activate()
            except:
                self.server_close()
                raise

    def server_bind(self):
        """绑定socket对象"""
        if self.allow_reuse_address:
            self.phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR, 1)
        self.phone.bind(self.server_address) # 绑定地址和端口号
        self.server_address = self.phone.getsockname()


    def server_activate(self):
        """服务端对象listen"""
        self.phone.listen(self.request_queue_size)


    def server_close(self):
        """关闭服务端socket对象"""
        self.phone.close()


    def get_request(self):
        """服务端开始监听accept"""
        return self.phone.accept()


    def close_request(self, request):
        """关闭客户socket对象"""
        request.close()


    def run(self):
        while True:   # 连接循环
            self.conn, self.client_addr = self.get_request()
            print("客户端地址:", self.client_addr)
            while True:   # 通信循环
                try:
                    head_struct = self.conn.recv(4)
                    print(head_struct)
                    if not head_struct:break

                    head_len = struct.unpack("i",head_struct)[0] # 拿到报头长度
                    head_json = self.conn.recv(head_len).decode(self.coding) # 拿到json字符串
                    head_dic = json.loads(head_json)
                    print(head_dic)  # 打印用户字典
                    # head_dic = {"cmd":"put", "file_name":"a.txt", "file_size":12345}
                    cmd = head_dic["cmd"]
                    if hasattr(self, cmd):
                        func = getattr(self,cmd)
                        func(head_dic)
                except Exception:
                    break

    def put(self, dic):
        """文件上传函数"""
        file_path = os.path.normpath(os.path.join(
            self.server_dir,
            dic["file_name"]
        ))
        file_size = dic["file_size"]
        recv_size = 0
        print("-------",file_path)
        with open(file_path,"wb") as f:
            while recv_size < file_size:
                recv_data = self.conn.recv(self.max_packet_size)
                f.write(recv_data)
                recv_size += len(recv_data)
                print("recvsize:%s filesize:%s"% (recv_size, file_size))


tcpserver1 = FtpServer(("127.0.0.1",8080))
tcpserver1.run()

客户端

# FTP客户端(面向对象)
import socket
import struct
import json
import os

class FtpClient:
    address_family = socket.AF_INET
    socket_type = socket.SOCK_STREAM
    allow_reuse_address = False
    max_packet_size = 8192
    coding = "utf-8"
    request_queue_size = 5


    def __init__(self, server_address, connect=True):
        self.server_address = server_address
        self.phone = socket.socket(self.address_family, self.socket_type)

        if connect:
            try:
                self.client_connect()
            except Exception:
                self.client_close()


    def client_connect(self):
        self.phone.connect(self.server_address)


    def client_close(self):
        self.phone.close()


    def run(self):
        """客户端主逻辑"""
        while True:
            inp = input(">>:").strip()   # put a.txt
            if not inp:continue
            l = inp.split()
            cmd = l[0]
            if hasattr(self, cmd):
                func = getattr(self, cmd)
                func(l)


    def put(self,l):
        cmd = l[0]
        filename = l[1]
        if not os.path.isfile(filename):
            print("%s文件不存在" % filename)
            return

        filesize = os.path.getsize(filename)
        # head_dic = {"cmd":"put", "file_name":"a.txt", "file_size":12345}
        head_dict = {"cmd":cmd, "file_name":filename, "file_size":filesize}
        head_json = json.dumps(head_dict).encode(self.coding)
        head_len = struct.pack("i", len(head_json))
        self.phone.send(head_len)
        self.phone.send(head_json)
        send_size = 0
        with open(filename, "rb") as f:
            for line in f:
                self.phone.send(line)
                send_size += len(line)
                print(send_size)
            else:
                print("文件上传成功")


client = FtpClient(("127.0.0.1",8080))
client.run()
posted @ 2017-05-05 17:38  pirate邹霉  阅读(181)  评论(0编辑  收藏  举报