网络通信

网络通信

下载文件就是 在本地创建一个与要下载文件同名的文件然后服务器发送要下载文件的字节客户端就写入到同名文件中

IP地址的分类

A类IP段 1.0.0.0126.255.255.255 (0段到127段不使用)
B类IP段 128.0.0.0191.255.255.255 掩码 255.0.0.0
C类IP段 192.0.0.0223.255.255.255 掩码 255.255.0.0
D类IP段 224.0.0.0239.255.255.255 (用于广播)
D类的IP地址不标识网络 地址覆盖范围为224.0.0.0~239.255.255.255

端口

不同机器发送数据通过IP区分机器在网络上的位置 通过port端口分区哪个进程接收数据
知名端口号0-1023 一般程序要使用知名端口号 需要root权限
注册端口号1024-49151
动态端口号49152-65535 程序如果在发送时没有指定端口 程序会随机指定一个端口

单工(广播)

要么只能发送数据要么只能接收数据的工作模式

半双工(对讲机)

同一时刻只能发送数据或接收数据的工作模式

全双工(电话)

同一时刻既可以发送也可以接收数据的工作模式

socket模块

socket 套接口具有全双工的特点 套接口发送的数据必须是bytes 数据类型
在已经创建套接口 但没有开始读取数据时 接收来的数据系统会暂存 调用接收功能的时候再从系统读取
socket.AF_INET 指定通过ipv4通信协议

UDP用户数据报协议

每条信息都要写上IP端口
socket.SOCK_DGRAM 数据报datagram套接口类型
一个datagram可能被压缩成多个包packets
分组在发送后可能无序的到达接收端
分组可能丢失且不会采取任何措施 接收端也不知道分组丢失
数据报分组有尺寸大小的限制 超出限制在某些路由节点上可能就无法传出
分组是在不建立连接的情况下被发送到远程进程的

UDP收发数据
import socket
def main():
    # 创建一个udp套接口
    udp = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    # 绑定一个本地信息
    upd.bind(("source_IP", source_port))
    # 输入要发送的内容
    string = input("输入要发送的数据:")
    # 发送一个字符串的utf8编码的bytes对象 到ip:port 
    udp.sendto(string.encode("utf-8"), ("dest_IP", dest_port))
    # 接收一个元组数据并设置单次接收的大小
    data = udp.recv(1024)
    # 打印接收到的元组并解码为utf8编码字符串
    print(data[0].decode("utf-8"))
    # 关闭套接口
    udp.close()

udp.bind(("", port)) 绑定一个接收数据的地址端口 ip地址输入空字符串使用默认网卡信息
udp.sendto(b"string".encode("gbk"), ("ipaddr", port)) b将字符串转换为bytes类型或重新编码为gbk编码的bytes类型进行发送
udp.recvfrom(1024) 接收发送方的数据返回一个元组("bytes", ("ipaddr", port)) 并设置单次接收数据的大小

TCP传输控制协议

socket.SOCK_STREAM 流套接口类型
不保留任何消息的边界 接收方一次收到发送方多次发送的数据但是不知道是分几次发送的
接收的数据字节与发送的顺序完全一致(意味着通信前必须建立连接)
接收的数据被无错的接收 如果有错误会尝试修复 如果无法修复会报错

TCP客户端
import socket
def main():
    # 创建客户端tcp套接口
    tcp_client = socket.socket(socket.AF_INAT, socket.STREAM)
    # 绑定一个客户端使用的端口
    tcp_client.bind(("", client_port))
    # 与服务器建立连接
    tcp_client.connect(("server_IP", server_port))
    # 输入要发送的内容
    string = input("输入要发送的内容")
    # 发送数据
    tcp_client.send(string.encode("utf-8"))
    # 接收数据
    data = tcp_client.recv(1024)
    # 打印接收的数据把bytes解码为UTF8编码格式的字符串
    print(data.decode("utf-8"))
    # 关闭套接口
    tcp_client.close()

tcp_client.bind(("", client_port)) 绑定客户端的通信IP端口 否则端口随机 端口只能绑定一次
tcp_client.connect(("server_IP", server_port)) 建立与服务端的TCP连接
tcp_client.send(b"string".encode("utf8")) 字符串加上b转换为bytes类型或重新编码为utf8编码的bytes类型
tcp_client.recv(1024) 接收对方发送的数据返回一个bytes类型对象 并设置单次接收数据的大小
如果接收到的数据为则表示对方调用了close() 如果不为空则表示对方调用的send()

TCP服务器

import socket
def main():
    # 创建服务端tcp套接口
    tcp_server = socket.socket(socket.AF_INET, socket.STREAM)
    # 服务器必须绑定一个端口 否则客户端不知道连接哪里
    tcp_server.bind(("", server_port))
    # 通过listen使套接口变为被动监听服务器进程
    tcp_server.listen(128)
    # 使用accept接收监听到的数据并返回新的套接口对象和客户端信息
    server_client, client_addr = tcp.server.accept()
    # 通过新套接口收发数据
    string = "连接成功"
    server_client.send(string.encode("utf8"))
    # 通过新套接口接收数据并设置一次接收的数据大小
    data = server_client.recv(1024)
    if data:  # 如果收到的数据为空则对方调用了close()
        # 打印接收的数据
        print("来自%s:%s" % (client_addr, data.decode("utf8")))
    else:
        # 关闭套接口
        server_client.close()
    # 关闭服务器套接口
    tcp_server.close()

tcp_server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 重复使用已经占用的端口
tcp_server.bind(("", server_port)) 必须绑定服务端ip信息 否则客户机不知道发送给哪个端口
tcp_server.listen(128) 使套接口变为被动连接并监听其他进程请求的服务器进程 listen在TCP网络编程中使进程变为服务器 并把相应的套接口变为被动连接
tcp_server.accept() 返回一个元组(socket()对象, ("client_ip", client_port))
serv_client_socket, client_ip_port = tcp_server.accept() 固定包含两个信息 新的服务端套接口和客户端信息 使用拆包来分别接收两个对象
serv_client_socket.send(string.encode("utf8")) 通过套接口发送数据给已连接的客户端
serv_client_socket.recv(1024) 通过新的套接口继续与客户端通信 返回客户端发送的data 如果此时对方断开连接会返回空数据

堵塞解堵塞

服务器的accept会等待客户端connect 如果客户端不连接 会堵塞一直等待客户端的连接
recv(1024)等待数据时会堵塞 当对方send()或调用close()会解堵塞 但是close会收到空数据
server.settblocking(False)设置套接口为非堵塞模式

TCP的三次握手与四次挥手

第一次握手

客户端在connect服务器时会发送一个syn同步的seq序列号给服务器表示已经准备就绪

第二次握手

服务器收到客户端syn同步的seq序列号后返回一个把seq+1的ack确认信息给客户端表示已经收到
同时也会发送一个syn同步的seq序列号表示服务器也准备就绪

第三次握手

客户端收到服务器返回的ack表示服务器已经知道自己准备就绪
再次发送包含第一次握手的seq+1的ack确认信息和返回服务器的syn确认的seq+1的ack给服务器表示已经收到服务器准备就绪的信息

第一次挥手

客户端关闭与服务器的数据传输底层会首先发送fin终止seq序列给服务器表示自己关闭了数据发送

第二次挥手

服务器收到这个fin后会首先给客户端返回一个ack确认的seq+1信息并通知程序关闭recv接收功能此时recv会解堵塞并收到空字符串 判断收到的信息是否为空来让程序判断客户端是否已经关闭与服务器的连接

第三次挥手

服务器可能也会关闭与客户端的数据连接但是也可能不关闭所以第三次挥手与第二次挥手不能合并否则如果不关闭那么客户端会一直处于等待服务器ack
服务器关闭与客户端的连接发送一个fin终止的seq序列和第二次挥手的ack确认的seq+1来通知客户端自己已经关闭与客户端的连接

第四次挥手

客户端收到服务器发送的fin终止seq序列并+1返回一个ack给服务器通知服务器自己已经收到
此时服务器应该通知客户端收到这个ack但是如果这样做会一直死循环 所以此时客户端不等待服务器的ack而是等待两倍的网络传输时间即2*MSL时间一般最大时间为2到5分钟

两种假设

如果服务器没有收到第四次挥手时客户端ack信息那么在一个time out后服务器会再次重复第三次挥手 此时客户端处于2MSL时间内直到收到后再发送一次ack给服务器
如果服务器收到了客户端第四次挥手的ack信息那么服务器顺利关闭数据连接但是客户端还是会等待2
MSL时间 因为客户端并不知道服务器是否收到
所以一般客户端不绑定端口否则在2MSL的时间内端口不能再重复利用
所以一般服务器不主动关闭通信否则最后服务器会等待2
MSL的时间

HTTP协议

HTTP协议是Hyper Text Transfer Protocol(超文本传输协议)的缩写

Http协议中的Header与Body

Header的每行最后要加\r\n
HeaderBody之间要用\r\n隔开
Body后无需加\r\n
ACSII码中
'\n' 10 换行
'\r' 13 回车
也可以表示为'\x0a''\x0d'(16进制)

客户端的request信息至少要包含GET / HTTP/1.1

服务器发送的response
HTTP开始部分为header
开始部分为body

HTTP/1.1 200 OK\r\n
Content-Encoding: gzip\r\n
Content-Type: text/xml\r\n
Content-Length: 399\r\n
Connection: keep-alive\r\n
X-Varnish-Cache: HIT\r\n
X-Varnish-Cache-Hits: 1241\r\n
\r\n
.....

一般客户端的GET请求没有body
一般客户端的post请求也有body

长连接短连接

HTTP1.0协议是短连接
每请求一个数据都要经过3次握手与4次挥手
HTTP1.1默认是长连接也可以短连接
只要经过三次握手确认连接后浏览器就可以一直请求想要的数据 但服务器必须告诉浏览器每次发送的数据长度 否则浏览器会一直等待或者意外断开才会结束等待
response 中设置Content-Length 告诉浏览器发送数据的长度
浏览器发现所有请求的数据都已经收到后会主动断开连接

你可能感兴趣的:(网络通信)