0.概述
一些相互连接的、以共享资源为目的的计算机的集合。使用网络能够把多方连接在一起,然后可以进行数据传递。
(1)tcp/ip协议详解
计算机诞生之后,产为了通信,产生了各种各样的通信协议,形成了不同的计算机网络,这些计算机网络虽然可以进行内部通信,但是不同的计算机网络之间却无法进行通信。为了把全世界的所有不同类型的计算机都连接起来,就必须规定一套全球通用的协议,为了实现互联网这个目标,互联网协议簇(Internet Protocol Suite)就是通用协议标准。因为互联网协议包含了上百种协议标准,但是最重要的两个协议是TCP和IP协议,所以,大家把互联网的协议简称TCP/IP协议。
(1)ip地址
IP地址是互联网上每台计算机的唯一标识,IP协议负责把数据从一台计算机通过网络发送到另一台计算机。
IPV4地址实际上是一个32位整数(称为IPv4),以字符串表示的IP地址如192.168.0.1
实际上是把32位整数按8位分组后的数字表示(点分10进制),目的是便于阅读。
IPv6地址实际上是一个128位整数,它是目前使用的IPv4的升级版,(采用点分16进制)以字符串表示类似于2001:0db8:85a3:0042:1000:8a2e:0370:7334
。
IP分类:
32比特的ip地址被分为两个部分:
————网络号(NetWork ID , NID)
————主机号(Host ID,HID)
IPv4定义了5类ip地址,分别为A,B,C,D,E类地址。
子网掩码:子网掩码是一个32位的2进制数,其对应网络地址的所有位置都为1,对应于主机地址的所有位置都为0,主要用于识别ip地址中的网络地址和主机地址。
告知路由器,IP地址的前多少位是网络地址,后多少位(剩余位)是主机地址,使路由器正确判断任意IP地址是否是本网段的,从而正确地进行路由。
(2)端口
要将数据发送到对方指定的应用程序上,为了标识这些应用程序,所以给这些网络应用程序都用数字进行标识,为了方便称呼这些数字,则将这些数字称为端口。
知名端口如下(为了通方便,一些端口被固定下来使用)形成了知名端口:
(3)传输协议
tcp协议(电子邮件,web应用)
简介: TCP协议,传输控制协议(英语:Transmission Control Protocol,缩写为 TCP)是一种面向连接的、可靠的、基于字节流的传输层通信协议。 TCP通信需要经过创建连接、数据传送、终止连接三个步骤。 特点 1. 面向连接 通信双方必须先建立连接才能进行数据的传输,双方都必须为该连接分配必要的系统内核资源,以管理连接的状态和连接上的传输。 双方间的数据传输都可以通过这一个连接进行。 完成数据交换后,双方必须断开此连接,以释放系统资源。 这种连接是一对一的,因此TCP不适用于广播的应用程序,基于广播的应用程序请使用UDP协议。 2.可靠传输 1)TCP采用发送应答机制 TCP发送的每个报文段都必须得到接收方的应答才认为这个TCP报文段传输成功 2)超时重传 发送端发出一个报文段之后就启动定时器,如果在定时时间内没有收到应答就重新发送这个报文段。 TCP为了保证不发生丢包,就给每个包一个序号,同时序号也保证了传送到接收端实体的包的按序接收。然后接收端实体对已成功收到的包发回一个相应的确认(ACK);如果发送端实体在合理的往返时延(RTT)内未收到确认,那么对应的数据包就被假设为已丢失将会被进行重传。 3)错误校验 TCP用一个校验和函数来检验数据是否有错误;在发送和接收时都要计算校验和。 4) 流量控制和阻塞管理 流量控制用来避免主机发送得过快而使接收方来不及完全收下
3.三次握手:
(1)第一次握手:Client将标志位SYN置为1,随机产生一个值seq=J,并将该数据包发送给Server,Client进入SYN_SENT状态,等待Server确认。
(2)第二次握手:Server收到数据包后由标志位SYN=1知道Client请求建立连接,Server将标志位SYN和ACK都置为1,ack=J+1,随机产生一个值seq=K,
并将该数据包发送给Client以确认连接请求,Server进入SYN_RCVD状态。
(3)第三次握手:Client收到确认后,检查ack是否为J+1,ACK是否为1,如果正确则将标志位ACK置为1,ack=K+1,并将该数据包发送给Server,Server检查ack是否为K+1,
ACK是否为 1,如果正确则连接建立成功,Client和Server进入ESTABLISHED状态,完成三次握手,随后Client与Server之间可以开始传输数据了。
4.四次挥手:
(1)第一次挥手:主机1(可以是客户端,也可以是服务器端),设置Sequence Number
和Acknowledgment Number
,向主机2发送一个FIN
报文段;此时,主机1进入FIN_WAIT_1
状态;
这表示主机1没有数据要发送给主机2了;
(2)第二次挥手:主机2收到了主机1发送的FIN
报文段,向主机1回一个ACK
报文段,Acknowledgment Number
为Sequence Number
加1;主机1进入FIN_WAIT_2
状态;主机2告诉主机1,
我也没有数据要发送了,可以进行关闭连接了;
(3)第三次挥手:主机2向主机1发送FIN
报文段,请求关闭连接,同时主机2进入CLOSE_WAIT
状态;
(4)第四次挥手:主机1收到主机2发送的FIN
报文段,向主机2发送ACK
报文段,然后主机1进入TIME_WAIT
状态;主机2收到主机1的ACK
报文段以后,就关闭连接;此时,
主机1等待2MSL后依然没有收到回复,则证明Server端已正常关闭,那好,主机1也可以关闭连接了。
5.补充:
(1)为什么需要三次握手,两次不可以吗?或者四次、五次可以吗?
我们来分析一种特殊情况,假设客户端请求建立连接,发给服务器SYN包等待服务器确认,服务器收到确认后,如果是两次握手,假设服务器给客户端在第二次握手时发送数据,数据从服务器发出,服务器认为连接已经建立,但在发送数据的过程中数据丢失,客户端认为连接没有建立,会进行重传。假设每次发送的数据一直在丢失,客户端一直SYN,服务器就会产生多个无效连接,占用资源,这个时候服务器可能会挂掉。这个现象就是我们听过的“SYN的洪水攻击”。
总结:第三次握手是为了防止:如果客户端迟迟没有收到服务器返回确认报文,这时会放弃连接,重新启动一条连接请求,但问题是:服务器不知道客户端没有收到,所以他会收到两个连接,浪费连接开销。如果每次都是这样,就会浪费多个连接开销。
(2)为什么TIME_WAIT状态需要经过2MSL(最大报文段生存时间)才能返回到CLOSE状态?
首先,MSL即Maximum Segment Lifetime,就是最大报文生存时间,是任何报文在网络上的存在的最长时间,超过这个时间报文将被丢弃。《TCP/IP详解》中是这样描述的:MSL是任何报文段被丢弃前在网络内的最长时间。RFC 793中规定MSL为2分钟,实际应用中常用的是30秒、1分钟、2分钟等。
TCP的TIME_WAIT需要等待2MSL,当TCP的一端发起主动关闭,三次挥手完成后发送第四次挥手的ACK包后就进入这个状态,等待2MSL时间主要目的是:防止最后一个ACK包对方没有收到,那么对方在超时后将重发第三次握手的FIN包,主动关闭端接到重发的FIN包后可以再发一个ACK应答包。在TIME_WAIT状态时两端的端口不能使用,要等到2MSL时间结束才可以继续使用。当连接处于2MSL等待阶段时任何迟到的报文段都将被丢弃。
(3)client发送完最后一个ack之后,进入time_wait状态,但是他怎么知道server有没有收到这个ack呢?莫非sever也要等待一段时间,如果收到了这个ack就close,
如果没有收到就再发一个fin给client?这么说server最后也有一个time_wait哦?求解答!
因为网络原因,主动关闭的一方发送的这个ACK包很可能延迟,从而触发被动连接一方重传FIN包。极端情况下,这一去一回,就是两倍的MSL时长。
如果主动关闭的一方跳过TIME_WAIT直接进入CLOSED,或者在TIME_WAIT停留的时长不足两倍的MSL,那么当被动关闭的一方早先发出的延迟包到达后,
就可能出现类似下面的问题:1.旧的TCP连接已经不存在了,系统此时只能返回RST包2.新的TCP连接被建立起来了,延迟包可能干扰新的连接,
这就是为什么time_wait需要等待2MSL时长的原因。
(4)为什么连接的时候是三次握手,关闭的时候却是四次握手?
因为当Server端收到Client端的SYN连接请求报文后,可以直接发送SYN+ACK报文。其中ACK报文是用来应答的,SYN报文是用来同步的。但是关闭连接时,当Server端收到FIN报文时,
很可能并不会立即关闭SOCKET,所以只能先回复一个ACK报文,告诉Client端,"你发的FIN报文我收到了"。只有等到我Server端所有的报文都发送完了,我才能发送FIN报文,
因此不能一起发送。
udp协议(实时在线聊天,视频协议等等)
特点:
面向无连接:传输数据之前源端和目的端不需要建立连接 每个数据报的大小都限制在64k(8个字节)以内 面向报文的不可靠协议(即发出去的数据不一定会接收到) 传输速率快,效率高
TCP和UDP对比
1.TCP面向连接的可靠协议,而UDP不可靠的协议 2.TCP连接过程可以传输大量数据,而UDP每个报文不超过64 3.TCP传输速度慢,效率低,而UDP传输速度快,效率高
(4)总结
有了ip+端口+通信协议,计算机通信的基础就有了,网络编程就是让指的是让在不同的电脑上的软件能够进行数据传递,其中ip+端口做通信对象的标识,而通信协议则提供了如何进行通信的具体规定。
1.互联网通信模型
1.网络模型的示意图
2.网络模型的各层的作用:
面向通信的低层:
物理层功能:主要是基于电器特性发送高低电压(电信号),高电压对应数字1,低电压对应数字0,它利用传输介质为数据链路层提供物理连接
数据链路层:定义了电信号的分组方式,为网络层提供服务的,解决两个相邻结点之间的通信问题。
网络层:是为传输层提供服务的,传送的协议数据单元称为数据包或分组。该层的主要作用是解决如何使数据包通过各结点传送的问题,即通过路径选择算法(路由)将数据包送到目的地。
引入一套新的地址用来区分不同的广播域/子网,这套地址即网络地址。(ip协议) 传输层:作用是为上层协议提供端到端的可靠和透明的数据传输服务,包括处理差错控制和流量控制等问题。该层向高层屏蔽了下层数据通信的细节,
使高层用户看到的只是在两个传输实体间的一条主机到主机的、可由用户控制和设定的、可靠的数据通路。(TCP/UDP协议)
面向信息处理的高层:
会话层:主要功能是管理和协调不同主机上各种进程之间的通信(对话),即负责建立、管理和终止应用程序之间的会话。会话层得名的原因是它很类似于两个实体间的会话概念。
例如,一个交互的用户会话以登录到计算机开始,以注销结束。 表示层:处理流经结点的数据编码的表示方式问题,以保证一个系统应用层发出的信息可被另一系统的应用层读出。如果必要,该层可提供一种标准表示形式,用于将计算机内部的多种数据
表示格式转换成网络通信中采用的标准表示形式。数据压缩和加密也是表示层可提供的转换功能之一。 应用层:OSI参考模型的最高层,是用户与网络的接口。该层通过应用程序来完成网络用户的应用需求,如文件传输、收发电子邮件等。
2.网络编程(SCOKET编程)
(1)SOCKET定义
socket(简称 套接字
) ,是支持TCP/IP的网络通信的基本操作单元,可以看做是不同主机之间的进程进行双向通信的端点,简单的说就是通信的两方的一种约定,用套接字中的相关函数来完成通信过程。它能实现不同主机间的进程间通信,我们网络上各种各样的服务大多都是基于 Socket 来完成通信的例如我们每天浏览网页、QQ 聊天、收发 email 等等。
(2)SCOKET的创建
利用python的内置模块就可以完成该功能: import socket socket.socket(AddressFamily, Type) 类 socket.socket 创建一个 socket,该类实例化时需要两个参数,返回socket 对象: 参数一:AddressFamily(地址簇) socket.AF_INET IPv4(默认) socket.AF_INET6 IPv6 socket.AF_UNIX 只能够用于单一的Unix系统进程间通信 参数二:Type(类型) socket.SOCK_STREAM 流式socket , for TCP (默认)
socket.SOCK_DGRAM 数据报式socket , for UDP 实际应用: (1)创建TCP连接: import socket # 创建tcp的套接字 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # ...这里是使用套接字的功能(省略)... # 不用的时候,关闭套接字 s.close()
(2)创建UDP连接: import socket # 创建tcp的套接字 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # ...这里是使用套接字的功能(省略)... # 不用的时候,关闭套接字 s.close()
(3)SOCKET内置方法
函数 描述
服务器端套接字
s.bind() 绑定地址(host,port)到套接字, 在AF_INET下,以元组(host,port)的形式表示地址。
s.listen() 开始TCP监听。backlog指定在拒绝连接之前,操作系统可以挂起的最大连接数量。该值至少为1,大部分应用程序设为5就可以了。
s.accept() 被动接受TCP客户端连接,(阻塞式)等待连接的到来
客户端套接字
s.connect() 主动初始化TCP服务器连接,。一般address的格式为元组(hostname,port),如果连接出错,返回socket.error错误。
s.connect_ex() connect()函数的扩展版本,出错时返回出错码,而不是抛出异常
公共用途的套接字函数
s.recv() 接收TCP数据,数据以字符串形式返回,bufsize指定要接收的最大数据量。flag提供有关消息的其他信息,通常可以忽略。
s.send() 发送TCP数据,将string中的数据发送到连接的套接字。返回值是要发送的字节数量,该数量可能小于string的字节大小。
s.sendall() 完整发送TCP数据,完整发送TCP数据。将string中的数据发送到连接的套接字,但在返回之前会尝试发送所有数据。成功返回None,失败则抛出异常。
s.recvfrom() 接收UDP数据,与recv()类似,但返回值是(data,address)。其中data是包含接收数据的字符串,address是发送数据的套接字地址。
s.sendto() 发送UDP数据,将数据发送到套接字,address是形式为(ipaddr,port)的元组,指定远程地址。返回值是发送的字节数。
s.close() 关闭套接字
s.getpeername() 返回连接套接字的远程地址。返回值通常是元组(ipaddr,port)。
s.getsockname() 返回套接字自己的地址。通常是一个元组(ipaddr,port)
s.setsockopt(level,optname,value) 设置给定套接字选项的值。
s.getsockopt(level,optname[.buflen]) 返回套接字选项的值。
s.settimeout(timeout) 设置套接字操作的超时期,timeout是一个浮点数,单位是秒。值为None表示没有超时期。一般,超时期应该在刚创建套接字时设置,因为它们可能用于连接的操作(如connect())
s.gettimeout() 返回当前超时期的值,单位是秒,如果没有设置超时期,则返回None。
s.fileno() 返回套接字的文件描述符。
s.setblocking(flag) 如果flag为0,则将套接字设为非阻塞模式,否则将套接字设为阻塞模式(默认值)。非阻塞模式下,如果调用recv()没有发现任何数据,或send()调用无法立即发送数据,那么将引起socket.error异常。
s.makefile() 创建一个与该套接字相关连的文件
(2)SOCKET的应用:
聊天室实现 import socket # 聊天器主要功能 # 1、发送消息 # 2、接收消息 # 3、退出消息 def send_msg(udp_socket): """发送消息的方法""" # 1、输入接收方的ip地址 ipaddr = input("请输入接收方地址: ") if len(ipaddr) == 0: ipaddr = "192.168.31.163" print("默认设置为:%s" % ipaddr) # 2、输入接收方的端口 port = input("请输入接收方端口号: ") if len(port) == 0: port = "6666" print("默认设置为:%s" % port) # 3、要求输入要发送的内容 content = input("请输入要发送的内容: ") # 4、发送数据 udp_socket.sendto(content.encode(), (ipaddr, int(port))) def recv_msg(udp_socket): # 1、接收数据 recv_data = udp_socket.recvfrom(1024) # 2、把接收到的数据解码并且显示出来 re_text = recv_data[0].decode() print("接收到消息为:%s" % re_text) ip_port = recv_data[1] # 3、打印显示发送方的IP和端口信息 print(ip_port) if __name__ == '__main__': # 创建套接字 udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # 绑定端口为 6666 udp_socket.bind(("", 6666)) while True: # 给出提示,显示聊天器的主要功能 print("*************************") print("**** 1.发送消息 ****") print("**** 2.接收消息 ****") print("**** 3.退出系统 ****") print("*************************") # 1、提示用户选择功能 num = int(input("请选择功能: ")) # 2、判断用户选择 # 如果选择1,调用发送消息的函数 if num == 1: # print("您选择了发送消息") send_msg(udp_socket) # 如果选择2,调用接收消息的函数 elif num == 2: # print("您 recv_msg(udp_socket) # 如果选择3,退出程序执行 else: print("程序正在退出...") break print("程序已退出!~") # 关闭套接字 udp_socket.close()
参考博客:TCP/IP各层详解