文章目录
一、弄懂HTTP、Socket、TCP这几个概念
网络由下往上分为物理层、数据链路层、网络层、传输层、会话层、表示层和应用层。IP协议对应于网络层,TCP协议对应于传输层,而HTTP协议对应于应用层。socket则是对TCP/IP协议的封装和应用。也可以说,TPC/IP协议是传输层协议,主要解决数据如何在网络中传输,而HTTP是应用层协议,主要解决如何包装数据。
1.1、概念
1、HTTP:
http协议
**官方概念:**HTTP协议是Hyper Text Transfer Protocol(超文本传输协议)的缩写,是用于从万维网(WWW:World Wide Web )服务器传输超文本到本地浏览器的传送协议。
白话概念:HTTP协议就是 服务器(Server)和客户端(Client)之间进行数据交互(相互传输数据)的一种形式。
==》 遵从http协议,则服务器和客户端可以进行简单的数据交互
HTTP工作原理
HTTP协议工作于客户端-服务端架构为上。浏览器作为HTTP客户端通过URL向HTTP服务端即WEB服务器发送所有请求。Web服务器根据接收到的请求后,向客户端发送响应信息。
2、Socket:
socket 被翻译为“套接字”,它是计算机之间进行通信的一种约定或一种方式。 实际上socket是对 TCP/IP 协议的封装,它的出现只是使得程序员更方便地使 TCP/IP 协议栈而已。socket本身并不是协议,它是应用层与 TCP/IP 协议族通信的中间软件抽象层,是一组调用接口(TCP/IP网络的API函数)
Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。
所以,我们无需深入理解tcp/udp协议,socket已经为我们封装好了,我们只需要遵循socket的规定去编程,写出的程序自然就是遵循tcp/udp标准的。
Python 网络编程 | 菜鸟教程 (runoob.com)
3、TCP:
传输控制协议
4、OSI七层:
引子:
一个完整的计算机系统是由硬件、操作系统、应用软件三者组成,具备了这三个条件,一台计算机系统就可以自己跟自己玩了(打个单机游戏,玩个扫雷啥的)
如果你要跟别人一起玩,那你就需要上网了,什么是互联网?
一系列统一的标准,这些标准称之为互联网协议,互联网的本质就是一系列的协议,总称为‘互联网协议’(Internet Protocol Suite).
互联网协议的功能:定义计算机如何接入internet,以及接入internet的计算机通信的标准。
详见网络通信原理:http://www.cnblogs.com/linhaifeng/articles/5937962.html
5、关于TCP/IP和HTTP协议的关系:
我们在传输数据时,可以只使用(传输层)TCP/IP协议,但是那样的话,如果没有应用层,便无法识别数据内容,如果想要使传输的数据有意义,则必须使用到应用层协议,应用层协议有很多,比如HTTP、FTP、TELNET等,也可以自己定义应用层协议。WEB使用HTTP协议作应用层协议,以封装HTTP文本信息,然后使用TCP/IP做传输层协议将它发到网络上。
而socket是对TCP/IP协议的封装,Socket本身并不是协议,而是一个调用接口(API),通过Socket,我们才能使用TCP/IP协议。实际上,Socket跟TCP/IP协议没有必然的联系。Socket编程接口在设计的时候,就希望也能适应其他的网络协议。所以说,Socket的出现只是使得程序员更方便地使用TCP/IP协议栈而已,是对TCP/IP协议的抽象,从而形成了我们知道的一些最基本的函数接口,比如create、listen、connect、accept、send、read和write等等。
6、socket和TCP/IP协议关系:
TCP/IP只是一个协议栈,就像操作系统的运行机制一样,必须要具体实现,同时还要提供对外的操作接口。这个就像操作系统会提供标准的编程接口,比如win32编程接口一样,TCP/IP也要提供可供程序员做网络开发所用的接口,这就是Socket编程接口。
7、HTTP和socket关系:
HTTP是轿车,提供了封装或者显示数据的具体形式;Socket是发动机,提供了网络通信的能力。传输层的TCP是基于网络层的IP协议的,而应用层的HTTP协议又是基于传输层的TCP协议的,而Socket本身不算是协议,就像上面所说,它只是提供了一个针对TCP或者UDP编程的接口。
1.2、将概念串起来
整个计算机网络都是由协议组成的。
A是client端,B是服务器端,当A向B发送请求时,数据传输的时候(平时使用requests、urllib请求数据),在我们操作系统和网络之中要经历5层网络模型。
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打交道。
二、通过Socket实现client和Server通信
2.1、概念
Socket编程一般会涉及Socket_client和Socket_server编程。
左侧是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之间的数据传输,之后连接就会关闭)。
2.2、示例
可以看一个服务端的简单例子
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
三、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"))
四、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")
<!DOCTYPE html><html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8">......