美文网首页
(1-2)socket -- sever

(1-2)socket -- sever

作者: 马梦里 | 来源:发表于2017-12-12 15:50 被阅读0次

一、

两层循环:一个是接收连接,一个是接收请求
逻辑:

  1. 实例化、绑定服务器和端口、监听
  2. 无限循环,保持接收状态
  3. 接收到请求后,解析请求,拿到路径
  4. 根据路径找到对应的视图函数,视图函数会返回响应
  5. 发送响应,断开连接
import socket

s = socket.socket()
host = '0.0.0.0'
port = 3000

s.bind((host, port))
s.listen()

while True:
    log('before accept')
    connection, address = s.accept()
    print(connection)
    print('address', address)
    print('after accept')

    request = b''
    buffer_size = 1024
    while True
        r = connection().rev(buffer_size)
        request += r 
        if len(r) < buffer_size:
            break
    print('ip and request, {}\n{}'.format(address, request.decode()))
    response = b'HTTP/1.1 233 VERY OK\r\nMJ:mamengli\r\n\r\n<h1>Hello World</h1>'
    connection().sendall(response)
    connection.close()
  1. 实例化socket
  2. 绑定ip和端口,并进行监听
    0.0.0.0表示任意ip都可以访问此服务器,访问的ip必须是3000
  3. 这是一个无限循环,表示一直接收客户端发过来的连接请求。
    accept()是一个阻断函数,当有客户端请求的时候,它会判断连接状态,然后往下走。如果没有请求连接,那么它会一直停留在这里。
    这就是socket的第一步:建立连接 图片.png
  4. 接收请求数据
    这又是一个无限循环,表示一直接收客户端发过来的数据(每次接收1024),直到接收完。
    这里request = b' ',如果把b 去掉,那么: 图片.png 表明客户端通过网络发送过来的数据是bytes类型,通过b‘ ’将其转换为str
    requestrequest.decode()的区别:
类型转换.png
  1. 发送响应
    利用sendall()函数发送请求,这里的放松给网络传播的数据必须是bytes类型。发送完数据后,关闭连接。

小知识点:

  1. 字符串的format()方法,实现字符串的格式化,使用方式:
    'name:{}, age:{}'.format('majun', 27)
    替代了%s %
  2. break:表示跳出当前循环(最底层的),还可以继续执 行外层循环,如果没有,那么循环结束。
    continue:跳出循环,继续执行循环。
  3. 数据类型的转换
    在客户端,数据在发送之前需要encode(),也就是将字符串转换为bytes类型。
    在服务器端,接收数据的格式也必须是bytes类型,不然格式不对称,所以有个b
    服务器端响应的时候,加上了一个b,表示发送的是字节类型。
    由此可知,encode()放和b的作用一样。
    由上图(类型转换.png)可以看到,request经过decode()后,变成了字符串类型。注意,第一个request前面有个b
    可见:
    encode()将字符串转换为bytes
    decode()bytes转换为str

二、

import socket

def log(*args, **kwargs):
    print('log', *args, **kwargs)

def route_index():
    header = 'HTTP/1.1 233 OK\r\nContent-Type: text/html\r\n'
    body = '<h1>Hello World</h1><img src="doge.gif"/>'
    r = '{}\r\n{}'.format(header, body)
    return r.encode()

def html_content(path):
    with open(path, encoding='utf-8') as f:
        return f.read()

def route_message():
    # 主页的处理函数, 返回主页的响应
    header = 'HTTP/1.1 233 OK\r\nContent-Type: text/html\r\n'
    body = html_content('html_basic.html')
    r = '{}\r\n{}'.format(header, body)
    return r.encode()

def route_image():
    # 图片的处理函数, 读取图片并生成响应返回,二进制方式读取
    with open('doge.gif', 'rb') as f:
        header = b'HTTP/1.1 200 OK\r\nContent-Type: image/gif\r\n\r\n'
        // 拼接的时候,数据类型要一直
        image = header + f.read()
        return image

def error(code=404):
    # 根据 code 返回不同的错误响应,目前只有 404
    e = {
        404: b'HTTP/1.1 404 NOT FOUND\r\nContent-Type: text/html\r\n\r\n<h1>NOT FOUND</h1>',
    }
    return e.get(code, b'')

//  表驱动法
def response_for_path(path):
    # 根据 path 调用相应的处理函数,没有处理的 path 会返回 404
    # 函数当做参数传递,不需要括号
    r = {
        '/': route_index,
        '/message': route_message,
        '/doge.gif': route_image,
    }
    response = r.get(path, error)
    # 调用函数的时候,需要加()
    return response()

def run(host, port):
    #  with 可以保证程序中断的时候正确关闭 socket 释放占用的端口
    with socket.socket() as s:
        s.bind((host, port))
        s.listen()
        # server 用 connection 来接收请求和发送响应
        while True:
            # accept 为阻断函数
            connection, address = s.accept()
            # 这里只读取了 1024 字节的内容, 应该用一个循环全部读取
            request = connection.recv(1000)
            request = request.decode()
            # 因为 chrome 会发送空请求导致 split 得到空 list
            # 所以这里先判断一下 split 得到的数据的长度
            parts = request.split()
            log('parts', parts)

            if len(parts) > 0:
                path = parts[1]
                # 用 response_for_path 函数来得到 path 对应的响应内容
                response = response_for_path(path)
                # 把响应发送给客户端
                connection.sendall(response)
            else:
                log("接收到了一个空请求")
            connection.close()

if __name__ == '__main__':
    config = dict(
        host='0.0.0.0',
        port=2000,
    )
    # 等价于:run(host = '0.0.0.0',port = 3000)
    run(**config)

函数当做参数传递的时候不需要写括号和参数,有括号的时候表示 return 的结果

  1. GET / POST
    端口直接和host在一起;
    POST方法提交的数据储存在body中;
    GRT提交的数据在请求头;
POST / HTTP/1.1
Host: localhost:3000
Connection: keep-alive

author=gua&message=hello

GET /?message=&author=gua HTTP/1.1
Host: localhost:3000
Connection: keep-alive

2. with open
读写文件前,我们先必须了解一下,在磁盘上读写文件的功能都是由操作系统提供的,现代操作系统不允许普通的程序直接操作磁盘,所以,读写文件就是请求操作系统打开一个文件对象(通常称为文件描述符),然后,通过操作系统提供的接口从这个文件对象中读取数据(读文件),或者把数据写入这个文件对象(写文件)。
3. 读文件
// 先打开文件,with自动调用close()方法,出错时,自动关闭
// mode 为读文件的模式,r 表示读

with open('/path/filename', 'mode') as f:
    // 接下来就是读文件,`read()`方法一次性读取所有文件
    // `python` 把文件读到内存,用一个`str`对象(字符串)表示
    f.read()

如果文件很小,一次性读取很方便,如果文件很多,而且是配置文件的时候,就要用到readlines()方法了:

for line in f.readlines()
    // `strip()` 方法可以去掉空格和行末的 `\r\n`
    print(line.strip())

4. 写文件
写文件和上面一样,只不过 r 改为 wf.read() 改为 f.write()
rbwb 表示以二进制方法读写;
open函数的其他参数:
要读取非UTF-8编码的文件时,比如:gbk,传入参数 encoding = 'gbk',如果文件中有一些非法编码的字符:errors = 'ignore'可以解决;
5. 异常处理:try except slse:
try的工作原理是,当开始一个try语句后,python就在当前程序的上下文中作标记,这样当异常出现时就可以回到这里,try子句先执行,接下来会发生什么依赖于执行时是否出现异常。
try语句将可能发生错误的代码包住,except用来捕获被包住代码可能发生的异常,如果异常匹配,则执行except里面的代码,如果没有异常,执行完try里面后的代码后,再执行else里面的代码;


client 是用 s 来操作:s = socket.socket()
s.connect((host, port)) s.send(request) s.recv(response)
server 是用 connection 来操作
connection, address = s.accept()
connection.recv(request) connection.sendall(response)
connection.close()

相关文章

网友评论

      本文标题:(1-2)socket -- sever

      本文链接:https://www.haomeiwen.com/subject/wlexixtx.html