6 网络编程

1.硬件C/S架构(打印机)

2.软件C/S架构

互联网中处处是C/S架构

如黄色网站是服务端,你的浏览器是客户端(B/S架构也是C/S架构的一种)

腾讯作为服务端为你提供视频,你得下个腾讯视频客户端才能看它的视频)

C/S架构与socket的关系:

我们学习socket就是为了完成C/S架构的开发

客户端软件发送一串信息,会通过操作系统,操作系统在调用硬件,在通过网络。传输到服务器上,硬件接收,传给操作系统,操作系统在传给服务端软件。

1 osi模型

七层:

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

五层:

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

四层:

应用层--传输层--网络层--接口层

物理层:发送电信号。如:01001010 二进制数字

数据链路层:分组;

有Ethevent协议
报文:头部head(18个字节=6字节本机地址+6字节目标地址+6字节描述)-data(数据)
				mac地址 ++ 以广播的方式找寻地址 ++ 只能在子网中传播

网络层:路由寻路

ip协议和arp协议(arp协议是通过IP地址找mac地址)
ip头部 - data
ip地址 ++ 找寻在哪个子网中

传输层:tcp/udp协议,基于端口协议

建立双向传输管道
tcp三次握手,保证了数据传输的安全,建立了双向链接;断开连接4次握手
udp数据直接传输,数据不安全。

应用层:http,ftp,自己的协议


2 socket层

为何学习socket一定要先学习互联网协议:

1.首先:本节课程的目标就是教会你如何基于socket编程,来开发一款自己的C/S架构软件

2.其次:C/S架构的软件(软件属于应用层)是基于网络进行通信的

3.然后:网络的核心即一堆协议,协议即标准,你想开发一款基于网络通信的软件,就必须遵循这些标准。

image

什么是socket?

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

所以,我们无需深入理解tcp/udp协议,socket已经为我们封装好了,我们只需要遵循socket的规定去编程,写出的程序自然就是遵循tcp/udp标准的。

也有人将socket说成ip+port,ip是用来标识互联网中的一台主机的位置,而port是用来标识这台机器上的一个应用程序,ip地址是配置到网卡上的,而port是应用程序开启的,ip与port的绑定就标识了互联网中独一无二的一个应用程序

而程序的pid是同一台机器上不同进程或者线程的标识

3 套接字工作流程

image

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


4 socket模块

服务端套接字函数
s.bind() 绑定(主机,端口号)到套接字
s.listen() 开始TCP监听
s.accept() 被动接受TCP客户的连接,(阻塞式)等待连接的到来

客户端套接字函数
s.connect() 主动初始化TCP服务器连接
s.connect_ex() connect()函数的扩展版本,出错时返回出错码,而不是抛出异常

公共用途的套接字函数
s.recv() 接收TCP数据
s.send() 发送TCP数据(send在待发送数据量大于己端缓存区剩余空间时,数据丢失,不会发完)
s.sendall() 发送完整的TCP数据(本质就是循环调用send,sendall在待发送数据量大于己端缓存区剩余空间时,数据不丢失,循环调用send直到发完)
s.recvfrom() 接收UDP数据
s.sendto() 发送UDP数据
s.getpeername() 连接到当前套接字的远端的地址
s.getsockname() 当前套接字的地址
s.getsockopt() 返回指定套接字的参数
s.setsockopt() 设置指定套接字的参数
s.close() 关闭套接字

面向锁的套接字方法
s.setblocking() 设置套接字的阻塞与非阻塞模式
s.settimeout() 设置阻塞套接字操作的超时时间
s.gettimeout() 得到阻塞套接字操作的超时时间

面向文件的套接字的函数
s.fileno() 套接字的文件描述符
s.makefile() 创建一个与该套接字相关的文件

*tcp是基于链接的,必须先启动服务端,然后再启动客户端去链接服务端*

*tcp服务端*

1 ss = socket() #创建服务器套接字
2 ss.bind()      #把地址绑定到套接字
3 ss.listen()      #监听链接
4 inf_loop:      #服务器无限循环
5     cs = ss.accept() #接受客户端链接
6     comm_loop:         #通讯循环
7         cs.recv()/cs.send() #对话(接收与发送)
8     cs.close()    #关闭客户端套接字
9 ss.close()        #关闭服务器套接字(可选)

*tcp客户端*

1 cs = socket()    # 创建客户套接字
2 cs.connect()    # 尝试连接服务器
3 comm_loop:        # 通讯循环
4     cs.send()/cs.recv()    # 对话(发送/接收)
5 cs.close()            # 关闭客户套接字

socket通信流程与打电话流程类似,我们就以打电话为例来实现一个low版的套接字通信

服务端:

import socket  # from socket import *

# 初始化套接字
"""
参数1:套接字的类型
    family = socket.AF_INET  基于网络通信
参数2:
    type = socket.SOCK_STREAM  流式协议(TCP/IP协议)
    type = socket.SOCK_DGRAM  udp/ip套接字
参数3:
	protocal=0  一般不填,默认值为 0。
"""
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
phone.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)  # 重用该端口地址
# 绑定端口
"""
传入元组形式参数:
(服务端IP地址,端口地址0~65535;0-1024操作系统所用)
"""
phone.bind(('127.0.0.1', 8800))

# 监听端口
"""
listen:最大挂起的链接数
"""
phone.listen(5)

# 建立连接
"""
accept:阻塞端口,等待客户端链接(三次握手)
conn:链接对象,与客户端通信使用
client_addr:客户端IP地址和端口
"""
while 1:  # 链接循环
    conn, client_addr = phone.accept()

    # 连接成功,接收客户端数据
    """
    recv(1024):收取1024个字节
        1.单位:bytes
        2.1024代表最大接收1024个bytes
    """
    while 1:  # 通信循环
        """
        data = conn.recv(1024)
        if not data: break  # 如果接收数据为空,则终止循环(适用于linux系统)
        print("客户端%s数据:" % client_addr[1], data)
    
        # 给客户端发数据
        conn.send(data.upper())
        """

        # windows系统接收为空,解决方案:
        try:
            data = conn.recv(1024)
            print("客户端%s数据:" % client_addr[1], data)
            conn.send(data.upper())
        except ConnectionResetError:
            break

    # 断开链接
    conn.close()

# 关机
phone.close()

客户端:

import socket

# 初始化套接字
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 连接服务器
phone.connect(('127.0.0.1', 8800))

# 数据通信
while 1:  # 通讯循环
    """发送的格式必须是bytes格式"""
    msg = input('>>:').strip()
    if not msg: continue  # 输入数据为空,则重新输入
    phone.send(msg.encode('utf-8'))
    data = phone.recv(1024)
    print(data.decode('utf-8'))

# 断开链接
phone.close()

pkill -9 python清理端口linux

tasklist python清理端口window


5 粘包套接字

windows:
    dir:查看文件夹内容
    ipconfig:ip地址信息
    tasklist:查看运行进程
linux:
    ls;ifconfig;ps aux

5-1 subprocess模块

调用系统的命令,进而使用它。与os模块的区别在于,os模块查出的信息会直接打印在终端上,不可存储起来。

而subprocess模块,可以将查询的命令结果赋值。进而使用。

os模块
res = os.system('dir /C/')
返回值:0:命令执行成功,非0执行失败
subprocess模块
obj = subprocess.Popen('参数1', shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
参数1:
    字符串形式的命令
shell:
    启动一个终端,来解析参数1的命令
stdout:
    命令的正确结果放入PIPE生成的管道中
stderr:
    命令的错误结果放入PIPE生成的管道中
返回值:默认返还给终端。如果被引用,则不会在终端打印

print('obj 1>>', obj.stdout.read().decode('gbk))  # 查看成功结果
print('obj 2>>', obj.stderr.read().decode('gbk))  # 查看错误结果

5-2 struct模块

struct模块可以将数字打成固定长度的模块

import struct  
res = struct.pack('i', 128)  # 打包方式一,'i'范围小
res = struct.pack('l', 128)  # 打包方式二,'l'范围大
print(res, type(res), len(res))

obj = struct.unpack('i', res)  # 解包
print(obj[0])

5-3 套接字模板

1.recv和send都不是直接收到对方的数据,而是操作自己的操作系统内容--->不是一个send对应一个recv
2.recv接收:有个等待网络延时的阶段,耗时较长。还有数据拷贝阶段耗时短
  send:只有一个拷贝阶段,将内存数据拷贝给缓存

服务端:

from socket import *
import struct
import json
import subprocess

phone = socket(AF_INET, SOCK_STREAM)
phone.bind(('127.0.0.1', 8920))
phone.listen(5)

while 1:  # 链接循环
    conn, client_addr = phone.accept()

    while 1:
        try:
            # 1.收命令
            data = conn.recv(8096)
            print("客户端%s数据:" % client_addr[1], data)

            # 2.执行命令拿到结果
            obj = subprocess.Popen(data.decode('utf-8'), shell=True,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE)

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

            # 3.把命令的结果返回给客户端
            # 第一步:制作固定长度的报头
            header_dic = {
                'filename': 'a.txt',
                'md5': '9dis0diks',
                '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 + stderr)
        except ConnectionResetError:
            break

    conn.close()

phone.close()

客户端:

from socket import *
import struct
import json

phone = socket(AF_INET, SOCK_STREAM)
phone.connect(('127.0.0.1', 8920))


while 1:
    # 发命令
    msg = input('>>:').strip()
    if not msg: continue
    phone.send(msg.encode('utf-8'))

    # 拿到结果并打印
    # 第一步:先收到报头的长度
    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)
    total_size = header_dic['total_size']

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

    print(recv_data.decode('gbk'))

phone.close()

6 文件传输

服务端:

from socket import *
import struct
import json
import os

SHARE_DIR = "c/user/dir"

phone = socket(AF_INET, SOCK_STREAM)
phone.bind(('127.0.0.1', 8920))
phone.listen(5)

while 1:  # 链接循环
    conn, client_addr = phone.accept()

    while 1:
        try:
            data = conn.recv(8096)
            print("客户端%s数据:" % client_addr[1], data)

            # 1.解析命令,提取相应命令参数
            cmd = data.decode('utf-8').split()
            filename = cmd[1]

            header_dic = {
                'filename': filename,
                'md5': '9dis0diks',
                'file_size': os.path.getsize('%s/%s' % (SHARE_DIR, filename))
            }
            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)
            
            # 2.以读的方式打开文件,读取文件内容,发送给客户端
            with open('%s/%s' % (SHARE_DIR, filename), 'rb') as f:
                for line in f:
                    conn.send(line)
        except ConnectionResetError:
            break

    conn.close()

phone.close()

客户端:

from socket import *
import struct
import json

DOWNLOAD_DIR = "c/user/dir"

phone = socket(AF_INET, SOCK_STREAM)
phone.connect(('127.0.0.1', 8920))


while 1:

    msg = input('>>:').strip()
    if not msg: continue
    phone.send(msg.encode('utf-8'))

    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)
    total_size = header_dic['file_size']
    filename = header_dic['filename']  # 拿到文件名

    # 1.以写的方式打开一个新文件,接收服务端内容。
    with open('%s/%s' % (DOWNLOAD_DIR, filename), 'wb') as f:
        recv_size = 0
        while recv_size < total_size:
            line = phone.recv(1024)
            f.write(line)
            recv_size += len(line)
            print('总大小:%s  已下载:%s' % (total_size, recv_size))

phone.close()

7 面向对象版本

服务端:

import socket
import struct
import json
import subprocess
import os

class MYTCPServer:
    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='file_upload'

    def __init__(self, server_address, bind_and_activate=True):
        """Constructor.  May be extended, do not override."""
        self.server_address=server_address
        self.socket = 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):
        """Called by constructor to bind the socket.
        """
        if self.allow_reuse_address:
            self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self.socket.bind(self.server_address)
        self.server_address = self.socket.getsockname()

    def server_activate(self):
        """Called by constructor to activate the server.
        """
        self.socket.listen(self.request_queue_size)

    def server_close(self):
        """Called to clean-up the server.
        """
        self.socket.close()

    def get_request(self):
        """Get the request and client address from the socket.
        """
        return self.socket.accept()

    def close_request(self, request):
        """Called to clean up an individual request."""
        request.close()

    def run(self):
        while True:
            self.conn,self.client_addr=self.get_request()
            print('from client ',self.client_addr)
            while True:
                try:
                    head_struct = self.conn.recv(4)
                    if not head_struct:break

                    head_len = struct.unpack('i', head_struct)[0]
                    head_json = self.conn.recv(head_len).decode(self.coding)
                    head_dic = json.loads(head_json)

                    print(head_dic)
                    #head_dic={'cmd':'put','filename':'a.txt','filesize':123123}
                    cmd=head_dic['cmd']
                    if hasattr(self,cmd):
                        func=getattr(self,cmd)
                        func(head_dic)
                except Exception:
                    break

    def put(self,args):
        file_path=os.path.normpath(os.path.join(
            self.server_dir,
            args['filename']
        ))

        filesize=args['filesize']
        recv_size=0
        print('----->',file_path)
        with open(file_path,'wb') as f:
            while recv_size < filesize:
                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,filesize))


tcpserver1=MYTCPServer(('127.0.0.1',8080))

tcpserver1.run()

客户端:

import socket
import struct
import json
import os


class MYTCPClient:
    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.socket = socket.socket(self.address_family,
                                    self.socket_type)
        if connect:
            try:
                self.client_connect()
            except:
                self.client_close()
                raise

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

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

    def run(self):
        while True:
            inp=input(">>: ").strip()
            if not inp:continue
            l=inp.split()
            cmd=l[0]
            if hasattr(self,cmd):
                func=getattr(self,cmd)
                func(l)


    def put(self,args):
        cmd=args[0]
        filename=args[1]
        if not os.path.isfile(filename):
            print('file:%s is not exists' %filename)
            return
        else:
            filesize=os.path.getsize(filename)

        head_dic={'cmd':cmd,'filename':os.path.basename(filename),'filesize':filesize}
        print(head_dic)
        head_json=json.dumps(head_dic)
        head_json_bytes=bytes(head_json,encoding=self.coding)

        head_struct=struct.pack('i',len(head_json_bytes))
        self.socket.send(head_struct)
        self.socket.send(head_json_bytes)
        send_size=0
        with open(filename,'rb') as f:
            for line in f:
                self.socket.send(line)
                send_size+=len(line)
                print(send_size)
            else:
                print('upload successful')


client=MYTCPClient(('127.0.0.1',8080))

client.run()

8 udp套接字

udp套接字不会发生粘包,一个sendto对应一个recvfrom

*udp是无链接的,先启动哪一端都不会报错*

udp服务端

1 ss = socket()   #创建一个服务器的套接字
2 ss.bind()       #绑定服务器套接字
3 inf_loop:       #服务器无限循环
4     cs = ss.recvfrom()/ss.sendto() # 对话(接收与发送)
5 ss.close()                         # 关闭服务器套接字

udp客户端

cs = socket()   # 创建客户套接字
comm_loop:      # 通讯循环
    cs.sendto()/cs.recvfrom()   # 对话(发送/接收)
cs.close()                      # 关闭客户套接字

*udp套接字简单示例*

服务端:

from socket import *


server = socket(AF_INET, SOCK_DGRAM)
server.bind(('127.0.0.1', 8900))

while 1:
    data, client_addr = server.recvfrom(1024)
    print(data, client_addr)

    server.sendto(data.upper(), client_addr)

server.close()

客户端:

from socket import *


client = socket(AF_INET, SOCK_DGRAM)

while 1:
    msg = input(">>>:").strip()
    client.sendto(msg.encode('utf-8'), ('127.0.0.1', 8900))

    data, server_addr = client.recvfrom(1024)
    print(data, server_addr)

client.close()
posted @ 2022-09-22 16:38  角角边  Views(59)  Comments(0)    收藏  举报