#网络通信
使用网络的目的:为了联通多方然后进行通信用的,即把数据从一方传递给另一方,为了让不同的电脑上运行的软件之间能够互相传递数据
小结:使用网络能够把多方链接在一起,然后进行数据传递
所谓的网络编程就是让不同的电脑上的软件能够进行数据传递,即进程之间的通信
特点:数据之间的共享

###1.IP地址:相当于收件的地址(不允许重复)
1).目的:用来标记网络中的一台电脑,在本地局域网上是唯一的
2).组成:由四组数组成由.隔开
3).分类:每一个IP地址包括两部分:网络地址和主机地址(前面相同为同一网络)
ipv4:xxx.xxx.xxx.xxx当今有256的四次方种
ipv6:(未来流行的趋势)
dest_ip:目标地址(目的ip)
src ip:源ip
4).查看本机IP地址:Mac、Linux——ifconfig window——ipconfig
5).私有ip
在这么多网络IP中,国际规定有一部分|P地址是用于我们的局域网使用,也就是属于私网IP ,不在公网中使用的,它们的范围是:

 10.0.0.0~10.255.255.255
 172.16.0. 0~172.31.255.255
 192.168.0.0~192. 168.255.255

###2.端口:对应的IP传入后由端口号再分配给对应的应用程序
1)端口号:端口是通过端口号来标记的,端口号只有整数范围065535
dest_port:目标端口
src_port:源端口
2)知名端口(Well Known Ports)
知名端口是众所周知的端口号。范围0
1023

80端口分配给HTTP服务
21端口分配给FTP服务 

3)动态端口:动态端口的范围是从1024到65535大
之所以称为动态端口,是因为它一般不固定分配某种服务,而是动态分配。
动态分配是指当一个系统程序或应用程序程序需要网络通信时,它向主机申请一个端口,主机从可用的端口号中分配一个供它使用
●当这个程序关闭时,同时也就释放了所占用的端口号

###3.socket简介
1.不同电脑上的进程之间如何通信
首要解决的问题是如何唯一标识一个进程,否则通信无从谈起!
在1台电脑上可以通过进程号(PID) 来唯一标识一个进程,但是在网络中这是行不通的。
其实TCP/IP协议族已经帮我们解决了这个问题,网络层的”ip地址”可以唯一标识网络 中的主机,而传输层的“协议+端口”可以唯一标识主机中的应用进程(进程)。

这样利用ip地址.协议.端就可以标识网络的进程了,网络中的进程通信就可以利用这个标志与其它进程进行交互

注意:
●所谓进程指的是:运行的程序以及运行时用到的资源这个整体称之为进程(在讲解多任务编程时进行详细讲解)
●所谓进程间通信指的是:运行的程序之间的数据共享

2.什么是socket
socket(简称套接字)是进程间通信的一种方式,它与其他进程间通信的一个主要不同是:
它能实现不同主机间的进程间通信, 我们网络上各种各样的服务大多都是基于Socket来完成通信的
例如我们每天浏览网页、QQ聊天、收发email等等
固定代码如下:

import socket


def main():
    # 创建一个udp套接字
    udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

    while True:

        # 从键盘获取数据
        send_data = input("请输入要发送的数据:")

        # 如果输入的数据是exit,那么退出
        if send_data == "exit":
            break
            
        # 可以使用套接字收发数据
       
        udp_socket.sendto(send_data.encode("utf_8"), ("192.168.43.103", 8080))

    # 关闭套接字
    udp_socket.close()


if __name__ == "__main__":
    main()

任意内容的转换:获取数据接收名.encode(“utf_8”) ————encode()编码
发送端口可以不绑定,在使用时会随机产生,但接收方一定得绑定

  • 同一端口在同一时刻不允许被使用两次
    固定代码如下:

    import socket

    def main():
    # 1.创建套接字
    udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    # 2.绑定一个本地信息
    localaddr = (“”, 7788)
    udp_socket.bind(localaddr)
    # 3.接收数据
    while True:
    recv_data = udp_socket.recvfrom(1024)
    # recv_data这个变量中存储的是一个元祖(接收到的数据,(发送方的ip,端口))
    recv_msg = recv_data[0] # 存储接收的数据
    send_addr = recv_data[1] # 存储发送方的地址信息
    # 4.打印接收到的数据
    print(“%s:%s” % (str(send_addr), recv_msg.decode(“gdk”)))

    # 5.关闭套接字
    udp_socket.close()
    if name == ‘main‘:
    main()

Windows 解码用decode(“gdk”),其他的可使用decode(“utf_ 8”)
收发数据的流程
1.创建套接字
udp_ socket=socket.socket(socket.AF_ INET, socket.SOCk_ DGRAM)
localaddr = (“ “, 789)
Nudp_ socket. bind(Localaddr)
2.使用套接字收发数据
udp_ socket.sendto(“xxxxx” . encode(“utf-8”), (“192, 168.33.11”, 7890))
udp_ socket, recvfrom(1024)
3.关闭套接字
udp_ socket.close()
缺点:存在接收漏洞,添加recvfrom()

##tcp
####1.介绍:
TCP(Transmission Control Protocol传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层通信协议,由IETF的RFC793定义。在简化的计算机网络OSI模型中,它完成第四层传输层所指定的功能,用户数据报协议(UDP)是同一层内另一个重要的传输协议。在因特网协议族(Internet protocol suite)中,TCP层是位于IP层之上,应用层之下的中间层。不同主机的应用层之间经常需要可靠的、像管道一样的连接,但是IP层不提供这样的流机制,而是提供不可靠的包交换。

TCP通信需要经过创建连接、数据传送、终止连接三个步骤

TCP通信模型中,在通信之前,一定要先建立相关连接才能发送数据

####2.TCP特点:
1.面向连接
通信双方必须先建立连接才能进行数据的传输,双方都必须为该连接分配必要的系统内核资源,以管理连接的状态和连接上的传输

双方间的数据传输都可以通过这一个连接进行。

完成数据交换后,双方必须断开此连接,以释放系统资源。

这种连接是一对一的,因此TCP不适用于广播的应用程序,基于广播的应用程序请使用UDP协议。

2.可靠传输

  1. TCP采用发送应等机制

TCP发送的每个报文段都必须得到接收方的应答才认为这个TCP报文段传输成功

2)超时重传

发送端发出一个报文段之后就后动定时器,如果在定时时间内没有收到应答就重新发送这个报文顾。

TCP为了保证不发生丢包,就给每个包一个序号,同时序号也保证了传送到接收端实体的包的按序接收,然后接收端实体对已成功收到的包发自一个相应的确认(ACK) ; 如果发送端实体在合理的往返时延(RTT)内未收到确认,那么对应的数据包就被假设为已丢失将会被进行重传.

3)错误校验

TCP月一个校验和消数来检验数据是否有错误;在发送和接收时都要计算校验和。

4)流量控制和阻塞管理

流量控制用来避免主机发送得过快而使接收方来不及完全收下,

TCP与udp的不同点

  • 面向连接(确认有三方交握,链接以创建才能传输)
  • 有序数据传输
  • 重发丢失数据包
  • 舍弃重复的数据包
  • 无差错的数据传输
  • 阻塞/流量控制

####3.tcp客户端
简单的说就是用户使用端
tcp客户端构建流程
tcp的客户端要比服务器端简单 如果说服务器端是需要自己买手机、查手机卡、设置铃声、等待别人打电话流程的话,那么客户端就只需要找一个电话亭,拿起电话拨打即可,流程要少很多

####4.tcp服务器
tcp服务器
想要完成一个tcp服务器的功能,需要的流程如下:

  1. socket创建-一个套接字
  2. bind绑定ip和port
  3. listen使套接字变为可以被动链接
  4. accept等待客户端的链接
  5. recv/send接收发送数据

一个很简单的tcp服务器如下:

import socket

def main():
    # 1.买个手机(创建套接字 socket)
    tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

    # 2. 插入手机卡(绑定本地信息 bind)
    tcp_server_socket.bind(("", 7890))

    # 3.将手机设置为正常的响铃模式(让默认的套接字由主动变为被动 listen)
    tcp_server_socket.listen(128)

    print("等待一个新的客户端到来")
    # 4.等待别人的电话到来(等待客户端的链接 accept)
    new_client_socket, client_addr =tcp_server_socket.accept()
    print("一个新的客户端已到来%s" % str(client_addr))

    # 接受客户端发来的请求
    recv_data = new_client_socket.recv(1024)  # 接收1024字节
    print("客户端的请求是:%s" % recv_data.decode("gdk"))

    # 回送一部分数据给客户端
    new_client_socket.send("hahahahha".encode("utf-8"))

    # 关闭套接字
    # 关闭accept返回的套接字意味着不会再为这个客户端服务
    new_client_socket.close() 
    print("已经服务完毕")

    tcp_server_socket.close()


if __name__ == '__main__':
    main()

监听套接字负责等待有新的客户端进行连接
accept产生新的套接字用来为客户端服务
accept()返回元祖

注意:
服务器与客户端的关系:服务器先运行等待(accept)客户端的连接(调用connect), 客户端连接后服务器会返回客户端信息(client_addr接收)

####5.从客户端下载数据(文件下载器)
使用with open()程序产生异常会自动调用close关闭
步骤:

  1. 创建套接字
  2. 获取服务器的ip port
  3. 链接服务器
  4. 获取下载的文件名字
  5. 将文件名字发送到服务器
  6. 接收文件中的数据
  7. 保存接收到的数据到一个文件中
  8. 关闭套接字

tcp注意点

  1. tcp服务器一般情况下都需要绑定,否则客户端找不到这个服务器

  2. tcp客户端一般不绑定,因为是主动链接服务器,所以只要确定好服务器的ip、port等信息就好,本地客户端可以随机

  3. tcp服务器中通过listen可以将socket创建出来的主动套接字变为被动的,这是做tcp服务器时必须要做的

4.当客户端需要链接服务器时,就需要使用connect进行链接,udp是不需要链接的而是直接发送,但是tcp必须先链接,只有链接成功才能通信

5.当一个tcp客户端连接服务器时,服务器端会有1个新的套接字,这个套接字用来标记这个客户端,单独为这个客户端服务

  1. listen后的套接字是被动套接字,用来接收新的客户端的链接请求的,而accept返回的新套接字是标记这个新客户端的

7.关闭listen后的套接字意味着被动套接字关闭了,会导致新的客户端不能够链接服务器, 但是之前已经链接成功的客户端正常通信。

8.关闭accept返回的套接字意味着这个客户端已经服务完毕

9.当客户端的套接字调用close后,服务器端会recv解堵塞,并且返回的长度为0,因此服务器可以通过返回数据的长度来区别客户端是否已经下线

###6.TCP三次握手、四次挥手

1、三次握手

●所谓三次握手(Three-way Handshake),是指建立一个TCP连接时,需要客户端和服务器总共发送3个包。

●三次握手的目的是连接服务器指定端口,建立TCP连接,并同步连接双方的序列号和确认号并交换 TCP 窗口大小信息.在socket编程中,客户端执行connect()时。将触发三次握手。

1.1 第一次握手:
客户端发送一个TCP的SYN标志位置1的包指明客户打算连接的服务器的端口,以及初始序号X,保存在包头的序列号(Sequence Number)字段里。

1.2 第二次握手:
服务器发回确认包(ACK)应答。即SYN标志位和ACK标志位均为1同时,将确认序号(Acknowledgement Number)设置为客户的I S N加1以.即X+1。

1.3 第三次握手:
客户端再次发送确认包(ACK) SYN标志位为0,ACK标志位为1.并且把服务器发来ACK的序号字段+1,放在确定字段中发送给对方.并且在数据段放写ISN的+1

1.4 SYN攻击
●在三次握手过程中,服务器发送SYN-ACK之后,收到客户端的ACK之前的TCP连接称为半连接(half-open connect).此时服务器处于Syn_ RECV状态.当收到ACK后,服务器转入ESTABLISHED状态.

●Syn攻击就是 攻击客户端 在短时间内伪造大量不存在的IP地址,向服务器不断地发送syn包,服务器回复确认包,并等待客户的确认,由于源地址是不存在的,服务器需要不断的重发直 至超时,这些伪造的SYN包将长时间占用未连接队列,正常的SYN请求被丢弃,目标系统运行缓慢,严重者引起网络堵塞甚至系统瘫痪。

●Syn攻击是一个典型的DDOS攻击。检测SYN攻击非常的方便,当你在服务器上看到大量的半连接状态时,特别是源IP地址是随机的,基本上可以断定这是一次SYN攻击.在Linux下可以如下命令检测是否被Syn攻击
netstat -n -p TCP | grep SYN_RECV
一般较新的TCP/IP协议栈都对这一过程进行修正来防范Syn攻击,修改tcp协议实现。主要方法有SynAttackProtect保护机制、SYN cookies技术、增加最大半连接和缩短超时时间等.
但是不能完全防范syn攻击。

2、TCP四次挥手
TCP的连接的拆除需要发送四个包,因此称为四次挥手(four-way handshake)。客户端或服务器均可主动发起挥手动作,在socket编程中,任何一方执行close()操作即可产生挥手操作。

3、补充tcp连接过程

在TCP/IP协议中,TCP协议提供可靠的连接服务,采用三次握手建立一个连接

(1) 第一次握手:建立连接时,客户端A发送SYN包(SYN=j)到服务器B,并进入SYN_SEND状态,等待服务器B确认。

(2) 第二次握手:服务器B收到SYN包,必须确认客户A的SYN(ACK=j+1),同时自己也发送一个SYN包(SYN=k),即SYN+ACK包,此时服务器B进入SYN_RECV状态。

(3) 第三次握手:客户端A收到服务器B的SYN+ACK包,向服务器B发送确认包ACK(ACK=k+1),此包发送完毕,客户端A和服务器B进入ESTABLISHED状态,完成三次握手。

完成三次握手,客户端与服务器开始传送数据。

●由于TCP连接是全双工的,因此每个方向都必须单独进行关闭。这个原则是当一方完成它的数据发送任务后就能发送一个FIN来终止这个方向的连接。收到一个 FIN只意味着这一方向上没有数据流动,一个TCP连接在收到一个FIN后仍能发送数据。首先进行关闭的一方将执行主动关闭,而另一方执行被动关闭。

(1)客户端A发送一个FIN,用来关闭客户A到服务器B的数据传送(报文段4)。

(2)服务器B收到这个FIN,它发回一个ACK,确认序号为收到的序号加1(报文段5)。和SYN一样,一个FIN将占用一个序号。

(3)服务器B关闭与客户端A的连接,发送一个FIN给客户端A(报文段6)。

(4)客户端A发回ACK报文确认,并将确认序号设置为收到序号加1(报文段7)。

4、问题:
4.1为什么建立连接协议是三次握手,而关闭连接却是四次握手呢?

这是因为服务端的LISTEN状态下的SOCKET当收到SYN报文的连接请求后,它可以把ACK和SYN(ACK起应答作用,而SYN起同步作用)放在一个报文里来发送。但关闭连接时,当收到对方的FIN报文通知时,它仅仅表示对方没有数据发送给你了;但未必你所有的数据都全部发送给对方了,所以你可能未必会马上会关闭SOCKET,也即你可能还需要发送一些数据给对方之后,再发送FIN报文给对方来表示你同意现在可以关闭连接了,所以它这里的ACK报文和FIN报文多数情况下都是分开发送的。

4.2为什么TIME_WAIT状态还需要等2MSL后才能返回到CLOSED状态?

●TIME_WAIT状态由两个存在的理由。

(1)可靠的实现TCP全双工链接的终止。

这是因为虽然双方都同意关闭连接了,而且握手的4个报文也都协调和发送完毕,按理可以直接回到CLOSED状态(就好比从SYN_ SEND状态到ESTABLISH状态那样);但是因为我们必须要假想网络是不可靠的,你无法保证你最后发送的ACK报文会一定被对方收到,因此对方处于LAST_ ACK状态下的SOCKET可能会因为超时未收到ACK报文,而重发FIN报文,所以这个TIME_WAIT状态的作用就是用来重发可能丢失的ACK报文。

(2)允许老的重复的分节在网络中消逝。

假 设在12.106.32.254的1500端口和206.168.1.112.219的21端口之间有一个TCP连接。我们关闭这个链接,过一段时间后在 相同的IP地址和端口建立另一个连接。后一个链接成为前一个的化身。因为它们的IP地址和端口号都相同。TCP必须防止来自某一个连接的老的重复分组在连 接已经终止后再现,从而被误解成属于同一链接的某一个某一个新的化身。为做到这一点,TCP将不给处于TIME_WAIT状态的链接发起新的化身。既然 TIME_WAIT状态的持续时间是MSL的2倍,这就足以让某个方向上的分组最多存活msl秒即被丢弃,另一个方向上的应答最多存活msl秒也被丢弃。 通过实施这个规则,我们就能保证每成功建立一个TCP连接时。来自该链接先前化身的重复分组都已经在网络中消逝了。

4.3为什么不能用两次握手进行连接?

●我们知道,3次握手完成两个重要的功能,既要双方做好发送数据的准备工作(双方都知道彼此已准备好),也要允许双方就初始序列号进行协商,这个序列号在握手过程中被发送和确认。
●现在把三次握手改成仅需要两次握手,死锁是可能发生的。作为例子,考虑计算机S和C之间的通信,假定C给S发送一个连接请求分组,S收到了这个分组,并发 送了确认应答分组。按照两次握手的协定,S认为连接已经成功地建立了,可以开始发送数据分组。可是,C在S的应答分组在传输中被丢失的情况下,将不知道S 是否已准备好,不知道S建立什么样的序列号,C甚至怀疑S是否收到自己的连接请求分组。在这种情况下,C认为连接还未建立成功,将忽略S发来的任何数据分 组,只等待连接确认应答分组。而S在发出的分组超时后,重复发送同样的分组。这样就形成了死锁。

补充:

a. 默认情况下(不改变socket选项),当你调用close( or closesocket,以下说close不再重复)时,如果发送缓冲中还有数据,TCP会继续把数据发送完。

b. 发送了FIN只是表示这端不能继续发送数据(应用层不能再调用send发送),但是还可以接收数据。

c. 应用层如何知道对端关闭?通常,在最简单的阻塞模型中,当你调用recv时,如果返回0,则表示对端关闭。在这个时候通常的做法就是也调用close,那么TCP层就发送FIN,继续完成四次握手。如果你不调用close,那么对端就会处于FIN_WAIT_2状态,而本端则会处于CLOSE_WAIT状态。这个可以写代码试试。

d. 在很多时候,TCP连接的断开都会由TCP层自动进行,例如你CTRL+C终止你的程序,TCP连接依然会正常关闭

核心:
三次握手——保证双方准备资源
四次挥手——释放资源