机器学习Python语言与信息数据获取和机器学习我的Python自学之路

深入浅出谈socket

2017-02-20  本文已影响129人  凉茶半盏

*现在我们开发往往不断使用封装好的web框架, 运行web服务也有相当多的容器, 但是其原理往往都离不开socket. 像是nginx底层就是采用类似python中epoll的异步监听方式加上socket结合来做. * 本文采取从最简单的socket通信实现聊天机器人, 到伪并发实现聊天机器人, 最后采用异步监听方式实现聊天机器人, 逐步推进.

首先我们实现一个最简单版的的socket服务端, server_s1.py

#!/usr/bin/env python
# -*- coding:utf-8 -*-
import socket

HOST='127.0.0.1'
PORT=9999

sockaddr=(HOST,PORT)
sk=socket.socket()
sk.bind(sockaddr)
sk.listen(5)
conn,address=sk.accept()
ret_bytes=conn.recv(1024)
print(str(ret_bytes,encoding='utf-8'))
conn.sendall(ret_bytes+bytes(', 已收到!',encoding='utf-8'))
sk.close()

至此简单的服务端已经写好了, 我们看看客户端, client_c1.py

#!/usr/bin/env python
# -*- coding:utf-8 -*-
import socket

HOST='127.0.0.1'
PORT=9999

sockaddr=(HOST,PORT)
ct=socket.socket()
ct.connect(sockaddr)
ct.sendall(bytes('第一次连接',encoding='utf-8'))
ret_bytes=ct.recv(1024)
print(str(ret_bytes,encoding='utf-8'))
ct.close()

到现在为止, 已经把简单聊天机器人已经写好了, 客户端向服务端发送第一次连接 , 服务端接受输出到客户端并回馈给客户端第一次连接, 已收到! 接下来我们试着让这个服务端更健壮一些, 尝试让它可以不断的返回客户端发送过来的内容

这是第二个版本的服务端, server_s2.py

#!/usr/bin/env python
# -*- coding:utf-8 -*-
import socket

HOST='127.0.0.1'
PORT=9999

sockaddr=(HOST,PORT)
sk=socket.socket()
sk.bind(sockaddr)
sk.listen(5)
while True:
    conn,address=sk.accept()
    while True:
        try:
            ret_bytes=conn.recv(1024)
        except Exception as ex:
            print("已从",address,"断开")
            break
        else:
            conn.sendall(ret_bytes+bytes(', 已收到!',encoding='utf-8'))
sk.close()

接下来看看客户端文件, client_c2.py

#!/usr/bin/env python
# -*- coding:utf-8 -*-
import socket

HOST='127.0.0.1'
PORT=9999

sockaddr=(HOST,PORT)
ct=socket.socket()
ct.connect(sockaddr)
while True:
    inp=input("请输入要发送的内容: ")
    ct.sendall(bytes(inp,encoding='utf-8'))
    ret_bytes=ct.recv(1024)
    print(str(ret_bytes,encoding='utf-8'))
ct.close()

现在第二个版本已经可以连续不断的处理同一连接的消息, 即使断开也不会影响服务器的健壮性. 但是, 我们的服务器功能还很单一, 只能一次处理一个客户端的连接. 接下来将用select模块实现伪并发处理客户端连接

这里是第三个版本的服务端文件, server_s3.py

#!/usr/bin/env python
# -*- coding:utf-8 -*-
import socket
import select

HOST = '127.0.0.1'
PORT = 9999

sockaddr = (HOST, PORT)

sk = socket.socket()
sk.bind(sockaddr)
sk.listen(5)

sk_inps = [sk, ]

while True:
    change_list, keep_list, error_list = select.select(sk_inps, [], sk_inps, 1)
    for sk_tmp in change_list:
        if sk_tmp == sk:
            conn, address = sk_tmp.accept()
            sk_inps.append(conn)
        else:
            try:
                ret_bytes = sk_tmp.recv(1024)
            except Exception as ex:
                sk_inps.remove(sk_tmp)
                print("已从", sk_tmp.getpeername(), "断开")
            else:
                sk_tmp.sendall(ret_bytes + bytes(', 已收到!', encoding='utf-8'))

    for sk_tmp in error_list:
        sk_inps.remove(sk_tmp)

sk.close()

我们首先来看一下循环的过程

循环过程

该版本的客户端延续上一版本即可, 无需更改. 至此, 我们就建立一个能并发简单处理多客户端连接的服务器. 但是, 对于change_list 中遍历时候我们既有读又有写的操作, 这样当后期的处理复杂的时候, 代码维护很难再进行下去. 接下来我们接着开发我们的伪并发处理的最终版本

这里是服务的文件, server_s4.py

#!/usr/bin/env python
# -*- coding:utf-8 -*-
import socket
import select

HOST = '127.0.0.1'
PORT = 9997

sockaddr = (HOST, PORT)

sk = socket.socket()
sk.bind(sockaddr)
sk.listen(5)

sk_inps = [sk, ]
sk_outs=[]
message_dic={}

while True:
    change_list, keep_list, error_list = select.select(sk_inps, sk_outs, sk_inps, 1)
    for sk_tmp in change_list:
        if sk_tmp == sk:
            conn, address = sk_tmp.accept()
            sk_inps.append(conn)
            message_dic[conn]=[]
        else:
            try:
                ret_bytes = sk_tmp.recv(1024)
            except Exception as ex:
                sk_inps.remove(sk_tmp)
                print("已从", sk_tmp.getpeername(), "断开")
                del message_dic[sk_tmp]
            else:
                sk_outs.append(sk_tmp)
                message_dic[sk_tmp].append(str(ret_bytes,encoding='utf-8'))

    for conn in keep_list:
        message= message_dic[conn][0]
        conn.sendall(bytes(message+", 已收到!",encoding='utf-8'))
        del message_dic[conn][0]
        sk_outs.remove(conn)

    for sk_tmp in error_list:
        sk_inps.remove(sk_tmp)

sk.close()

以上就是伪并发处理客户端请求所有内容, 究其本质其实是IO多路复用原理. 同时python中也提供了真正的并发处理模块socketserver, 下面我们采用socketserver来实现

首先看我们的服务端文件, server_s5.py

#!/usr/bin/env python
# -*- coding:utf-8 -*-
import socketserver

HOST = '127.0.0.1'
PORT = 9997

sockaddr = (HOST, PORT)

class MySocket(socketserver.BaseRequestHandler):
    def handle(self):
        conn = self.request
        while True:
            try:
                ret_bytes = conn.recv(1024)
            except Exception as ex:
                print("已从", self.client_address, "断开")
                break
            else:
                conn.sendall(ret_bytes + bytes(', 已收到!', encoding='utf-8'))


if __name__ == "__main__":
    server = socketserver.ThreadingTCPServer(sockaddr, MySocket)
    server.serve_forever()

以上我们将Socket从基础原理到复杂自定义已经使用封装好的模块使用介绍完毕. 接下来我们补充一些理论知识和常用的Socket参数和方法: **
首先我们来回顾一下OSI模型和TCP/IP协议簇,如图(
图片引自网络
)

OSI模型与TCP/IP协议簇
每层都有相对应的协议,但是socket API只是操作系统提供的一个用于网络编程的接口, 如图(图片引自网络) socket与各层关系

根据 socket 传输数据方式的不同(其实就是使用协议的不同), 导致其与不同层打交道

以下是注意点:

上一篇 下一篇

猜你喜欢

热点阅读