第十章 python socker编程

10.1 弄懂HTTP、Socker、TCP这几个概念

整个计算机网络都是由协议组成的。

image-20200922101541768

A是client端,B是服务器端,当A向B发送请求时,数据传输的时候(平时使用requests、urllib请求数据),在我们操作系统和网络之中要经历5层网络模型。

应用层:和具体的包相关,如果是requests,则走http协议,如果是一些邮件则走SMTP协议,如果用的FTP的包,则走FTP协议。

传输层:

网络层:从一个ip到另一个ip时,网络该如何发包

数据链路层:

物理层:

A传送数据是从上到下的(拆包),B接受数据是从下到上的(组包)。

浏览器也是实现了http协议,http协议是构建与TCP、IP...协议之上。可以看到应用层有很多协议,HTTP、SMTP、DNS.......。

如果我们要实现TCP、UDP协议,自己写协议比较复杂,一般都是操作系统提供的。操作系统提供了一个Socket,可以理解为一个API,不属于任何一个协议,我们通过Socket可以和传输层打交道。

把Socket理解为一个插座,数据理解为电力,如果我们要充电,就需要插座,而且电在传输过程中要经过很多变压器(传送层下面的层),将设备和插座连接起来,就可以使用电了。

在TCP协议上提供了一个接口(Socket),通过Socket和底层协议建立了一个连接。

HTTP协议是单向的,A发送请求,B响应,但B不能主动发送消息给A。

但是TCP协议是双向的,只要不主动断开,就一直在存在联系。

Socket可以操控TCP,这样就可以实现自己的协议,不需要去TCP协议上进行开发。这个在很多情况下是很实用的,比如聊天工具,会在应用层实现一套自己的协议,通过Socket和TCP打交道。

10.2 通过Socket实现client和Server通信

Socket编程一般会涉及Socket_client和Socket_server编程。

image-20200922104045152

左侧是server端,右侧是client端。

server端,比如写一个web系统,部署到服务器。

client端,比如常见的PC软件、浏览器。

爬虫是一个很典型的client端,请求server端的数据。

自己实现一个web server端,有个特点是必须随时处于监听和服务的状态,因为不知道客户端什么时候发送请求过来。

绑定:

协议(Socket本身是接口,用来连接应用层和传输层)、

地址(Socket可以监听本地ip,也可以监听对外Ip)

端口(每一个应用程序只能占用一个端口,使用Socket指定端口,就可以把应用程序和端口绑定起来,这样在多个应用程序下数据传输不会混乱,特定的应用程序使用某一个端口进行数据传输),相同的应用程序可以使用一个端口(多线程中运行了相同的程序)。

listen:

监听Socker连接请求

绑定完之后就可以开始监听,监听之后调用accept函数会阻塞连接请求,直到client端发起连接请求(TCP,三次握手),成功后就创建一个Socket连接。然后运行recv函数获取数据(需要client端send发送数据过来),之后可以打印并做各种操作(服务端可以send数据给client端,理论上只要有Socket连接,Server端就可以一直向client端发送数据,但是在HTTP请求中,一般只进行一次server和client之间的数据传输,之后连接就会关闭)。

可以看一个服务端的简单例子

server:

import socket

server = socket.socket(socket.AF_INET,socket.SOCK_STREAM) # AF_INET是ipv4,socket.SOCK_STREAM是一个协议
server.bind(('0.0.0.0', 8000))# IP地址,端口
server.listen()
sock, addr = server.accept() # accept 会返回一个sock和addr
#获取从客户端发送的数据
data = sock.recv(1024) #1024=1k的数据
print(data.decode("utf8")) # data是byte类型

server.close()
sock.close()
bobby

client:

import socket

client = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
client.connect(('127.0.0.1', 8000)) # 要明确指明ip地址
client.send("bobby".encode("utf8"))

client.close()

这样通信就完成了,再修改一下,让服务端发送数据给client端

server:

import socket

server = socket.socket(socket.AF_INET,socket.SOCK_STREAM) # AF_INET是ipv4,socket.SOCK_STREAM是一个协议
server.bind(('0.0.0.0', 8000))# IP地址,端口
server.listen()
sock, addr = server.accept() # accept 会返回一个sock和addr
#获取从客户端发送的数据
data = sock.recv(1024) #1024=1k的数据
print(data.decode("utf8")) # data是byte类型
sock.send("hello {}".format(data.decode("utf8")).encode("utf8")) # 从client传过来的数据是byte类型,先decode为str,再encode为byte,传入client端

server.close()
sock.close()
bobby

client:

import socket

client = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
client.connect(('127.0.0.1', 8000)) # 要明确指明ip地址
client.send("bobby".encode("utf8"))
data = client.recv(1024)
print (data.decode("utf8"))

client.close()
hello bobby

10.3 Socker实现聊天和多用户连接

server:

import socket

server = socket.socket(socket.AF_INET,socket.SOCK_STREAM) # AF_INET是ipv4,socket.SOCK_STREAM是一个协议
server.bind(('0.0.0.0', 8000))# IP地址,端口
server.listen()
sock, addr = server.accept() # accept 会返回一个sock和addr
# server是用来监听的,sock才是用户连接的socket
#获取从客户端发送的数据
#一次获取1k的数据
while True:
    data = sock.recv(1024) # 假设1024字节就可以把数据取完
    print(data.decode("utf8"))
    re_data = input()
    sock.send(re_data.encode("utf8"))

client:

import socket

client = socket.socket(socket.AF_INET,socket.SOCK_STREAM) # 对用户来说,只需要一个client就可以了
client.connect(('127.0.0.1', 8000)) # 要明确指明ip地址
while True:
    re_data = input()
    client.send(re_data.encode("utf8"))
    data = client.recv(1024)
    print (data.decode("utf8"))

但是问题是server端只能接受一个请求,一个用户进来后,就一直在while循环里出不来。

如何实现多用户连接呢,可以用线程,每一个socket是一个线程,主线程接受请求。

server:

import socket
import threading

server = socket.socket(socket.AF_INET,socket.SOCK_STREAM) # AF_INET是ipv4,socket.SOCK_STREAM是一个协议
server.bind(('0.0.0.0', 8000))# IP地址,端口
server.listen()

# server是用来监听的,sock才是用户连接的socket
#获取从客户端发送的数据
#一次获取1k的数据
def handle_sock(sock, addr):
    while True:
        data = sock.recv(1024) # 假设1024字节就可以把数据取完
        print(data.decode("utf8"))
        re_data = input()
        sock.send(re_data.encode("utf8"))

while True:
    sock, addr = server.accept()
    # 用线程去处理新接收的连接(用户)
    client_thread = threading.Thread(target=handle_sock, args=(sock, addr)) # target必须为函数名
    client_thread.start()

client:

import socket

client = socket.socket(socket.AF_INET,socket.SOCK_STREAM) # 对用户来说,只需要一个client就可以了
client.connect(('127.0.0.1', 8000)) # 要明确指明ip地址
while True:
    re_data = input()
    client.send(re_data.encode("utf8"))
    data = client.recv(1024)
    print (data.decode("utf8"))

10.4 socket模拟http请求

requests ->urllib -> socket

requests 基于urllib ,urllib 基于socket,凡是网络相关的连接(比如数据库连接,进程间的通信等)最底层都是用socket来连接的。

#requests -> urlib -> socket
import socket
from urllib.parse import urlparse # urlparse是用来解析url的,不是用来发起请求的
def get_url(url):
    #通过socket请求html
    url = urlparse(url) # 需要先进行url解析
    host = url.netloc # url主域名
    path = url.path # url子路径
    if path == "": # 如果直接请求主域名
        path = "/"

    # 建立socket连接
    client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    client.connect((host, 80)) # HTTP请求都是80端口
    # http协议是基于TCP协议的
    # GET {} 第一行必须这样写
    client.send("GET {} HTTP/1.1\r\nHost:{}\r\nConnection:close\r\n\r\n".format(path, host).encode("utf8"))
    data = b"" # 从server获取的数据为byte
    while True:
        d = client.recv(1024) # 临时获取的数据
        if d:
            data += d
        else: # 如果没有数据,则退出
            break

    data = data.decode("utf8")
    html_data = data.split("\r\n\r\n")[1] # 去掉header信息
    print(html_data)
    client.close()

if __name__ == "__main__":
    get_url("http://www.baidu.com")
posted @ 2020-09-22 20:53  yueqiudian  阅读(343)  评论(0编辑  收藏  举报