zoukankan      html  css  js  c++  java
  • 网络编程 ------ 基础

    一 . 软件

      客户端: CS架构 , client --> server

      浏览器: BS架构 , browser --> server

    二 . socket模块

      socket通常也称作"套接字" , 用于描述IP地址和端口 , 是一个通信链的句柄 , 应用程序通常通过"套接字"向网络发出请求或者应答网络请求.

      socket起源于Unix , 而Unix/Linux基本哲学之一就是"一切皆文件" , 对于文件用[打开] [读写] [关闭] 模式来操作 . socket就是该模式的一个实现 , socket即是一种特殊的文件 , 一些socket函数就是对其进行的操作

      socket和file的区别:

        -- file模块是针对某个指定文件进行[打开] [读写] [关闭]

        -- socket模块是针对 服务器端 和 客户端 Socket 进行[打开] [读写] [关闭]

    import socket
    
    # 创建服务端socket对象
    server = socket.socket()
    
    # 绑定IP和端口
    server.bind(('192.168.13.155',8000))
    
    # 后边可以等5个人
    server.listen(5)
    
    print('服务端准备开始接收客户端的连接')
    # 等待客户端来连接,如果没人来就傻傻的等待。
    # conn是客户端和服务端连接的对象(伞),服务端以后要通过该对象进行收发数据。
    # addr是客户端的地址信息。
    # #### 阻塞,只有有客户端进行连接,则获取客户端连接然后开始进行通信。
    conn,addr = server.accept()
    
    print('已经有人连接上了,客户端信息:',conn,addr)
    
    
    # 通过对象去获取(王晓东通过伞给我发送的消息)
    # 1024表示:服务端通过(伞)获取数据时,一次性最多拿1024字节。
    data = conn.recv(1024)
    print('已经有人发来消息了',data)
    
    
    # 服务端通过连接对象(伞)给客户端回复了一个消息。
    conn.send(b'stop')
    
    # 与客户端断开连接(放开那把伞)
    conn.close()
    
    # 关闭服务端的服务
    server.close()
    View Code

      服务端和客户端的操作:

    import socket
    
    server = socket.socket()
    
    server.bind(("IP地址",端口))
    
    server.listen(服务次数)
    
    while 1:
        conn,addr = server.accept()   # 等待与客户端连接
        
        while 1:
            data = conn.recv(最大字节数 = 1024)
            if data == b"exit":
                break
    
            result = data + b"任意"
            conn.send(result)     # 返回给客户端的内容
        
        conn.close()
    客户端
    import socket
    
    sk = socket.socket()
    
    sk.connect("服务端IP",服务端端口)
    
    while 1:
        name = input(">>>")
        sk.send(name.encode("utf-8"))   # 传给服务端必须是字节
        
        if name == "exit":
            break
    
        result = sk.recv(1024)          # 服务端回复给客户端的也是字节
        print(result.decode("utf-8"))
    
    sk.close()
    客户端

      总结:

        python3 : send / recv 都是字节

        python2 : send / recv 都是字符串

        服务端 :

          accept : 阻塞 , 等待客户端来连接

          recv : 阻塞 , 等待客户端发来数据

        客户端 :

          connect : 阻塞 ,一直在连接 , 直到连接成功才往下运行其他代码

          recv : 阻塞 , 等待服务端发来数据    

     三 . Tcp协议和Udp协议

      Tcp : 可靠的 , 面向连接的协议 , 传输效率低全双工通信 , 面向字节流 . 使用Tcp的应用 : Web浏览器 , 电子邮件 , 文件传输程序.

      Udp : 不可靠的 , 无连接的服务 , 传输效率高 , 一对一 , 一对多 , 多对一 , 多对多 , 面向报文 , 尽最大努力服务 , 无阻塞控制 . 使用UDP的应用 : 域名系统(DNS) , 视频流 , IP语音(Volp) . 

    四 . 套接字初使用

      1. 基于TCP协议的socket

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

      server端 :

    import socket
    sk = socket.socket()
    sk.bind(('127.0.0.1',8898))  #把地址绑定到套接字
    sk.listen()          #监听链接
    conn,addr = sk.accept() #接受客户端链接
    ret = conn.recv(1024)  #接收客户端信息
    print(ret)       #打印客户端信息
    conn.send(b'hi')        #向客户端发送信息
    conn.close()       #关闭客户端套接字
    sk.close()        #关闭服务器套接字(可选)
    View Code

      client端 :

    import socket
    sk = socket.socket()           # 创建客户套接字
    sk.connect(('127.0.0.1',8898))    # 尝试连接服务器
    sk.send(b'hello!')
    ret = sk.recv(1024)         # 对话(发送/接收)
    print(ret)
    sk.close()            # 关闭客户套接字
    View Code

      问题 : 在重启服务端时可能会遇到

     

      解决方法:

    #加入一条socket配置,重用ip和端口
    import socket
    from socket import SOL_SOCKET,SO_REUSEADDR
    sk = socket.socket()
    sk.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) #就是它,在bind前加
    sk.bind(('127.0.0.1',8898))  #把地址绑定到套接字
    sk.listen()          #监听链接
    conn,addr = sk.accept() #接受客户端链接
    ret = conn.recv(1024)   #接收客户端信息
    print(ret)              #打印客户端信息
    conn.send(b'hi')        #向客户端发送信息
    conn.close()       #关闭客户端套接字
    sk.close()        #关闭服务器套接字(可选)
    View Code

      2 . 基于UDP协议的socket

        udp是无链接的 , 启动服务之后可以直接接受消息 , 不需要提前建立链接.

      server端:

    import socket
    udp_sk = socket.socket(type=socket.SOCK_DGRAM)   #创建一个服务器的套接字
    udp_sk.bind(('127.0.0.1',9000))        #绑定服务器套接字
    msg,addr = udp_sk.recvfrom(1024)
    print(msg)
    udp_sk.sendto(b'hi',addr)                 # 对话(接收与发送)
    udp_sk.close()                         # 关闭服务器套接字
    View Code

      client端:

    import socket
    
    ip_port=('127.0.0.1',9000)
    
    udp_sk=socket.socket(type=socket.SOCK_DGRAM)
    udp_sk.sendto(b'hello',ip_port)
    
    back_msg,addr=udp_sk.recvfrom(1024)
    
    print(back_msg.decode('utf-8'),addr)
    View Code

      QQ聊天:

    #_*_coding:utf-8_*_
    import socket
    ip_port=('127.0.0.1',8081)
    udp_server_sock=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
    udp_server_sock.bind(ip_port)
    
    while True:
        qq_msg,addr=udp_server_sock.recvfrom(1024)
        print('来自[%s:%s]的一条消息:33[1;44m%s33[0m' %(addr[0],addr[1],qq_msg.decode('utf-8')))
        back_msg=input('回复消息: ').strip()
    
        udp_server_sock.sendto(back_msg.encode('utf-8'),addr)
    
    server
    server
    #_*_coding:utf-8_*_
    import socket
    ip_port=('127.0.0.1',8081)
    udp_server_sock=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
    udp_server_sock.bind(ip_port)
    
    while True:
        qq_msg,addr=udp_server_sock.recvfrom(1024)
        print('来自[%s:%s]的一条消息:33[1;44m%s33[0m' %(addr[0],addr[1],qq_msg.decode('utf-8')))
        back_msg=input('回复消息: ').strip()
    
        udp_server_sock.sendto(back_msg.encode('utf-8'),addr)
    
    server
    client

      时间服务器:

    # _*_coding:utf-8_*_
    from socket import *
    from time import strftime
    
    ip_port = ('127.0.0.1', 9000)
    bufsize = 1024
    
    tcp_server = socket(AF_INET, SOCK_DGRAM)
    tcp_server.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
    tcp_server.bind(ip_port)
    
    while True:
        msg, addr = tcp_server.recvfrom(bufsize)
        print('===>', msg)
    
        if not msg:
            time_fmt = '%Y-%m-%d %X'
        else:
            time_fmt = msg.decode('utf-8')
        back_msg = strftime(time_fmt)
    
        tcp_server.sendto(back_msg.encode('utf-8'), addr)
    
    tcp_server.close()
    
    server
    server
    #_*_coding:utf-8_*_
    from socket import *
    ip_port=('127.0.0.1',9000)
    bufsize=1024
    
    tcp_client=socket(AF_INET,SOCK_DGRAM)
    
    while True:
        msg=input('请输入时间格式(例%Y %m %d)>>: ').strip()
        tcp_client.sendto(msg.encode('utf-8'),ip_port)
    
        data=tcp_client.recv(bufsize)
    
    client
    client

    五 . 黏包

      同时执行多条命令之后 , 得到的结果很可能只有一部分 , 在执行其他命令的时候又接收到之前执行的另外一部分结果 , 这种现象就是黏包 . 

      注意 : 只有TCP有黏包现象 , UDP永远不会黏包

      TCP是面向连接的 , 面向流的 , 提供高可靠性服务 . 

      收发两端都要有一一承兑的socket . 因此 , 发送端为了将多个发往接收端的包 , 更有效的发到对方 , 使用了优化方法 , 将多次间隔较小且数据量小的数据 , 合并成一个大的数据块 , 然后进行封包 . 

      1. 发生黏包的两种情况

        情况一 : 发送方的缓存机制

          发送端需要等缓冲区满才发送出去 , 造成黏包(发送数据时间间隔很短 , 数据量很小 , 会合到一起 , 产生黏包)

    #_*_coding:utf-8_*_
    from socket import *
    ip_port=('127.0.0.1',8080)
    
    tcp_socket_server=socket(AF_INET,SOCK_STREAM)
    tcp_socket_server.bind(ip_port)
    tcp_socket_server.listen(5)
    
    
    conn,addr=tcp_socket_server.accept()
    
    
    data1=conn.recv(10)
    data2=conn.recv(10)
    
    print('----->',data1.decode('utf-8'))
    print('----->',data2.decode('utf-8'))
    
    conn.close()
    
    服务端
    server
    #_*_coding:utf-8_*_
    import socket
    BUFSIZE=1024
    ip_port=('127.0.0.1',8080)
    
    s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    res=s.connect_ex(ip_port)
    
    
    s.send('hello'.encode('utf-8'))
    s.send('egg'.encode('utf-8'))
    
    客户端
    client

        情况二 : 接收方的缓存机制

          接受方不及时接受缓冲区的包 , 造成多个包接受(客户端发送了一段数据 , 服务端只收了一小部分 , 服务端下次再收的时候还是从缓冲区拿上次遗留的数据 , 产生黏包)

    #_*_coding:utf-8_*_
    from socket import *
    ip_port=('127.0.0.1',8080)
    
    tcp_socket_server=socket(AF_INET,SOCK_STREAM)
    tcp_socket_server.bind(ip_port)
    tcp_socket_server.listen(5)
    
    
    conn,addr=tcp_socket_server.accept()
    
    
    data1=conn.recv(2) #一次没有收完整
    data2=conn.recv(10)#下次收的时候,会先取旧的数据,然后取新的
    
    print('----->',data1.decode('utf-8'))
    print('----->',data2.decode('utf-8'))
    
    conn.close()
    server
    #_*_coding:utf-8_*_
    import socket
    BUFSIZE=1024
    ip_port=('127.0.0.1',8080)
    
    s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    res=s.connect_ex(ip_port)
    
    
    s.send('hello egg'.encode('utf-8'))
    client

        总结 :

          黏包现象只发生在tcp协议中 :

            1. 从表面上看 , 黏包问题主要是发送方和接受方的缓存机制 , tcp协议面向流通信的特点

            2. 实际上 , 主要还是因为接收方不知道消息之间的界限 , 不知道一次性提取多少字节的数据所造成的

    六 . 黏包的解决的方案

    1. struct模块

    import struct
    
    
    res=struct.pack("i","")
    
    print(res)
    print(len(res))
    
    
    obj=struct.unpack("i",res)
    print(obj[0])
    
    """
    结果:
    b'xb3xb5Vx07'
    4
    123123123
    """
    View Code

      该模型可以把一个类型转换成固定长度的bytes(固定长度4)

    2. subprocess模块

    import subprocess
    
    res=subprocess.Popen("dir",
                         shell=True,
                         stderr=subprocess.PIPE,
                         stdout=subprocess.PIPE)
    
    
    print(res.stdout.read().decode("gbk"))
    
    """
    结果:
     驱动器 C 中的卷是 Windows
     卷的序列号是 A786-D135
    
     C:UsersAdministratorPycharmProjectsuntitled1基础28day 的目录
    
    2018/09/04  15:50    <DIR>          .
    2018/09/04  15:50    <DIR>          ..
    2018/09/04  15:50               218 1.py
    2018/09/04  14:53                 0 __init__.py
                   2 个文件            218 字节
                   2 个目录 215,588,208,640 可用字节
    """
    View Code

    3. 使用struct模块解决黏包

      借助struct模块 , 我们可以知道长度数字可以转换成一个标准大小的4字节数字 . 因袭可以利用这个特点来预先发送长度 . 

    发送时 接收时
    先发送struct转换好的数据长度4字节 先接受4字节使用struct转换成数字来获取要接收的数据长度
    再发送数据 再按照长度接受数据
    import socket
    import subprocess
    
    server = socket.socket()
    
    server.bind(('127.0.0.1',8008))
    
    server.listen(5)
    
    while True:
        print("server is working.....")
        conn,addr = server.accept()
        # 字节类型
        while True:
            # 针对window系统
            try:
                cmd = conn.recv(1024).decode("utf8") # 阻塞
    
                if cmd == b'exit':
                    break
    
                res=subprocess.Popen(cmd,
                                 shell=True,
                                 stderr=subprocess.PIPE,
                                 stdout=subprocess.PIPE,
                                 )
                # print("stdout",res.stdout.read())
                # print("stderr",res.stderr.read().decode("gbk"))
                out=res.stdout.read()
                err=res.stderr.read()
    
                print("out响应长度",len(out))
                print("err响应长度",len(err))
                if err:
                     import struct
                     header_pack = struct.pack("i", len(err))
                     conn.send(header_pack)
                     conn.send(err)
                else:
                     #构建报头
                     import struct
                     header_pack=struct.pack("i",len(out))
                     print("header_pack",header_pack)
                     # # 发送报头
                     conn.send(str(len(out)).encode("utf8"))
                     # 发送数据
                     conn.send(out)
    
            except Exception as e:
                break
    
        conn.close()
    server
    import socket
    import struct
    sk = socket.socket()
    
    sk.connect(('127.0.0.1',8008))
    
    while 1:
        cmd = input("请输入命令:")
        sk.send(cmd.encode('utf-8')) # 字节
        if cmd=="":
            continue
        if cmd == 'exit':
            break
    
        header_pack=sk.recv(4)
        data_length=struct.unpack("i",header_pack)[0]
        print("data_length",data_length)
    
        data_length=int(sk.recv(1024).decode("utf8"))
        print("data_length",data_length)
    
        recv_data_length=0
        recv_data=b""
    
        while recv_data_length<data_length:
            data=sk.recv(1024)
            recv_data_length+=len(data)
            recv_data+=data
    
        print(recv_data.decode("gbk"))
    
    sk.close()
    client

     4. 解决黏包的两种方法

    方法一:

    import socket
    import json
    
    sock = socket.socket()
    sock.bind(("121.12.11.11",5555))
    sock.listen(5)
    
    while 1:
        print("server is working....")
        conn,addr = sock.accept()
        
        while 1:
            data = conn.recv(1024).decode("utf-8")
            file_info = josn.loads(data)
    
            # 文件信息
            action = file_info.get("action")
            filename = file_info.get("filename")
            filesize = file_info.get("filesize")
    
            connsend(b"succes")
    
            # 接收文件数据
            with open("put/" +filename , "wb") as f:
                recv_data_length = 0
                while recv_data_lengh < filesize:
                    data = conn.recv(1024)
                    recv_data_length += len(data)
                    f.write(data)
                    print("文件总大小: %s , 已接收 %s" %(filesize,recv_data_length))
            print("接收成功!")
            break
        
        sock.close()
    FTP -- server
    import socket
    import os
    import json
    
    
    sock=socket.socket()
    sock.connect(("121.12.11.11",5555))
    
    while 1:
        cmd = ("请输入命令:")
        action,filename = cmd.strip().split(" ")
    
        file_info = {"action":action,"filename":filename,"filesize":filesize}
        file_info_josn = josn.dumps(file_info).encode("utf-8")
        sock.send(file_info_josn)
    
        # 确认服务端接收到了文件信息
        code = sock.recv(1024).decode("utf-8")
        if code == "succes":
            # 发送文件数据
            with open("filename","rb") as f:
                for line in f:
                    sock.send(line)
        else:
            print("服务器异常!")
        break
    
    sock.close()
    FTP -- client

    方法二:

    import struct
    import socket
    import josn
    import hashlib
    
    
    sock = socket.socket()
    sock.bind(("121.21.12.11",5555))
    sock.listen(5)
    
    while 1:
        print("server is working...")
        conn,addr = sock.accept()
        
        while 1:
            
            # 接收josn的打包长度
            file_info_length_pack = conn.recv(4)
            file_info_length = struct.unpack("i",file_info_length_pack)[0]
    
            # 接收josn字符串
            file_info_josn = conn.recv(file_info_length)
            file_info = josn.loads(file_info_josn)
    
            action=file_info.get("action")
            filename=file_info.get("filename")
            filesize=file_info.get("filesize")
    
            # 循环接收文件
            md5 = hashlib.md5()
            with open("put/" + filename,"wb") as f:
                recv_data_length = 0
    
                while recv_data_length < filesize:
                    data = conn.recv(1024)
                    recv_data_length += len(data)
                    f.write()
                    
                    # md5摘要
                    md5.update(data)
                    print("文件总大小:%s,已成功接收%s" %(filesize,recv_data_length))
            print("接收成功!")
            conn.send(b"ok")
            
            md5_val = md5.hexdigest()
            client_md5 = conn.recv(1024).decode("utf-8")
            
            if md5_val == client_md5:
                conn.send(b"203")
    
            else:
                conn.send(b"204")
        
            break
        
        sock.close()
    FTP -- server
    import socket
    import os
    import json
    import struct
    import hashlib
    
    
    sock=socket.socket()
    sock.connect(("121.21.12.11",5555))
    
    while 1:
        cmd = input("请输入命令:")
        action , filename = cmd.strip().split(" ")
        filesize = os.patn.getsize(filename)
    
        file_info = {"action":action,"filename":filename,"filesize":filesize}
        file_info_josn = josn.dumps(file_info).encode("utf-8")
    
        res = struct.pack("i",len(file_info_josn))
        
        # 发送file_info_josn 的打包长度
        sock.send(res)
    
        # 发送 file_info_josn字节串
        sock.send(file_info_josn)
    
        md5 = hashlib.md5()
        # 发送文件数据
        with open(filename,"rb") as f:
            for line in f:
                sock.send(line)
                md5.update(line)
        data = sock.recv(1024)
        md5_val = md5.hexdigest()
        sock.send(md5_val.encode("utf-8"))
        is_val = sock.recv(1024).decode("utf-8")
        
        if is_val == "203":
            print("文件上传成功!")
    
        else:
            print("文件上传失败!")
        break
    
    sock.close()
    FTP -- client

    七 . 并发编程(socketserver模块)

    import socketserver
    
    
    class Myserver(socketserver.BaseRequestHandler):
        def handle(self):
    
            # 字节类型
            while 1:
    
                # 针对window系统
                try:
                    print("等待信息")
                    data = self.request.recv(1024)  # 阻塞
    
                    # 针对linux
                    if len(data) == 0:
                        break
    
                    if data == b'exit':
                        break
    
                    response = data + b'SB'
                    self.request.send(response)
    
                except Exception as e:
                    break
    
            self.request.close()
    
    # 1 创建socket对象 2 self.socket.bind()  3 self.socket.listen(5)
    server=socketserver.ForkingUDPServer(("127.0.0.1",8899),Myserver)
    
    server.serve_forever()
    FTP -- server
    import socket
    
    sk = socket.socket()
    
    sk.connect(('127.0.0.1',8899))
    
    while 1:
        name = input(">>>>:")
        sk.send(name.encode('utf-8')) # 字节
    
        response = sk.recv(1024) # 字节
        print(response.decode('utf-8'))
    FTP -- client
  • 相关阅读:
    【Henu ACM Round#15 F】Arthur and Questions
    【Henu ACM Round#16 F】Om Nom and Necklace
    【Henu ACM Round#16 E】Paths and Trees
    JS制作的简单的三级及联
    .Net实现的批量删除(使用了repeater控件)
    setInterval和setTimeout调用方法小知识科普
    AJAX制作JSON格式的实时更新数据的方法
    关于获取网站域名的写法杂谈
    JS初识(着重讲解Date函数)
    Guid函数
  • 原文地址:https://www.cnblogs.com/xiangweilai/p/9579066.html
Copyright © 2011-2022 走看看