返回顶部

网络编程基础

一. 简介

1.1 简单的socket通信例子(TCP)

服务端:

import socket

phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
phone.bind(('127.0.0.1',8083)) #0-65535:0-1024给操作系统使用
phone.listen(5)

print('starting...')
while True: # 链接循环
    conn,client_addr=phone.accept()
    print(client_addr)

    while True: #通信循环
        try:
            data=conn.recv(1024)
            if not data:break #适用于linux操作系统
            print('客户端的数据',data)

            conn.send(data.upper())
        except ConnectionResetError: #适用于windows操作系统
            break
    conn.close()

phone.close()

客户端:

import socket

phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)

phone.connect(('127.0.0.1',8083))

while True:
    msg=input('>>: ').strip() #msg=''
    if not msg:continue
    phone.send(msg.encode('utf-8')) #phone.send(b'')
    # print('has send')
    data=phone.recv(1024)
    # print('has recv')
    print(data.decode('utf-8'))

phone.close()

 

1.2 简单的socket通信(UDP)

服务端:

import socket
import subprocess
ip_port = ('127.0.0.1', 9003)
bufsize = 1024
udp_server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
udp_server.bind(ip_port)
while True:
    # 收消息
    cmd, addr = udp_server.recvfrom(bufsize)
    print('用户命令----->', cmd,addr)
    # 逻辑处理
    res = subprocess.Popen(cmd.decode('gbk'), shell=True, stderr=subprocess.PIPE, stdin=subprocess.PIPE,
                           stdout=subprocess.PIPE)
    stderr = res.stderr.read()
    stdout = res.stdout.read()
    # 发消息
    print(len(stdout)+len(stderr))
    udp_server.sendto(stdout + stderr, addr)
udp_server.close()

客户端:

from socket import *
import time
ip_port = ('127.0.0.1', 9003)
bufsize = 8192
udp_client = socket(AF_INET, SOCK_DGRAM)
while True:
    msg = input('>>: ').strip()
    if len(msg) == 0:
        continue
    udp_client.sendto(msg.encode('gbk'), ip_port)
    data, addr = udp_client.recvfrom(bufsize)
    print(data.decode('gbk'), end='')

 

1.3 粘包现象

 

   所谓粘包问题主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的。

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

 

1.4 解决粘包

  通过struct模块为字节流加上自定义固定长度报头,报头中包含字节流长度,然后一次send到对端,对端在接收时,先从缓存中取出定长的报头,然后再取真实数据

服务端:

import socket
import subprocess
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',9909)) #0-65535:0-1024给操作系统使用
phone.listen(5)

print('starting...')
while True: # 链接循环
    conn,client_addr=phone.accept()
    print(client_addr)

    while True: #通信循环
        try:
            #1、收命令
            cmd=conn.recv(8096)
            if not cmd:break #适用于linux操作系统

            #2、执行命令,拿到结果
            obj = subprocess.Popen(cmd.decode('gbk'), shell=True,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE)

            stdout=obj.stdout.read()
            stderr=obj.stderr.read()

            #3、把命令的结果返回给客户端
            #第一步:制作固定长度的报头
            header_dic={
                'filename':'a.txt',
                'md5':'xxdxxx',
                'total_size': len(stdout) + len(stderr)
            }

            header_json=json.dumps(header_dic)

            header_bytes=header_json.encode('utf-8')

            #第二步:先发送报头的长度
            conn.send(struct.pack('i',len(header_bytes)))

            #第三步:再发报头
            conn.send(header_bytes)

            #第四步:再发送真实的数据
            conn.send(stdout)
            conn.send(stderr)

        except ConnectionResetError: #适用于windows操作系统
            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',9909))

while True:
    #1、发命令
    cmd=input('>>: ').strip() #ls /etc
    if not cmd:continue
    phone.send(cmd.encode('gbk'))

    #2、拿命令的结果,并打印

    #第一步:先收报头的长度
    obj=phone.recv(4)
    header_size=struct.unpack('i',obj)[0]

    #第二步:再收报头
    header_bytes=phone.recv(header_size)

    #第三步:从报头中解析出对真实数据的描述信息
    header_json=header_bytes.decode('utf-8')
    header_dic=json.loads(header_json)
    print(header_dic)
    total_size=header_dic['total_size']

    #第四步:接收真实的数据
    recv_size=0
    recv_data=b''
    while recv_size < total_size:
        res=phone.recv(1024) #1024是一个坑
        recv_data+=res
        recv_size+=len(res)

    print(recv_data.decode('gbk'))

phone.close()

1.5 简单的FTP

服务端:

import socket
import subprocess
import struct
import json
import os

share_dir=r'/Users/linhaifeng/PycharmProjects/网络编程/05_文件传输/优化版本/server/share'

def get(conn,cmds):
    filename = cmds[1]

    # 3、以读的方式打开文件,读取文件内容发送给客户端
    # 第一步:制作固定长度的报头
    header_dic = {
        'filename': filename,  # 'filename':'1.mp4'
        'md5': 'xxdxxx',
        'file_size': os.path.getsize(r'%s/%s' % (share_dir, filename))
    # os.path.getsize(r'/Users/linhaifeng/PycharmProjects/网络编程/05_文件传输/server/share/1.mp4')
    }

    header_json = json.dumps(header_dic)

    header_bytes = header_json.encode('utf-8')

    # 第二步:先发送报头的长度
    conn.send(struct.pack('i', len(header_bytes)))

    # 第三步:再发报头
    conn.send(header_bytes)

    # 第四步:再发送真实的数据
    with open('%s/%s' % (share_dir, filename), 'rb') as f:
        # conn.send(f.read())
        for line in f:
            conn.send(line)

def put(conn,cmds):
    pass

def run():
    phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    # phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
    phone.bind(('127.0.0.1',8912)) #0-65535:0-1024给操作系统使用
    phone.listen(5)

    print('starting...')
    while True: # 链接循环
        conn,client_addr=phone.accept()
        print(client_addr)

        while True: #通信循环
            try:
                #1、收命令
                res=conn.recv(8096) # b'put 1.mp4'
                if not res:break #适用于linux操作系统

                #2、解析命令,提取相应命令参数
                cmds=res.decode('utf-8').split() #['put','1.mp4']
                if cmds[0] == 'get':
                    get(conn,cmds)
                elif cmds[0] == 'put':
                    input(conn,cmds)


            except ConnectionResetError: #适用于windows操作系统
                break
        conn.close()

    phone.close()


if __name__ == '__main__':
    run()

客户端:

import socket
import struct
import json

download_dir=r'/Users/linhaifeng/PycharmProjects/网络编程/05_文件传输/优化版本/client/download'

def get(phone,cmds):
    # 2、以写的方式打开一个新文件,接收服务端发来的文件的内容写入客户的新文件
    # 第一步:先收报头的长度
    obj = phone.recv(4)
    header_size = struct.unpack('i', obj)[0]

    # 第二步:再收报头
    header_bytes = phone.recv(header_size)

    # 第三步:从报头中解析出对真实数据的描述信息
    header_json = header_bytes.decode('utf-8')
    header_dic = json.loads(header_json)
    '''
            header_dic={
                'filename': filename, #'filename':'1.mp4'
                'md5':'xxdxxx',
                'file_size': os.path.getsize(filename)
            }
    '''
    print(header_dic)
    total_size = header_dic['file_size']
    filename = header_dic['filename']

    # 第四步:接收真实的数据
    with open('%s/%s' % (download_dir, filename), 'wb') as f:
        recv_size = 0
        while recv_size < total_size:
            line = phone.recv(1024)  # 1024是一个坑
            f.write(line)
            recv_size += len(line)
            print('总大小:%s   已下载大小:%s' % (total_size, recv_size))

def put(phone,cmds):
    pass

def run():
    phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)

    phone.connect(('127.0.0.1',8912))

    while True:
        #1、发命令
        inp=input('>>: ').strip() #get a.txt
        if not inp:continue
        phone.send(inp.encode('utf-8'))

        cmds=inp.split() #['get','a.txt']
        if cmds[0] == 'get':
            get(phone,cmds)
        elif cmds[0] == 'put':
            put(phone,cmds)

    phone.close()



if __name__ == '__main__':
    run()

 

1.6 初识socketserver

基于tcp的套接字,关键就是两个循环,一个链接循环,一个通信循环

socketserver模块中分两大类:server类(解决链接问题)和request类(解决通信问题)

 

源码分析总结:

基于tcp的socketserver我们自己定义的类中的

  1.   self.server即套接字对象
  2.   self.request即一个链接
  3.   self.client_address即客户端地址

基于udp的socketserver我们自己定义的类中的

  1.   self.request是一个元组(第一个元素是客户端发来的数据,第二部分是服务端的udp套接字对象),如(b'adsf', <socket.socket fd=200, family=AddressFamily.AF_INET, type=SocketKind.SOCK_DGRAM, proto=0, laddr=('127.0.0.1', 8080)>)
  2.   self.client_address即客户端地址

服务端:

import socketserver
class MyServer(socketserver.BaseRequestHandler):
    def handle(self):
        # 创建一个链接,继承于socketserver中的BaseRequestHandler类
        conn = self.request
        # 发送登录提示
        print("Client connect by:", self.client_address)
        while True:
            try:
                print("Waitting for recving message...")
                # 接收消息
                message = conn.recv(1024)
                if len(message) == 0:
                    break
                print(message.decode('utf-8'))
                # 收到exit就退出

                # 回复消息

                # 发送消息
                conn.sendall(message.upper())
            except ConnectionResetError:
                print('connection is lost by:', self.client_address)
                break

if __name__ == "__main__":

    server = socketserver.ThreadingTCPServer(('127.0.0.1', 8089, ), MyServer)
    # 调用serve_forever方法
    server.serve_forever()

客户端:

import socket

phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)

phone.connect(('127.0.0.1',8089))

while True:
    msg=input('>>: ').strip() #msg=''
    if not msg:continue
    phone.send(msg.encode('utf-8')) #phone.send(b'')
    # print('has send')
    data=phone.recv(1024)
    # print('has recv')
    print(data.decode('utf-8'))

phone.close()

 

posted @ 2020-10-22 11:51  muguangrui  阅读(65)  评论(0编辑  收藏  举报