• Python 17 socket网络编程


    网络编程

    第一部分:计算机网络基础

    一、软件开发架构

    1、应用类——C/S架构(client / server)

    服务端一直运行,等待服务别人。客户端寻求服务。

    客户端通过互联网与服务端建立连接

    2、web类——B/S架构(broser / server)

    b/s其实也是一种c/s架构,只是将客户端都变成了浏览器或某个程序,统一连接的入口。

    二、网络基本概念

    1、mac地址

    计算机通过网卡和网线与其他机器进行连接,每一台计算机都有唯一的网卡编号,称之为mac地址。但是mac地址很长很复杂,不容易记忆,于是人们将mac地址按照一定的规则重新改编成由4个点分十进制组成的IP地址。

    2、IP地址

    由4个点分十进制数组成,每个数的范围是0~255,其本质是一个8位二进制数。

    3、arp协议

    规定了如何通过ip地址直到mac地址的协议。发送方将封装了目标IP、自身IP和mac地址的数据传输给交换机,交换机通过广播的方式发送给目标网段,目标收到后确认是自己的IP,会将自己的mac返回给交换机,交换机将双方的IP和mac缓存,以后就可以知道IP和mac的对应关系。

    4、端口

    相当于是一台机器上的一个应用程序的编号,同一时期同一机器上,一个端口号只能由一个应用程序占用。范围是0--65535。

    5、交换机

    用来解决多台机器间的通信问题。当机器a需要给机器b发送消息时,通过广播的方式向局域网内的所有机器发送讯息,b接受到消息确认是自己时通过单播的方式回复a。arp协议就是a拿着b的ip地址广播,b确定是自己的ip后将自己的mac地址返回给a,a将这个mac地址缓存下来,后面再进行通信就直接能定位到b,不再需要广播。

    6、路由器

    路由器将多个交换机连接起来。机器不能直接和局域网外的机器通信,需要通过网关。通过IP地址和子网掩码按位与运算,得到局域网的网段。

    7、TCP协议

    特点:可靠的、面向连接、无边界字节流、速度较慢、占用操作系统的链接资源。

    ①可靠:TCP协议会保证数据的完整性,每次发送消息都需要受到一条回执,否则会重复发送,确保对方收到。不会发生丢包。

    ②面向连接:必须先建立连接再通信,实现一个全双工的双向通信。全双工意思是客户端和服务端可以互相收发消息。这个过程就像是打电话,双方必须一直保持电话才可以通信。

    ③无边界字节流:多条数据之间是没有边界的,无法区分,会产生粘包,只能在应用层解决。

    ④速度慢:因为面向连接且可靠,建立连接和断开连接都需要时间,并且对于数据完整性的保护也需要消耗时间。

    ⑤占用操作系统资源:在不使用任何异步机制、在阻塞IO模型下,一个server端只能和一个client端连接

    通过tcp协议建立链接需要通过三次握手和四次挥手:

           三次握手:客户端向服务端发送SYN请求建立链接——服务端同意(ACK)并请求向客户端建立链接(SYN)——客户端同意(ACK)。

           四次挥手:客户端向服务端请求断开连接——服务端同意, 服务端向客户端请求断开连接——客户端同意。

    为什么是三次握手和四次挥手呢?因为TCP协议是全双工的,建立在双方通信的基础上,所以如果服务端同意客户端向自己建立连接,那么一定会请求与客户端建立连接,同意客户端的请求和发送自己的请求是绑定在一起必须一起发生的,这两个步骤可以合并。但是断开连接的过程并不是,客户端可以单方面的断开与服务端的连接,而服务端可以选择继续保持连接,因此同意断开和请求断开并不是一定绑定在一起的。

    8、UDP协议

    UDP协议是不可靠的、无连接、面向数据报、速度快、能够传输的数据长度有限、可以和任意多个客户端通信。他不建立连接,直接将信息传递到网络中。这个过程就像是发信息,不用管对方是否看到,都可以直接发送数据。

    9、http协议 

    超文本传输协议,用于服务端和浏览器进行数据交互,规定了请求和响应的格式,底层基于TCP协议,可以传输任意类型的数据。

    工作流程一般是客户端与服务器建立连接——客户端向服务器发送请求——服务器接收请求并根据请求返回数据——断开连接。http请求是一种无状态的短连接,为了应对数量庞大的网络请求,所以http连接都是一次性的,每次只处理一个请求,请求结束则断开,这样有限的请求数才能更上需求,且服务器不会保存每次请求的状态,不会保留信息,大大减轻了存储负担。

    http请求分为三个部分:

    • 状态行:请求方式、url和协议版本
    • 请求头:访问的域名、用户代理、cookie等信息
    • 请求体:请求的数据

    http响应也分为三个部分:

    • 状态行:状态码、响应短语、协议版本
    • 响应头:搭建服务器的软件、发送响应的时间、相应数据的格式等
    • 响应体:具体数据

    HTTP请求报文格式:

     HTTP响应报文格式:

    10、https协议

    http协议是不安全的,体现在三个方面:内容不加密可能泄露、无法认证双方的身份、无法确认收到的内容是否完整。

    https是http+ssl(secure socket layer安全套接层)构成的协议,用来解决http的安全问题,其实质就是在http协议后,传输层之前添加一层加密。https使用公开密钥加密和共享密钥加密相结合的混合密钥加密方式,具体流程如下:建立连接时,服务端将封装了自己密钥的证书发送给客户端,客户端使用认证机构的公钥解密,得到服务端的公钥,之后的通信过程中,客户端和服务端都通过这一共享公钥进行加密解密。

    三、互联网协议——osi五层模型

    互联网协议就是只当进行网络通信时,数据的传输格式的规范。

    分为应用层、传输层、网络层、数据链路层和物理层。

    应用层包裹了真正需要传输的数据,传输层包装了使用的传输协议比如TCP、UDP,网络层包装了目标的IP地址,数据链路层包装了发送者的mac地址,物理层将这些数据都转换成01这样的电信号。

    第二部分:socket学习

    一、socket基本使用

    socket又叫套接字,是python提供的用来帮助我们将数据包装成符合互联网协议格式的数据。

    1、基于TCP协议的socket

    import socket
    sk = socket.socket()    #创建socket对象,买手机
    sk.bind(('127.0.0.1', 8080))    #绑定ip和端口,绑定手机卡
    sk.listen()    #开始监听,打开手机等待电话
    conn, addr = sk.accept()    #接到电话,在这里已经经过了三次挥手建立了连接,程序会阻塞
    ret = conn.recv(1024).decode('utf-8')    #听到别人说的话
    conn.send(b'asd')    #对别人说话
    conn.close()    #挂断电话
    sk.close()    #关机
    server端
    import socket
    sk = socket.socket()
    sk.connect(('127.0.0.1', 8080))
    sk.send(b'xxxx')
    ret = sk.recv(1024).decode('utf-8')
    sk.close()
    client端

    注意,传输过程中只能传输字节类型的数据。

    #如果是英文和数字,直接使用b''
    data = b'xxxxx'
    
    #如果是中文字符,两种方式编码
    data = '你好'.encode('utf-8')
    data = bytes('你好', encoding='utf-8')
    
    #解码
    ret.decode('utf-8')
    编码和解码

     2、基于UDP协议的socket

    import socket
    sk = socket.socket(type=socket.SOCK_DGRAM)
    sk.bind(('127.0.0.1', 8080))
    
    msg, addr = sk.recvfrom(1024)
    sk.sendto(b'xxx')
    
    sk.close()
    server端
    import socket
    sk = socket.socket()
    ip_port = ('127.0.0.1', 8080)
    sk.sendto(b'xx', ip_port)
    sk.recvfrom(1024)
    sk.close()
    client端

    基于UDP的socket服务端不需要监听,也不能主动发消息,只能被动地等待客户端发送消息,获取客户端发送的信息和其地址,回复的时候也需要带上客户端地址。

    二、黏包现象

    现象:当发送消息时,接收方得到的消息混乱,和上一次没接收完的消息混在一起。

    只有TCP协议才会有黏包现象,UDP协议没有。TCP协议会将超出限制的部分在下一次的数据传输中获取,连接非常可靠,不会丢包,但是也导致消息没有边界,出现黏包问题。而UDP协议直接将超出的部分丢弃,不存在黏包问题,一次只接收一次的数据,但是会导致丢包。

    产生原因:

    服务端:1、TCP是长连接,每一次发送数据服务端都会进行一次确认,发送次数越多网络延迟越大,所以TCP有自己的优化算法,如果一次发送的数据非常小,会先进行缓存,在很短的时间内如果又发送数据,会将两次数据的包合并,一起发送,即连续多次send,这样减少了发送次数。2、当一次发送的数据太长超过其限制时,服务端会自己将其拆包发送,等于是发送了多次。

    客户端:1、客户端收到的数据都会放在缓存中,等待接收,如果第一次的数据还没有接收就又有数据发来,则多次数据粘在一起。2、如果一次的数据过长,超出接收的长度,TCP会在服务端将超出的部分缓存,等待下一次接收数据时再一起获取。

    黏包的解决方式:

    1、简陋的方式

    黏包的本质原因就是服务端不知道一次消息的长度是多少,导致不知道每次到底该接受多少,所以可以在发送数据前,先把数据的长度发送给服务端,服务端再接收固定长度的数据就可以避免黏包。

    import socket
    sk = socket.socket()
    sk.bind(('127.0.0.1', 8081))
    sk.listen()
    
    conn, addr = sk.accept()
    while True:
        cmd = input('>>>').encode('utf-8')
        conn.send(cmd)
        data_len = conn.recv(1024).decode('utf-8')
        conn.send(b'ok')
        data = conn.recv(int(data_len)).decode('gbk')
        print(data)
    
    conn.close()
    sk.close()
    server端
    import socket
    import subprocess
    
    sk = socket.socket()
    sk.connect(('127.0.0.1', 8081))
    
    while True:
        cmd = sk.recv(1024).decode('utf-8')
        ret = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        stdout = ret.stdout.read()
        stderr = ret.stderr.read()
        ret_len = len(stdout) + len(stderr)
        sk.send(str(ret_len).encode('utf-8'))
        ret = sk.recv(1024)
        sk.send(stdout)
        sk.send(stderr)
    
    sk.close()
    client端

    2、改进的方式

    方式一有一个缺陷,当发送数据长度时,服务端并不知道这个长度数据是多长,所以也只能定为接收1024个字节,然后还需要返回给客户端一个值,客户端接收后再发送数据,不然客户端还是连着发几个数据,一样会造成黏包,但是这样就多了一次网络延迟,性能较差。所以针对这种情况,可以使用struct模块,他可以帮助我们将任意长度的数字转换成固定长度的字节,这样第一次发送的长度数据是一个固定的值,服务端也接收固定的值,客户端就可以连着发送后面的数据,不需要双方进行确认来延迟了。

    import struct
    import socket
    sk = socket.socket()
    sk.bind(('127.0.0.1', 8081))
    sk.listen()
    
    conn, addr = sk.accept()
    while True:
        cmd = input('>>>').encode('utf-8')
        conn.send(cmd)
        data_len = struct.unpack('i',conn.recv(1024))[0]
        data = conn.recv(data_len).decode('gbk')
        print(data)
    
    conn.close()
    sk.close()
    server端
    import struct
    import socket
    import subprocess
    
    sk = socket.socket()
    sk.connect(('127.0.0.1', 8081))
    
    while True:
        cmd = sk.recv(1024).decode('utf-8')
        ret = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        stdout = ret.stdout.read()
        stderr = ret.stderr.read()
        ret_len = len(stdout) + len(stderr)
        sk.send(struct.pack('i', ret_len))
        sk.send(stdout)
        sk.send(stderr)
    
    sk.close()
    client端

    3、完善的方式

    在网络上传输的所有数据都叫做数据包,数据包里的数据都叫做报文,报文里除了数据还有IP、mac、端口号等,所有的报文都有报头,报头告诉另一方报文的长度是多少。我们可以自己定制报文和报头,将数据的长度等信息放在报头中。

    三、socketserver

     socket只能实现服务器在同一时刻与一个客户端进行通讯,socketserver提供了并发实现socket服务器的功能。

    import socketserver
    
    class MyServer(socketserver.BaseRequestHandler):
        def handle(self):
            ret = self.request.recv(1024).decode('utf-8')  #self.request就相当于是conn
            print(ret)
            self.request.send(b'hi')
    
    if __name__ == "__main__":
        socket = socketserver.ThreadingTCPServer(('127.0.0.1', 8080), MyServer)
        socket.serve_forever()
    server端
    import socket
    sk = socket.socket()
    sk.connect(('127.0.0.1', 8080))
    sk.send(b'hello')
    ret = sk.recv(1024).decode('utf-8')
    print(ret)
    sk.close()
    client端

    第三部分、websocket

    一、简介

    1、轮询

    客户端不断向服务端发送请求,服务端也不断的询问并回复,这种方式占用的资源太多,但是能保证数据实时性。

    2、长轮询

    客户端向服务端发送请求,服务端接收后在一段时间内去询问,如果有消息就返回,如果超时客户端再向服务端发起一次请求。这种方式节省了一定的资源浪费,但是数据实时性相对不好。

    3、websocket

    客户端与服务端建立长连接,不再断开,可以互相发送消息。

    建立websocket连接就是在基本的http请求中加入一些请求头,升级成websocket协议,之后收发的消息都会有加密解密的过程。

    二、websocket使用

    1、简单使用

    import json
    from flask import Flask, request, render_template
    from geventwebsocket.handler import WebSocketHandler
    from gevent.pywsgi import WSGIServer
    from geventwebsocket.websocket import WebSocket
    
    
    app = Flask(__name__)
    
    user_socket_dict = {}
    
    @app.route("/ws")
    def ws():
        user_socket = request.environ.get("wsgi.websocket")  # type:WebSocket
        print(user_socket)
        while 1:
            msg = user_socket.receive()
            print(json.loads(msg))
            user_socket.send('hello')
    
    
    @app.route("/")
    def index():
        return render_template("websocket.html")
    
    
    if __name__ == "__main__":
        http_serv = WSGIServer(('127.0.0.1', 5000), app, handler_class=WebSocketHandler)
        http_serv.serve_forever()
    服务端py
    <script type="text/javascript">
        var ws =  new WebSocket("ws://127.0.0.1:5000/ws");
    
        ws.onmessage = function(data){
            console.log(data);
            console.log(data.data);
        }
    </script>
    html

    2、群聊

    import json
    from flask import Flask, request, render_template
    from geventwebsocket.handler import WebSocketHandler
    from gevent.pywsgi import WSGIServer
    from geventwebsocket.websocket import WebSocket
    
    
    app = Flask(__name__)
    
    user_socket_list = []
    
    @app.route("/ws")
    def ws():
        user_socket = request.environ.get("wsgi.websocket")  # type:WebSocket
        if user_socket:
            user_socket_list.append(user_socket)
    
        while True:
            msg = user_socket.receive()
            for sk in user_socket_list:
                if sk == user_socket:
                    continue
                try:
                    sk.send(msg)
                except:
                    user_socket_list.remove(sk)
    
    @app.route("/")
    def index():
        return render_template("群聊.html")
    
    
    if __name__ == "__main__":
        http_serv = WSGIServer(('127.0.0.1', 5000), app, handler_class=WebSocketHandler)
        http_serv.serve_forever()
    服务端py
    请输入:<input type="text" id="msg"><button onclick="send_msg()">发送</button>
    <div id="msg_list"></div>
    
    <script type="text/javascript">
        var ws =  new WebSocket("ws://127.0.0.1:5000/ws");
    
        ws.onmessage = function(data){
          var tag = document.createElement("div");
          tag.innerText = data.data;
          document.getElementById("msg_list").appendChild(tag);
        };
    
        function send_msg(){
            var msg = document.getElementById("msg").value;
            var tag = document.createElement("div");
            tag.innerText = msg;
            document.getElementById("msg_list").appendChild(tag);
            document.getElementById("msg").value = "";
            ws.send(msg)
        }
    
    </script>
    html

    3、单聊

    import json
    from flask import Flask, request, render_template
    from geventwebsocket.handler import WebSocketHandler
    from gevent.pywsgi import WSGIServer
    from geventwebsocket.websocket import WebSocket
    
    
    app = Flask(__name__)
    
    user_socket_dict = {}
    
    @app.route("/ws/<username>")
    def ws(username):
        user_socket = request.environ.get("wsgi.websocket")  # type:WebSocket
        if user_socket:
            user_socket_dict[username] = user_socket
        while True:
            msg = user_socket.receive()  # {"from_user": "xxx", "to_user": "xxx", "msg": "xxx"}
            msg_dict = json.loads(msg)
            to_socket = user_socket_dict.get(msg_dict.get("to_user"))
            to_socket.send(msg)
    
    
    @app.route("/")
    def index():
        return render_template("单聊.html")
    
    
    if __name__ == "__main__":
        http_serv = WSGIServer(('127.0.0.1', 5000), app, handler_class=WebSocketHandler)
        http_serv.serve_forever()
    服务端py
    <div>我:<input id="from_user"><button onclick="connect()">建立连接</button></div>
    <div>发送给:<input id="to_user"></div>
    <div>消息:<input id="msg"><button onclick="send_msg()">发送</button></div>
    
    <div id="msg_list"></div>
    
    <script type="text/javascript">
        var ws = null;
    
        function connect(){
            var from_user = document.getElementById("from_user").value;
            ws = new WebSocket("ws://127.0.0.1:5000/ws/" + from_user);
            ws.onmessage = function(data){
                var msg_obj = JSON.parse(data.data);
                var tag = document.createElement("div")
                tag.innerText = msg_obj.from_user + "" + msg_obj.msg;
                document.getElementById("msg_list").appendChild(tag);
            }
        }
    
        function send_msg(){
            var from_user = document.getElementById("from_user").value;
            var to_user = document.getElementById("to_user").value;
            var msg = document.getElementById("msg").value;
            var msg_obj = {from_user:from_user, to_user:to_user, msg:msg};
    
            var tag = document.createElement("div");
            tag.innerText = "我:" + msg;
            document.getElementById("msg_list").appendChild(tag);
    
            ws.send(JSON.stringify(msg_obj));
        }
    
    </script>
    html
  • 相关阅读:
    linux基础(一)
    网络基础之网络协议篇
    操作系统简介
    计算机组成原理
    【C#】=>符号的使用
    【Unity3D】用继承EditorUpdater类来实现Editor模式下的后台处理
    【Unity3D】Tags和Layers
    【Unity3D】Unity3D中Material与ShareMaterial引用的区别
    【Unity3D】Unity中用C#读取CSV文件
    【Unity3D】用C#读取INI配置文件
  • 原文地址:https://www.cnblogs.com/yinwenjie/p/10856749.html
Copyright © 2020-2023  润新知