网络编程
低级别的网络服务python
高级别的网络服务web
socket又称“套接字”,应用程序经过“套接字”向网络发出请求或者应答网络请求,使主机间或者一台计算机上的进程间能够通信。 数据库
tcp
传输控制协议(Transfer Control Protocol)
tcp优劣势:编程
一、稳定
二、相对于udp而言,要慢一些(几乎能够忽略不计)
三、web服务器都是使用的tcp
udp优劣势:
一、不稳定(几乎能够忽略不计,可是总存在隐患)
二、比tcp要快一些
tcp的原理就相似生活中的“打电话”,先要双方接通,才能通话
udp的原理就相似生活中的“写信”,信寄出去,就无论了,能不能到达也是不能保证的
TCP三次握手
当须要使用tcp协议的时候(如http,其底层就是tcp协议)
第一次握手
客户端先发一个“syn”的数据包给服务器。数据包中包括参数:序列号(SEQUENCE NUM),假设为J。上述为0.后端
第二次握手
服务器收到“syn”的数据包后,给客户端发一个“syn+ack”的数据包。数据包中包括参数,,分别是:序列号(SEQUENCE NUM,假设为K)确认号(ACK NUM,假设为J+1),上述分别是“0”和“1”。
客户端将以前发送的数据包中的J值加1,再将收到的服务器发送过来的数据包中的“J+1”进行比较,若是同样,说明第二次链接成功。
第三次握手
客户端给服务端发一个“ack”的数据包。浏览器
序列号(SEQUENCE NUM,假设为J+1):上述为1
确认号(ACK NUM,假设为K+1):上述为1
服务器将以前发送的数据包中的K值加1,而后将收到的客户端发送过来的数据包中的“K+1”进行比较,若是同样,说明第三次链接成功
如此,三次握手创建成功。
HTTP请求过程
一、客户端向服务器发送“syn”请求包,创建第一次握手
二、服务器向客户端发送“syn+ack”数据包,创建第二次握手
三、客户端想服务器发送“ack”的数据包,创建第三次握手;紧随第三次握手的数据包后面,客户端紧接着向服务器发送“http”的数据包
四、服务器向客户端发送以前的“http”的确认包,并紧随着发送“http”的响应数据包,
五、客户端接收到“http”包后,再向服务器发送“ack”的确认包,告诉服务器数据包收到了
tcp协议中,不论是客户端仍是服务器,只要收到了数据,就必定会发送一个ack确认包给发送方。这也就致使了tcp比udp稳定的缘由。
TCP四次挥手
TCP的十种状态
注服务器
当一端收到一个FIN后,内核让read返回0来通知应用层另外一端已经终止了向本段的数据传送
发送FIN一般是应用层对socket进行关闭的结果
TTL
Time To Live,IP包被路由器丢弃以前容许经过的最大网段数量。
虽然TTL从字面上翻译,是能够存活的时间,但实际上TTL是IP数据包在计算机网络中能够转发的最大跳数。TTL字段由IP数据包的发送者设置,在IP数据包从源到目的的整个转发路径上,每通过一个路由器,路由器都会修改这个TTL字段值,具体的作法是把该TTL的值减1,而后再将IP包转发出去。若是在IP包到达目的IP以前,TTL减小为0,路由器将会丢弃收到的TTL=0的IP包并向IP包的发送者发送 ICMP time exceeded消息。
TTL的主要做用是避免IP包在网络中的无限循环和收发,节省了网络资源,并能使IP包的发送者能收到告警消息。
TTL 是由发送主机设置的,以防止数据包不断在IP互联网络上永不终止地循环。转发IP数据包时,要求路由器至少将 TTL 减少 1。
2MSL
MSL:Maximum Segment Lifetime,报文最大生存时间,一个数据包在网络上传输所用的最大的时间,称为msl,通常为1~2分钟。
TCP的最后一次挥手,怎么能保证服务器端必定会收到呢?网络
若是在一个msl时间内,服务端没有收到“最后一次挥手”,那么服务端会再次发一个“FIN”数据包给客户端,这一段时间最长又是一个msl,总的加起来就是2msl,在此期间,若是客户端接收到了“FIN”数据包,那么会再发一次“ACK”给服务器;相反若是在2msl时间后,尚未收到服务器的“FIN”数据包的话,说明“最后一次挥手”成功。
注:
一、在此等待的2msl期间内,会占用端口,端口不会被释放。
二、主动关闭的一段并不是必定是客户端,也能够是服务端。因此一旦是服务端主动关闭,因为服务端是绑定了端口的,程序就没法立马运行了,由于在2msl期间内,端口仍是被占用的。固然,客户端无所谓,反正是动态分配端口。
长链接和短连接
TCP在真正的读写操做以前,server与client之间必须创建一个链接,
当读写操做完成后,双方再也不须要这个链接时它们能够释放这个链接,
链接的创建经过三次握手,释放则须要四次握手,
因此说每一个链接的创建都是须要资源消耗和时间消耗的。
TCP通讯的整个过程
1. TCP短链接
模拟一种TCP短链接的状况:
l client 向 server 发起链接请求
l server 接到请求,双方创建链接
l client 向 server 发送消息
l server 回应 client
l 一次读写完成,此时双方任何一个均可以发起 close 操做
在第 步骤5中,通常都是 client 先发起 close 操做。固然也不排除有特殊的状况。
从上面的描述看,短链接通常只会在 client/server 间传递一次读写操做!
2. TCP长链接
再模拟一种长链接的状况:
l client 向 server 发起链接
l server 接到请求,双方创建链接
l client 向 server 发送消息
l server 回应 client
l 一次读写完成,链接不关闭
l 后续读写操做...
l 长时间操做以后client发起关闭请求
3. TCP长/短链接操做过程
3.1 短链接的操做步骤是:
创建链接——数据传输——关闭链接...创建链接——数据传输——关闭链接
3.2 长链接的操做步骤是:
创建链接——数据传输...(保持链接)...数据传输——关闭链接
4. TCP长/短链接的优势和缺点
l 长链接能够省去较多的TCP创建和关闭的操做,减小浪费,节约时间。对于频繁请求资源的客户来讲,较适用长链接。
l client与server之间的链接若是一直不关闭的话,会存在一个问题:
随着客户端链接愈来愈多,server迟早有扛不住的时候,这时候server端须要采起一些策略,
如关闭一些长时间没有读写事件发生的链接,这样能够避免一些恶意链接致使server端服务受损(如LOL中的挂机,一段时间后就会断开链接);
若是条件再容许就能够以客户端机器为颗粒度,限制每一个客户端的最大长链接数,
这样能够彻底避免某个蛋疼的客户端连累后端服务。
l 短链接对于服务器来讲管理较为简单,存在的链接都是有用的链接,不须要额外的控制手段。
l 但若是客户请求频繁,将在TCP的创建和关闭操做上浪费时间和带宽。
5. TCP长/短链接的应用场景
长链接多用于操做频繁,点对点的通信,并且链接数不能太多状况。
每一个TCP链接都须要三次握手,这须要时间,若是每一个操做都是先链接,
再操做的话那么处理速度会下降不少,因此每一个操做完后都不断开,
再次处理时直接发送数据包就OK了,不用创建TCP链接。
例如:数据库的链接用长链接,若是用短链接频繁的通讯会形成socket错误,
并且频繁的socket 建立也是对资源的浪费。
而像WEB网站的http服务通常都用短连接,由于长链接对于服务端来讲会耗费必定的资源,
而像WEB网站这么频繁的成千上万甚至上亿客户端的链接用短链接会更省一些资源,
若是用长链接,并且同时有成千上万的用户,若是每一个用户都占用一个链接的话,
那可想而知吧。因此并发量大,但每一个用户无需频繁操做状况下需用短连好。
python代码实现
服务端
流程
一、socket建立一个套接字
二、bind绑定ip和port
三、listen 使套接字变为能够被动连接(默认建立的套接字是主动去连接别人的)
四、accept 等待客户端的连接(accept和客户端的connect是一对一的关系,服务器的accept只响应客户端的connect)
五、send/recv 发送和接收数据(recv和send是一对多的关系,服务器的recv响应客户端的send,也响应客户端socket的close;反之亦然)。有一个好玩的事情,当某一端send空数据的时候,另外一端recv并无响应,而当close的时候,recv倒是能响应的,不过数据为空,猜想是send不能发送空数据
代码
# coding:utf-8
import socket
import config
import logging
logging.basicConfig(level=logging.INFO, format="%(asctime)-15s %(levelname)s %(filename)s %(lineno)d %(message)s",)
def main():
# 建立套接字
# family:套接字家族,AF_UNIX或者AF_INET(默认)
# type:套接字类型,面向链接的仍是面向非链接的,SOCK_STREAM(默认)或者SOCK_DGRAM
# protocol:通常不填默认为0
sk = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 重用ip和port,防止报错
sk.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 绑定ip和port
sk.bind((config.host, config.port))
# 使套接字变为被动链接,最多可接收给定参数的客户端的链接(默认套接字是主动去链接别人的)
sk.listen(5)
nsk, addr = sk.accept()
logging.info("client connected! socket={}, addr={}".format(nsk, addr))
data = nsk.recv(1024)
if len(data) == 0:
# 客户端关闭了链接
nsk.close()
else:
nsk.send("thank you".encode("utf-8"))
data2 = nsk.recv(1024)
print(data2)
if __name__ == '__main__':
main()
客户端
代码实现的客户端
# coding:utf-8
import socket
import config
def main():
# 建立socket
sk = socket.socket()
print("client connected! socket={}".format(sk))
# 链接服务器
sk.connect((config.host, config.port))
# 发送数据到服务器
sk.send(b"")
data = sk.recv(1024)
if len(data) == 0:
# 服务器端主动断开链接
sk.close()
else:
print(data)
sk.close()
if __name__ == '__main__':
main()
浏览器客户端
使用postman模拟浏览器请求(get/post/put/delete均可以),修改服务器代码以下:并发
def main():
# 建立套接字
sk = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 重用ip和port,防止报错
sk.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 绑定ip和port
sk.bind((config.host, config.port))
# 使套接字变为被动链接,最多可接收给定参数的客户端的链接(默认套接字是主动去链接别人的)
sk.listen(5)
nsk, addr = sk.accept()
logging.info("client connected! socket={}, addr={}".format(nsk, addr))
data = nsk.recv(1024)
if len(data) == 0:
# 客户端关闭了链接
nsk.close()
else:
nsk.send("thank you".encode("utf-8"))
data2 = nsk.recv(1024)
print(data2)
调试发现,一次浏览器的请求,其实作了四个操做,分别是:socket
- 建立套接字:sk = socket.socket()
- 链接服务器:sk.connect((ip, port))
- 发送消息:socket.send(请求头)。请求头如:b'POST / HTTP/1.1\r\nUser-Agent: PostmanRuntime/7.17.1\r\nAccept: */*\r\nCache-Control: no-cache\r\nPostman-Token: 016ca998-9f45-4ba5-949b-07a51ea0f3e9\r\nHost: 127.0.0.1:5002\r\nAccept-Encoding: gzip, deflate\r\nContent-Length: 0\r\nConnection: keep-alive\r\n\r\n'
- 关闭链接:sk.close()。经过调试发现data2的数据为空字符串,说明客户端关闭了链接