web服务前言

HTTP(hypertext transport protocol),即超文本传输协议。这个协议详细规定了浏览器和万维网服务器之间互相通信的规则。
特点:

  1. HTTP叫超文本传输协议,基于请求/响应模式的!
  2. HTTP是无状态协议。

为了方便认识http的请求和响应协议,用一段py来抓包分析下

import socket
import time
def handle_request(client):
    time.sleep(10)
    buf = client.recv(1024)
    print(buf.decode('utf-8'))
    client.send(bytes("HTTP/1.1 200 OK\r\n\r\n",encoding='utf-8'))
    client.send(bytes('Hello World', encoding='utf-8'))
def main():
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    sock.bind(('192.168.1.102', 8000))
    sock.listen(2)

    while True:
        connection, address = sock.accept()
        handle_request(connection)
        connection.close()
if __name__ == '__main__':
    main()

请求协议

get请求协议的格式如下:

请求首行; // 请求方式 请求路径 协议和版本,例如:GET /index.html HTTP/1.1
请求头信息;// 请求头名称:请求头内容,即为key:value格式,例如:Host:localhost
空行; // 用来与请求体分隔开
请求体。 // GET没有请求体,只有POST有请求体。

get抓包分析

  • GET / HTTP/1.1
    #请求主机ip:port
  • Host: 192.168.1.102:8000
    #客户端支持的链接方式,保持一段时间链接
  • Connection: keep-alive
    #表明客户端不愿意接受缓存请求,它需要的是最即时的资源。
  • Pragma: no-cache
    #没有缓存
  • Cache-Control: no-cache
    #更加支持用https
  • Upgrade-Insecure-Requests: 1
    #与浏览器和OS相关的信息。有些网站会显示用户的系统版本和浏览器版本信息,这都是通过获取User-Agent头信息而来的;
  • User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36
  • Accept:
    #告诉服务器,当前客户端可以接收的文档类型,其实这里包含了/,就表示什么都可以接收;
  • text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,/;q=0.8
    #告诉服务器,当前客户端可以接收的文档类型,其实这里包含了/,就表示什么都可以接收;
  • Accept-Encoding: gzip, deflate
    #当前客户端支持的语言,可以在浏览器的工具选项中找到语言相关信息;
  • Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,cy;q=0.7
get请求特点:
  1. 没有请求体
  2. 数据必须在1K之内!
  3. GET请求数据会暴露在浏览器的地址栏中
post请求
#python 发送一个post请求
import requests

test = {'key1': 'value1', 'key2': 'value2'}
ret = requests.post("http://192.168.1.102:8000", data=test)
print(ret)
print(ret.text)

抓包分析

#post请求

  • POST / HTTP/1.1
  • Host: 192.168.1.102:8000
  • User-Agent: python-requests/2.18.4
  • Accept-Encoding: gzip, deflate
  • Accept: /
  • Connection: keep-alive

#请求body长度

  • Content-Length: 23
  • Content-Type: application/x-www-form-urlencoded

#请求体

  • key1=value1&key2=value2
post请求特点
  1. 数据不会出现在地址栏中
  2. 数据的大小没有上限
  3. 有请求体
  4. 请求体中如果存在中文,会使用URL编码!

响应

  • HTTP/1.1 200 OK
    Hello World

响应协议的格式如下:

响应首行;
响应头信息;
空行;
响应体。

响应协议说明

HTTP/1.1 200 OK:响应协议为HTTP1.1,状态码为200,表示请求成功,OK是对状态码的解释;
Server:WSGIServer/0.2 CPython/3.5.2:服务器的版本信息;
Content-Type: text/html;charset=UTF-8:响应体使用的编码为UTF-8;
Content-Length: 724:响应体为724字节;
Set-Cookie: JSESSIONID=C97E2B4C55553EAB46079A4F263435A4; Path=/hello:响应给客户端的Cookie;
Date: Wed, 25 Sep 2012 04:15:03 GMT:响应的时间,这可能会有8小时的时区差;


tcp三次握手四次断开说明

web服务前言

服务端代码

import socket

server = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
back_log = 5
buffer_size = 1024
ip_port = ('192.168.1.102', 8000)
server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
server.bind(ip_port)

server.listen(back_log)
print('waiting.....')

doing = 1
while doing > 0:
    conn, addr = server.accept()

    while doing > 0:
        data = conn.recv(buffer_size).decode('utf-8')
        if not data :
            break
        print('recv:',data)
        print('test:')
        if data == 'exit':
            conn.close()
            break
        if data == 'exitall':
            doing=0
            break
        else:
            if data:
                conn.send(data.upper().encode('utf-8'))

server.close()

客户端代码

import socket

client = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
buffer_size = 1024
ip_port = ('192.168.1.102', 8000)

client.connect(ip_port)
while True:
    msg = input('input>>>\n').strip()
    if not msg:
        continue
    client.send(bytes(msg,encoding='utf-8'))       
    data = client.recv(buffer_size).decode(encoding='utf-8')
    print(data)
    if msg == 'exit':
        break

client.close()

1、开启一个监听在8000端口的tpc进程

[root@ns1 conf.d]# ss -antp|grep 8000
LISTEN     0(当前等待的已经是ESTAB的连接数量,排除正在通信的)      5(表示接收连接最大数)      192.168.1.102:8000                     *:*                   users:(("python3.6",pid=29528,fd=3))

我们用watch来看服务器的连接情况
2、连接一个客户端,进程观察
其实这个阶段有很多状态变化,服务端会从SYN_RECV到ESTAB

Every 1.0s: ss -antp|grep 8000                                                                                          Sat May 12 10:36:44 2018

LISTEN     0      5  192.168.1.102:8000                     *:*                   users:(("python3.6",pid=39882,fd=3))
ESTAB      0      0  192.168.1.102:8000               192.168.1.101:58132               users:(("python3.6",pid=39882,fd=4))

2.1断开连接,进程观察
客户端断开,TIME-WAIT是等待服务器断开

Every 1.0s: ss -antp|grep 8000                                                                                          Sat May 12 10:41:39 2018

LISTEN     0      5  192.168.1.102:8000                     *:*                   users:(("python3.6",pid=41041,fd=3))
TIME-WAIT  0      0  192.168.1.102:8000               192.168.1.101:59438

几秒之后客户端就确认断开了 这里为什么会有延迟,是因为有可能服务器端发送到客户端的数据还没有发完,所以服务器要确认

Every 1.0s: ss -antp|grep 8000                                                                                          Sat May 12 10:43:50 2018

LISTEN     0      5  192.168.1.102:8000                     *:*                   users:(("python3.6",pid=41041,fd=3))

这里还有一种情况,出现close-wait,这种情况是因为客户端断开连接,而服务器还连接着到客户端的连接,有可能是服务器的bug(正常情况这个阶段是很快被服务器确认的)

Every 1.0s: ss -antp|grep 8000                                                                                          Sat May 12 10:47:46 2018

LISTEN     0      5  192.168.1.102:8000                     *:*                   users:(("python3.6",pid=41041,fd=3))
CLOSE-WAIT 0      0  192.168.1.102:8000               192.168.1.101:61056               users:(("python3.6",pid=41041,fd=4))

另外一组测试,测试多个连接
3、先连接3个客户端,进程观察
一共3个ESTAB连接 1给非阻塞,2个阻塞

Every 1.0s: ss -antp|grep 8000                                                                                          Sat May 12 10:51:28 2018

LISTEN     2      5  192.168.1.102:8000                     *:*                   users:(("python3.6",pid=43041,fd=3))
ESTAB      0      0  192.168.1.102:8000               192.168.1.101:62088
ESTAB      0      0  192.168.1.102:8000               192.168.1.101:62079
ESTAB      0      0  192.168.1.102:8000               192.168.1.101:62070               users:(("python3.6",pid=43041,fd=4))

3.1 在连接3个客户端,一共6个,进程观察
6个建立ESTAB连接,1个非阻塞,5个阻塞

Every 1.0s: ss -antp|grep 8000                                                                                          Sat May 12 10:52:02 2018

LISTEN     5      5  192.168.1.102:8000                     *:*                   users:(("python3.6",pid=43041,fd=3))
ESTAB      0      0  192.168.1.102:8000               192.168.1.101:62249
ESTAB      0      0  192.168.1.102:8000               192.168.1.101:62088
ESTAB      0      0  192.168.1.102:8000               192.168.1.101:62233
ESTAB      0      0  192.168.1.102:8000               192.168.1.101:62242
ESTAB      0      0  192.168.1.102:8000               192.168.1.101:62079
ESTAB      0      0  192.168.1.102:8000               192.168.1.101:62070               users:(("python3.6",pid=43041,fd=4))

3.2 继续添加3个客户端,一共9个连接 ,进程观察
一共9个连接,1个非阻塞ESTAB,6个阻塞ESTAB,2个等待服务器回应SYN-RECV

Every 1.0s: ss -antp|grep 8000                                                                                          Sat May 12 10:52:24 2018

LISTEN     6      5  192.168.1.102:8000                     *:*                   users:(("python3.6",pid=43041,fd=3))
SYN-RECV   0      0  192.168.1.102%if355331399:8000               192.168.1.101:62369
SYN-RECV   0      0  192.168.1.102:8000               192.168.1.101:62364
ESTAB      0      0  192.168.1.102:8000               192.168.1.101:62249
ESTAB      0      0  192.168.1.102:8000               192.168.1.101:62088
ESTAB      0      0  192.168.1.102:8000               192.168.1.101:62233
ESTAB      0      0  192.168.1.102:8000               192.168.1.101:62355
ESTAB      0      0  192.168.1.102:8000               192.168.1.101:62242
ESTAB      0      0  192.168.1.102:8000               192.168.1.101:62079
ESTAB      0      0  192.168.1.102:8000               192.168.1.101:62070               users:(("python3.6",pid=43041,fd=4))

几秒之后,服务器一直没有响应客户端的请求,服务器断开连接

Every 1.0s: ss -antp|grep 8000                                                                                          Sat May 12 10:53:33 2018

LISTEN     6      5  192.168.1.102:8000                     *:*                   users:(("python3.6",pid=43041,fd=3))
ESTAB      0      0  192.168.1.102:8000               192.168.1.101:62249
ESTAB      0      0  192.168.1.102:8000               192.168.1.101:62088
ESTAB      0      0  192.168.1.102:8000               192.168.1.101:62233
ESTAB      0      0  192.168.1.102:8000               192.168.1.101:62355
ESTAB      0      0  192.168.1.102:8000               192.168.1.101:62242
ESTAB      0      0  192.168.1.102:8000               192.168.1.101:62079
ESTAB      0      0  192.168.1.102:8000               192.168.1.101:62070               users:(("python3.6",pid=43041,fd=4))

3.3 客户端断开第一个连接,就是非阻塞那个, 进行观察
1个等待服务器确认断开,1个非阻塞,5个阻塞。之前的62070非阻塞断开,第二个阻塞状态转为非阻塞

Every 1.0s: ss -antp|grep 8000                                                                                          Sat May 12 10:54:25 2018

LISTEN     5      5  192.168.1.102:8000                     *:*                   users:(("python3.6",pid=43041,fd=3))
ESTAB      0      0  192.168.1.102:8000               192.168.1.101:62249
ESTAB      0      0  192.168.1.102:8000               192.168.1.101:62088
ESTAB      0      0  192.168.1.102:8000               192.168.1.101:62233
ESTAB      0      0  192.168.1.102:8000               192.168.1.101:62355
ESTAB      0      0  192.168.1.102:8000               192.168.1.101:62242
ESTAB      0      0  192.168.1.102:8000               192.168.1.101:62079               users:(("python3.6",pid=43041,fd=4))
TIME-WAIT  0      0  192.168.1.102:8000               192.168.1.101:62070

3.4继续断开2个连接,观察
2个等待确认断开,1个非阻塞,3个阻塞

Every 1.0s: ss -antp|grep 8000                                                                                          Sat May 12 10:56:25 2018

LISTEN     3      5  192.168.1.102:8000                     *:*                   users:(("python3.6",pid=43041,fd=3))
ESTAB      0      0  192.168.1.102:8000               192.168.1.101:62249
TIME-WAIT  0      0  192.168.1.102:8000               192.168.1.101:62088
ESTAB      0      0  192.168.1.102:8000               192.168.1.101:62233               users:(("python3.6",pid=43041,fd=4))
ESTAB      4      0  192.168.1.102:8000               192.168.1.101:62355
ESTAB      0      0  192.168.1.102:8000               192.168.1.101:62242
TIME-WAIT  0      0  192.168.1.102:8000               192.168.1.101:62079

  • 由此分析三次握手四次断开:
    1、在三次握手中,客户端先发起请求,syn seq请求信号
    2、服务器接收到信号转为SYN-RECV
    3、服务器查看半连接池,如果还有数量可以接收,就给客户端确认,并且发出服务器请求信号,状态还是SYN-RECV
    4、客户端接收到服务器的同意,建立客户端到服务器的连接(DDOS***,这个阶段不给确认),客户端同意服务器的连接,然后确认信息
    5、服务器接收到客户端的确认,建立服务器到客户端连接,状态由SYN-RECV到ESTAB
    6、三次握手成功,期间双方可以友好交流,每次交流都是发出请求,确认收到。
    7、客户端发送断开请求,状态转为close_wait
    8、服务器确认同意,客户端到服务器的请求断开
    9、服务器确认可以关闭请求,发送请求
    10、客户端确认,断开服务器到客户端连接

常见IO模型介绍

介绍IO模型因为配置web服务高并发,提供一些基础理解。
还有进程线程的一些概念,大概就是进程之前数据相互独立,切换进程比切换线程消耗大很多,线程是能共享进程资源,是一串指令的集合,这里不做详细解释了

1、阻塞IO 上述服务端socekt就是阻塞io

web服务前言

2、非阻塞IO

web服务前言

import socket
import time

server = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
buffer_size = 1024
ip_port = ('127.0.0.1', 8000)
server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
server.bind(ip_port)

server.listen(5)
server.setblocking(False)   #非阻塞
print('waiting.....')

doing = 1
while doing > 0:

    try:
        conn, addr = server.accept()
        while doing > 0:
            try:
                data = conn.recv(buffer_size).decode('utf-8') 
                if not data :
                    break
                print('recv:',data)
                print('test:')
                if data == 'exit':
                    conn.close()
                 if data == 'exitall':
                    doing = 0
                    break
                else:
                    if data:
                        conn.send(data.upper().encode('utf-8'))
            except Exception as e:
                time.sleep(1)
                print(e)
    except Exception as e:
        time.sleep(1)
        print(e)

server.close()
3、 IO 多路复用-select

用户进程调用select,内核负责所有select添加的sokect的状态,当任何一个socket中的数据准备好了,select就会返回,返回所有select添加的socket。这个时候用户进程再调用read操作,将数据从kernel拷贝到用户进程。IO多路复select在按找时间空间划分cpu,就已经实现来单线程高并发。缺点也很明显,水平出发消耗比较高,非活跃socket本因无须操作,select还依赖文件描述符,每台服务器能打开的文件描述符资源也是有限的。
web服务前言
服务端代码

import socket
import select
sk=socket.socket()
sk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
sk.bind(("127.0.0.1",8008))
sk.listen(5)
inputs=[sk,]
while True:
    r, w, e = select.select(inputs, [], [], 5)

    for i in r:
        if i is sk:  # 看上述r描述
            conn, add = i.accept()  # 把r进来的socket接收到  不接收会一直存在内核
            inputs.append(conn)
        else:
            data_byte = i.recv(1024)
            print(str(data_byte, 'utf-8'))
            inp = input('回答%s号客户>>>' % inputs.index(i))
            i.sendall(bytes(inp, 'utf-8'))

    print('>>>>>>')

客户端代码

import socket

sk=socket.socket()

sk.connect(("127.0.0.1",8000))

while 1:
    inp=input(">>").strip()
    sk.send(inp.encode("utf8"))
    data=sk.recv(1024)
    print(data.decode("utf8"))
IO多路复用-poll-epoll

poll 本质跟select区别不大,只是取消来文件描述符的限制
epoll在linux内核2.6之后出现,同时支持水平触发跟边缘触发,边缘触发意思就是返回文件描述符发生变化的socket,大大减少来服务器的开销,内核复制数据到用户空间使用mmap内存映射技术,省掉了文件描述符在系统调用时复制的开销,另一个本质的改进在于epoll采用基于事件的就绪通知方式,一旦基于某个文件描述符就绪时,内核会采用类似callback的回调机制,迅速激活这个文件描述符。
事件通知方式实现方式

from concurrent.futures import ProcessPoolExecutor
import requests

def task(url):
    response = requests.get(url)
    return response

def done(future,*args,**kwargs):
    response = future.result()
    print(response.status_code,response.content)

pool = ProcessPoolExecutor(7)
url_list = [
    'https://www.taobao.com',
    'https://www.sina.com',
    'https://www.baidu.com',
]
for url in url_list:
    print(url)
    v = pool.submit(task,url)
    v.add_done_callback(done)
print('all')
pool.shutdown(wait=True)

epoll的实现在python中特别简单,selectors模块的EpollSelector
服务端

import selectors
import socket

sel = selectors.EpollSelector()

def accept(sock, mask):
    conn, addr = sock.accept()  # Should be ready
    print('accepted', conn, 'from', addr)
    conn.setblocking(False)
    sel.register(conn, selectors.EVENT_READ, read)

def read(conn, mask):
    data = conn.recv(1000)  # Should be ready
    if data:
        print('echoing', repr(data), 'to', conn)
        conn.send(data)  # Hope it won't block
    else:
        print('closing', conn)
        sel.unregister(conn)
        conn.close()

sock = socket.socket()
sock.bind(('localhost', 1234))
sock.listen(100)
sock.setblocking(False)
sel.register(sock, selectors.EVENT_READ, accept)

while True:
    events = sel.select()
    for key, mask in events:
        callback = key.data
        callback(key.fileobj, mask)

客户端

import socket
client = socket.socket()

client.connect(('localhost', 9000))

while True:
    cmd = input('>>> ').strip()
    if len(cmd) == 0 : continue
    client.send(cmd.encode('utf-8'))
    data = client.recv(1024)
    print(data.decode())

client.close()

转载于:https://blog.51cto.com/marvin89/2115474

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值