Python_网络编程总结

目录

计算机网络就是把各个计算机连接到一起,让网络中的计算机可以互相通信。网络编程就是如何在程序中实现两台计算机的通信。本次将讲解网络的基础知识,包括比较常见的 TCP 协议和 UDP 协议,以及如何使用 TCP 编程和 UDP 编程。

  1. 网络基础

当今的时代是一个网络的时代,网络无处不在。而我们前面学习编写的程序都是单机的,即不能和其他电脑上的程序进行通信。为了实现不同电脑之间的通信,就需要使用网络编程。下面,我们来了解一下网络相关的基础知识。

1.1 为什么要使用通信协议

计算机为了联网,就必须规定通信协议,早期的计算机网络,都是由各厂商自己规定一套协议,IBM、Apple 和 Microsoft 都有各自的网络协议,互不兼容,这就好比一群人有的说英语,有的说中文,有的说德语,说同一种语言的人可以交流,不同的语言之间就不行了,如图 17.1 所示。 为了把全世界的所有不同类型的计算机都连接起来,就必须规定一套全球通用的协议,为了实现互联网这个目标,互联网协议簇 (Internet Protocol Suite) 即通用协议标准出现了。Internet 是由 inter 和 net 两个单词组合起来的,原意就是连接 “网络” 的网络,有了 Internet, 任何私有网络,只要支持这个协议,就可以接入互联网。

1.2 TCP/IP 简介

因为互联网协议包含了上百种协议标准,但是最重要的两个协议是 TCP 和 IP 协议,所以,大家把互联网的协议简称 TCP/IP 协议。

1.2.1 IP 协议

在通信时,通信双方必须知道对方的标识,好比发送快递必须知道对方的地址。互联网上每个计算机的唯一标识就是 IP 地址。IP 地址实际上是一一个 32 位整数 (称为 IPv4),它是以字符串表示的 IP 地址, 如 172.16.254.1,实际上是把 32 位整数按 8 位分组后的数字表示,目的是便于阅读,如图所示。 IP 协议负责把数据从一台计算机通过网络发送到另一台计算机。数据被分割成一一小块一一小块,类似于将一个大包裹拆分成几个小包裹,然后通过 IP 包发送出去。由于互联网链路复杂,两台计算机之间经常有多条线路,因此,路由器就负责决定如何把一个 IP 包转发出去。IP 包的特点是按块发送,途经多个路由,但不保证都能到达,也不能保证顺序到达。 172.16.106.128 是设备在网络中的 IP 地址 127.0.0.1 表示本机地址,提示:如果和自己的电脑通信就可以使用该地址。 127.0.0.1 该地址对应的域名是 localhost,域名是 ip 地址的别名,通过域名能解析出一个对应的 ip 地址。 ping www.baidu.com 检查是否能上公网 ping 当前局域网的 ip 地址 检查是否在同一个局域网内 ping 127.0.0.1 检查本地网卡是否正常

简而言之,IP 地址的作用就是用来标识网络中唯一的一台设备的,也就是说通过 IP 地址能够找到网络中某台设备

1.2.2 端口和端口号的介绍

不同电脑上的飞秋之间进行数据通信,它是如何保证把数据给飞秋而不是给其它软件呢? 其实,每运行一个网络程序都会有一个端口,想要给对应的程序发送数据,找到对应的端口即可。 端口是传输数据的通道,好比教室的门,是数据传输必经之路。那么如何准确的找到对应的端口呢? 其实,每一个端口都会有一个对应的端口号,好比每个教室的门都有一个门牌号,想要找到端口通过端口号即可。端口号效果图: 操作系统为了统一管理这么多端口,就对端口进行了编号,这就是端口号,端口号其实就是一个数字,好比我们现实生活中的门牌号, 端口号有 65536 个。那么最终飞秋之间进行数据通信的流程是这样的,通过 ip 地址找到对应的设备,通过端口号找到对应的端口,然后通过端口把数据传输给应用程序。最终通信流程效果图: 端口号可以标识唯一的一个端口。端口号可以分为知名端口号和动态端口号。知名端口号是指众所周知的端口号,范围从0到1023。这些端口号一般固定分配给一些服务,比如 21 端口分配给 FTP(文件传输协议) 服务,25 端口分配给 SMTP(简单邮件传输协议)服务,80 端口分配给 HTTP 服务。一般程序员开发应用程序使用端口号称为动态端口号, 范围是从1024到65535。如果程序员开发的程序没有设置端口号,操作系统会在动态端口号这个范围内随机生成一个给开发的应用程序使用。当运行一个程序默认会有一个端口号,当这个程序退出时,所占用的这个端口号就会被释放。

1.2.3 TCP 协议

之前我们学习了 IP 地址和端口号,通过 IP 地址能够找到对应的设备,然后再通过端口号找到对应的端口,再通过端口把数据传输给应用程序,这里要注意,数据不能随便发送,在发送之前还需要选择一个对应的传输协议,保证程序之间按照指定的传输规则进行数据的通信, 而这个传输协议就是我们即将要学习的 TCP。TCP 的英文全拼 (Transmission Control Protocol) 简称传输控制协议,它是一种面向连接的、可靠的、基于字节流的传输层通信协议。 TCP 协议是建立在 IP 协议之上的。TCP 协议负责在两台计算机之间建立可靠连接,保证数据包按顺序到达。TCP 协议会通过 3 次握手建立可靠连接,如图 17.3 所示。 然后需要对每个 IP 包进行编号, 确保对方按顺序收到, 如果包丢掉了, 就自动重发。如图 17.4 所示。 许多常用的更高级的协议都是建立在 TCP 协议基础上的, 比如用于浏览器的 HTTP 协议、发送邮件的 SMTP 协议等。一个 TCP 报文除了包含要传输的数据外, 还包含源 IP 地址和目标 IP 地址、源端口和目标端口。

端口有什么作用?

  • 在两台计算机通信时, 只发送 IP 地址是不够的, 因为同一台计算机上运行着多个网络程序。一个 TCP 报文来了之后, 到底是交给浏览器还是 QQ, 就需要端口号来区分。每个网络程序都向操作系统申请唯一的端口号, 这样, 两个进程在两台计算机之间建立网络连接就需要各自的 IP 地址和各自的端口号。

一个进程也可能同时与多个计算机建立链接, 因此它会申请很多端口。端口号不是随意使用的, 而是按照一定的规定进行分配。例如, 80 端口分配给 HTTP 服务, 21 端口分配给 FTP 服务。

TCP 的特点:

  • 面向连接

    • 通信双方必须先建立好连接才能进行数据的传输,数据传输完成后,双方必须断开此连接,以释放系统资源。

  • 可靠传输

    • TCP 采用发送应答机制

    • 超时重传

    • 错误校验

    • 流量控制和阻塞管理

总而言之,TCP 是一个稳定、可靠的传输协议,常用于对数据进行准确无误的传输,比如: 文件下载,浏览器上网。

1.3 UDP 简介

相对 TCP 协议, UDP 协议则是面向无连接的协议。使用 UDP 协议时, 不需要建立连接, 只需要知道对方的 IP 地址和端口号, 就可以直接发数据包。但是, 数据无法保证一定到达。虽然用 UDP 传输数据不可靠, 但它的优点是比 TCP 协议的速度快。对于不要求可靠到达的数据而言, 就可以使用 UDP 协议。TCP 协议和 UDP 协议的区别如图 17.5 所示。

1.4 Socket 简介

为了让两个程序通过网络进行通信, 二者均必须使用 Socket 套接字。 Socket 的英文原义是 “孔” 或“插座”’通常也称作“套接字”, 用于描述 IP 地址和端口, 它是一个通信链的句柄, 可以用来实现不同虚拟机或不同计算机之间的通信, 如图 17.6 所示。在 Internet 上的主机上一般运行了多个服务软件, 同时提供几种服务。每种服务都打开一个 Socket, 并绑定到一个端口上, 不同的端口对应于不同的服务。 Socket 正如其英文原意那样, 像一个多孔插座。一台主机犹如布满各种插座的房间, 每个插座有一个编号, 有的插座提供 220 伏交流电, 有的提供 110 伏交流电, 有的则提供有线电视节目。客户软件将插头插到不同编号的插座, 就可以得到不同的服务。 在 Python 中使用 socket 模块的 socket() 函数就可以完成,语法格式如下:

s = socket.socket(AddressFamily, Type)

函数 socket.socket 创建一个 socket, 返回该 socket 的描述符, 该函数带有两个参数:

  • Address family: 可以选择 AF_INET(用于 Internet 进程间通信) 或者 AF _UNIX(用于同一台机器进程间通信), 实际工作中常用 AF_INET。

  • Type: 套接字类型, 可以是 SOCK_STREAM(流式套接字, 主要用于 TCP 协议) 或者 SOCK_DGRAM(数据报套接字, 主要用于 UDP 协议)。

例如, 为了创建 TCP/IP 套接字, 可以用下面的方式调用 socket.socket():

tcp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

同样, 为了创建 UDP/IP 套接字, 需要执行以下语句:

udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

创建完成后, 生成一个 socket 对象, socket 对象的主要方法如表 17.1 所示。

  1. TCP 编程

由于 TCP 连接具有安全可靠的特性, 所以 TCP 应用更为广泛。创建 TCP 连接时, 主动发起连接的叫客户端, 被动响应连接的叫服务器。例如, 当我们在浏览器中访问淘宝网站时, 我们自己的计算机就是客户端, 浏览器会主动向淘宝的服务器发起连接。如果一切顺利, 淘宝的服务器接收了我们的连接, 一个 TCP 连接就建立起来了, 后面的通信就是发送网页内容了。

2.1 创建 TCP 服务器

创建 TCP 服务器的过程, 类似于生活中接听电话的过程。如果要接听别人的来电, 首先需要购买一部手机, 然后安装手机卡。接下来, 设置手机为接听状态, 最后静等对方来电。如同接听电话过程一样, 在程序中, 如果想要完成一个 TCP 服务器的功能, 需要的流程如下:

  1. 使用 socket() 创建一个套接字

  2. 使用 bind() 绑定 IP 和 port

  3. 使用 listen() 使套接字变为可被动连接

  4. 使用 accept() 等待客户端的连接

  5. 使用 recv/send() 接收发送数据

例如,使用 socket 模块,通过网络调试助手向本地服务器 (IP 地址为169.254.62.15) 发起请求,服务器接到请求,向网络调试助手发送 "Hello World"。网络调试助手安装包:

链接:https://pan.baidu.com/s/1N3UgW3tZnqfBEEFbB3huZg
提取码: wxpn

具体代码如下:

# -*- coding: utf-8 -*-
# @Time   : 2020/2/26 23:00
# @Author : 我就是任性-Amo
# @FileName: 02-tcp服务端程序开发.py
# @Software: PyCharm
# @Blog   :https://blog.csdn.net/xw1680


import socket # 导入socket模块

if __name__ == '__main__':
  # 1.创建TCP服务器套接字
  # AF_INET: ipv4 AF_INET6: ipv6
  tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  # 设置端口复用,让程序退出立端口号立即释放
  tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)

  # 2.绑定端口号
  # 元组中的第一个元素表示ip地址,一般不用指定,表示本机的任何一个ip即可
  # 元组中的第二个元素表示端口
  tcp_server_socket.bind(("", 9090))
  # 3.设置监听
  # 128: 表示最大等待建立连接的个数
  tcp_server_socket.listen(128)
  # 4.等待客户端的链接请求
  # 注意点: 每次当客户端和服务端建立连接成功都会返回一个新的套接字
  # tcp_server_socket只负责等待接收客户端的连接请求,收发消息不使用该套接字
  new_socket, ip_port = tcp_server_socket.accept()
  # 代码执行到此说明客户端和服务端建立连接成功
  print("客户端的ip和端口号为:", ip_port)
  # 5.接受客户端的数据
  # 收发消息都使用返回的这个新的套接字
  recv_data = new_socket.recv(1024)
  # 对二进制数据进行解码变成字符串
  recv_content = recv_data.decode("gbk")
  print("接受客户端的数据为:", recv_content)
  send_content = "Hello World"
  # 对字符串进行编码
  send_data = send_content.encode()
  # 6.发送数据到客户端
  new_socket.send(send_data)
  # 关闭服务与客户端套接字,表示和客户端终止通信
  new_socket.close()
  # 7.关闭服务端套接字,表示服务端以后不再等待客户端的链接请求
  tcp_server_socket.close()

运行结果如下: 说明: 当客户端和服务端建立连接后,服务端程序退出后端口号不会立即释放,需要等待大概 1-2 分钟。 解决办法有两种:

  1. 更换服务端端口号

  2. 设置端口号复用 (推荐大家使用),也就是说让服务端程序退出后端口号立即释放。

# 参数1: 表示当前套接字
# 参数2: 设置端口号复用选项
# 参数3: 设置端口号复用选项对应的值
tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)

2.2 创建 TCP 客户端

TCP 的客户端要比服务器简单很多,如果说服务器是需要自己买手机、插手机卡、设置铃声、等待别人打电话流程的话,那么客户端就只需要找一个电话亭, 拿起电话拨打即可,流程要少很多。在上个实例当中,我们使用网络调试助手作为客户端接收数据,下面, 创建一个 TCP 客户端,通过该客户端向服务器发送和接收消息。具体代码如下:

# -*- coding: utf-8 -*-
# @Time   : 2020/2/26 20:18
# @Author : 我就是任性-Amo
# @FileName: 01-tcp客户端程序开发.py
# @Software: PyCharm
# @Blog   :https://blog.csdn.net/xw1680

import socket # 导入socket模块

if __name__ == '__main__':
  # 1.创建tcp客户端套接字
  tcp_client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  # 提示: 客户端程序不强制要求绑定端口号
  tcp_client_socket.bind(("", 9090))
  # 2.和服务端套接字建立连接
  tcp_client_socket.connect(("172.16.106.128", 9090))
  send_content = "你好,我是客户端小白!" # 这里可以改为input()输入
  # 对字符串进程编码成为二进制数据
  send_data = send_content.encode("utf-8")
  # 3.发送数据到服务端
  # windows里面的网络调试助手使用的是gbk编码
  # linux 里面的网络调试助手使用utf-8编码
  tcp_client_socket.send(send_data)
  # 4.接受服务端数据
  # 1024:表示每次接收的最大字节数
  recv_data = tcp_client_socket.recv(1024)
  # 对二进制数据进行解码
  recv_content = recv_data.decode("utf-8")
  print("接收服务端的数据为:", recv_content)
  # 5.关闭套接字
  tcp_client_socket.close()

运行结果如下:

2.3 TCP 网络应用程序开发流程总结

2.3.1 客户端

步骤说明:

  1. 创建客户端套接字对象

  2. 和服务端套接字建立连接

  3. 发送数据

  4. 接收数据

  5. 关闭客户端套接字

2.3.2 服务端

步骤说明:

  1. 创建服务端端套接字对象

  2. 绑定端口号

  3. 设置监听

  4. 等待接受客户端的连接请求

  5. 接收数据

  6. 发送数据

  7. 关闭套接

2.4 TCP 网络应用程序的注意点

  1. 当 TCP 客户端程序想要和 TCP 服务端程序进行通信的时候必须要先建立连接

  2. TCP 客户端程序一般不需要绑定端口号,因为客户端是主动发起建立连接的。

  3. TCP 服务端程序必须绑定端口号,否则客户端找不到这个 TCP 服务端程序。

  4. listen后的套接字是被动套接字,只负责接收新的客户端的连接请求,不能收发消息。

  5. 当 TCP 客户端程序和 TCP 服务端程序连接成功后, TCP 服务器端程序会产生一个新的套接字,收发客户端消息使用该套接字。

  6. 关闭accept返回的套接字意味着和这个客户端已经通信完毕。

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

  8. 当客户端的套接字调用close后,服务器端的recv会解阻塞,返回的数据长度为 0,服务端可以通过返回数据的长度来判断客户端是否已经下线,反之服务端关闭套接字,客户端的recv也会解阻塞,返回的数据长度也为 0。

2.5 多任务版 TCP 服务端程序开发

2.5.1 需求

目前我们开发的 TCP 服务端程序只能服务于一个客户端,如何开发一个多任务版的 TCP 服务端程序能够服务于多个客户端呢? 完成多任务,可以使用线程,比进程更加节省内存资源。

2.5.2 具体实现步骤

  1. 编写一个 TCP 服务端程序,循环等待接受客户端的连接请求

  2. 当客户端和服务端建立连接成功,创建子线程,使用子线程专门处理客户端的请求,防止主线程阻塞

  3. 把创建的子线程设置成为守护主线程,防止主线程无法退出。

2.5.3 多任务 TCP 服务端程序的示例代码

# -*- coding: utf-8 -*-
# @Time   : 2020/3/9 23:33
# @Author : 我就是任性-Amo
# @FileName: 03-多任务TCP服务端程序.py
# @Software: PyCharm
# @Blog   :https://blog.csdn.net/xw1680


import socket
import threading


# 处理客户端请求的任务
def handle_client_request(ip_port, new_client):
  print("客户端的ip和端口号为:", ip_port)
  # 5. 接收客户端的数据
  # 收发消息都使用返回的这个新的套接字
  # 循环接收客户端的消息
  while True:
      recv_data = new_client.recv(1024)
      if recv_data:
          print("接收的数据长度是:", len(recv_data))
          # 对二进制数据进行解码变成字符串
          recv_content = recv_data.decode("utf-8")
          print("接收客户端的数据为:", recv_content, ip_port)
          send_content = "问题正在处理中..."
          # 对字符串进行编码
          send_data = send_content.encode("utf-8")
          # 6. 发送数据到客户端
          new_client.send(send_data)
      else:
          # 客户端关闭连接
          print("客户端下线了:", ip_port)
          break
  # 关闭服务与客户端套接字,表示和客户端终止通信
  new_client.close()


if __name__ == '__main__':
  # 1.创建socket套接字
  tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  # 设置端口号复用
  tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
  # 2.绑定ip地址和端口号
  tcp_server_socket.bind(("", 9090))
  # 3.设置监听
  tcp_server_socket.listen(128)
  # 4.循环等待客户端连接
  while True:
      new_client, ip_port = tcp_server_socket.accept()
      # 当客户端和服务端建立连接成功,创建子线程,让子线程专门负责接收客户端的消息
      sub_thread = threading.Thread(target=handle_client_request, args=(ip_port, new_client))
      # 设置守护主线程,主线程退出子线程直接销毁
      sub_thread.setDaemon(True)
      # 启动子线程执行对应的任务
      sub_thread.start()
  # tcp_server_socket.close()  
  # 因为服务端的程序需要一直运行,所以关闭服务端套接字的代码可以省略不写

2.6 socket 之 send 和 recv 原理剖析

2.6.1 认识 TCP socket 的发送和接收缓冲区

当创建一个 TCP socket 对象的时候会有一个发送缓冲区和一个接收缓冲区,这个发送和接收缓冲区指的就是内存中的一片空间。

2.6.2 send 原理剖析

send 是不是直接把数据发给服务端?

不是,要想发数据,必须得通过网卡发送数据,应用程序是无法直接通过网卡发送数据的,它需要调用操作系统接口,也就是说,应用程序把发送的数据先写入到发送缓冲区 (内存中的一片空间),再由操作系统控制网卡把发送缓冲区的数据发送给服务端网卡 。

2.6.3 recv 原理剖析

recv 是不是直接从客户端接收数据?

不是,应用软件是无法直接通过网卡接收数据的,它需要调用操作系统接口,由操作系统通过网卡接收数据,把接收的数据写入到接收缓冲区 (内存中的一片空间),应用程序再从接收缓存区获取客户端发送的数据。

2.6.4 send 和 recv 原理剖析图

说明:

  1. 发送数据是发送到发送缓冲区

  2. 接收数据是从接收缓冲区获取

2.6.5 小结

不管是 recv 还是 send 都不是直接接收到对方的数据和发送数据到对方,发送数据会写入到发送缓冲区,接收数据是从接收缓冲区来读取,发送数据和接收数据最终是由操作系统控制网卡来完成。

  1. UDP 编程

UDP 是面向消息的协议,如果通信时不需要建立连接,数据的传输自然是不可靠的,UDP 一般用于多点通信和实时的数据业务,例如:

  1. 语音广播

  2. 视频

  3. 聊天软件

  4. TFTP (简单文件传送)

  5. SNMP (简单网络管理协议)

  6. RIP (路由信息协议,如报告股票市场、航空信息)

  7. DNS(域名解释)

和 TCP 类似,使用 UDP 的通信双方也分为客户端和服务器。

3.1 创建 UDP 服务器

UDP 服务器不需要 TCP 服务器那么多的设置,因为它们不是面向连接的。除了等待传入的连接之外,几乎不需要做其他工作。下面我们来实现一个将摄氏温度转化为华氏温度的功能。例如,在客户端输入要转换的摄氏温度,然后发送给服务器,服务器根据转化公式,将摄氏温度转化为华氏温度,发送给客户端显示。创建 udp_server.py 文件,实现 UDP 服务器。具体代码如下:

# -*- coding: utf-8 -*-
# @Time : 2020/3/10 00:00
# @Author : 我就是任性-Amo
# @FileName: 04-创建udp服务器.py
# @Software: PyCharm
# @Blog :https://blog.csdn.net/xw1680


import socket # 导入socket模块

if __name__ == '__main__':
# 1.创建udp套接字
udp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 2.绑定地址(ip,port)到套接字
udp_server_socket.bind(("", 8888))
print("绑定udp到8888端口~")
# 3.接收数据
data, addr = udp_server_socket.recvfrom(1024)
data = float(data) * 1.8 + 32 # 转换公式
print(data)
print(addr)
send_data = "转换后的温度(单位:华式温度):" + str(data)
print("Received from %s:%s." % addr)
# 4.发送给客户端
udp_server_socket.sendto(send_data.encode(), addr)
# 5.关闭服务器端套接字
udp_server_socket.close()

上述代码中,使用 socket.socket() 函数创建套接字, 其中设置参数为socket.SOCK_ DGRAM, 表明创建的是 UDP 套接字。此外需要注意,udp_server_socket.recvfrom() 函数生成的 data 数据类型是 byte, 不能直接进行四则运算,需要将其转化为 float 浮点型数据。最后在使用 sendto() 函数发送数据时,发送的数据必须是 byte 类型,所以需要使用 encode() 函数将字符串转化为 byte 类型。运行结果如图所示:

3.2 创建 UDP 客户端

创建一个 UDP 客户端程序的流程很简单,具体步骤如下:

  1. 创建客户端套接字

  2. 发送 / 接收数据

  3. 关闭套接字

下面根据上面小节的实例,实现 UDP 客户端,用户接收转换后的华氏温度。具体代码如下:

# -*- coding: utf-8 -*-
# @Time : 2020/3/10 00:11
# @Author : 我就是任性-Amo
# @FileName: 05-创建udp客户端.py
# @Software: PyCharm
# @Blog :https://blog.csdn.net/xw1680

import socket # 导入socket模块

if __name__ == '__main__':
# 1.创建udp套接字
udp_client_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 2.输入要转换的温度
data = input("请输入要转换的温度(单位: 摄氏度):")
# 3.发送数据
udp_client_socket.sendto(data.encode(), ("127.0.0.1", 8888))
# 4.打印接收数据
print(udp_client_socket.recv(1024).decode())
# 5.关闭套接字
udp_client_socket.close()

在上述代码中,主要就是接收的数据和发送的数据类型都是 byte。所以发送时,使用 encode() 函数将字符串转化为 byte。而在输出时,使用 decode() 函数将 byte 类型数据转换为字符串,方便用户阅读。

在两个 cmd 窗口中分别运行 04 - 创建 udp 服务器. py 和 05 - 创建 udp 客户端. py 文件,然后在 05 - 创建 udp 客户端. py 窗口中输入要转换的摄氏度,04 - 创建 udp 服务器. py 窗口会立即显示转换后的华氏温度。如图所示。

  1. 练习

4.1 输出本地计算机名称与本地计算机的 IP 地址

编写一个程序,可以输出本地计算机的计算机名称和本地计算机的 IP 地址。输出效果如图所示。 具体代码如下:

# -*- coding: utf-8 -*-
# @Time : 2020/3/10 00:51
# @Author : 我就是任性-Amo
# @FileName: 06-输出本地计算机名称与本地计算机的IP地址.py
# @Software: PyCharm
# @Blog :https://blog.csdn.net/xw1680

import socket

host_name = socket.gethostname()
print("你的计算机名称为: " + host_name)
host_ip = socket.gethostbyname(host_name)
print("你的计算机IP地址为: " + host_ip)

4.2 获取远程主机的 IP 地址

编写一个程序,输出如图 17.19 所示互联网公司官网 (远程主机) 的 IP 地址(大的互联网公司服务器主机分布在不同区域、不同的机房,所以用户所在地不同,输出的 IP 地址会有所不同)。 具体代码如下:

# -*- coding: utf-8 -*-
# @Time : 2020/3/10 00:59
# @Author : 我就是任性-Amo
# @FileName: 07-获取远程主机的ip地址.py
# @Software: PyCharm
# @Blog :https://blog.csdn.net/xw1680

import socket

host_name = "www.jd.com"
host_ip = socket.gethostbyname(host_name)
print("www.jd.com: " + host_ip)

host_name = "www.baidu.com"
host_ip = socket.gethostbyname(host_name)
print("www.baidu.com: " + host_ip)

host_name = "www.python.org"
host_ip = socket.gethostbyname(host_name)
print("www.python.org: " + host_ip)

host_name = "www.taobao.com"
host_ip = socket.gethostbyname(host_name)
print("www.taobao.com: " + host_ip)

4.3 模拟网络嗅探器

网络嗅探器可以检测本机所在局域网内的网络流量和数据包收发情况,实现网络嗅探时,需要将网卡设置为混杂模式,请尝试用 Python 实现一个简单的网络嗅探器。

# -*- coding: utf-8 -*-
# @Time : 2020/3/10 01:19
# @Author : 我就是任性-Amo
# @FileName: 08-模拟网络嗅探器.py
# @Software: PyCharm
# @Blog :https://blog.csdn.net/xw1680

import socket
import os
import struct
from ctypes import *

host = "192.168.1.164"


class IP(Structure):
_fields_ = [
("ihl", c_ubyte, 4),
("version", c_ubyte, 4),
("tos", c_ubyte),
("len", c_ushort),
("id", c_ushort),
("offset", c_ubyte),
("ttl", c_ubyte),
("protocol_num", c_ubyte),
("sum", c_ushort),
("src", c_ulong),
("dst", c_ulong)
]

def __new__(self, socket_buffer=None):
return self.from_buffer_copy(socket_buffer)

def __init__(self, socket_buffer=None):
self.protocol_map = {1: "ICMP", 6: "TCP", 17: "UDP"}
self.src_address = socket.inet_ntoa(struct.pack("<L", self.src))
self.dst_address = socket.inet_ntoa(struct.pack("<L", self.dst))
try:
self.protocol = self.protocol_map[self.protocol_num]
except:
self.protocol = str(self.protocol_num)


if os.name == "nt": # WIN平台
socket_protocol = socket.IPPROTO_IP
else:
socket_protocol = socket.IPPROTO_ICMP

sniffer = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket_protocol)

sniffer.bind((host, 0))

sniffer.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1)

# 在WIN平台上,需要设置IOCTL以启用混杂模式
if os.name == "nt":
sniffer.ioctl(socket.SIO_RCVALL, socket.RCVALL_ON)

try:
while True:
raw_buffer = sniffer.recvfrom(65565)[0]
ip_header = IP(raw_buffer[0:20])
print("Protocol:%s %s -> %s" % (ip_header.protocol, ip_header.src_address, ip_header.dst_address))
except KeyboardInterrupt:
if os.name == "nt":
sniffer.ioctl(socket.SIO_RCVALL, socket.RCVALL_OFF)

4.4 扫描并输出局域网占用的 IP 地址

局域网中,用户在设置 IP 地址时,为了避免 IP 地址发生冲突,通常需要很快地找出局域网内已经使用的 IP 地址,请尝试用 Python 编写一个局域网 IP 地址扫描程序,将局域网中已经占用的 IP 及对应计算机名显示出来。

import sys, os, socket, string
import threading

list_of_name = []
list_of_ip = [] # 存放结果
thread_pool = []


def showInfo():
print("""
命令格式:LAN_ip_hostname -all startip
LAN_ip_hostname -ip ipaddr
LAN_ip_hostname -hostname hostname
说明:-all 扫描局域网中所有IP对应的hostname,需要起始IP,如192.168.0.1
-ip 获取指定IP的hostname
-hostname 根据主机名,得到其IP地址
""")


def lanAll(startip):
index = startip.rfind('.') # 找最右边的.的索引
ipfirstpart = startip[0:index + 1] # ip地址中前三位,如192.168.1
intstart = int(startip[index + 1:]) # ip地址最后一位,转为int型

f = range(intstart, 255)

global g_mutex # 互斥锁。不能定义称全局变量,否则,目标函数不认同
g_mutex = threading.Lock() # 初始化互斥锁

for iplastpart in f:
targetip = ipfirstpart + str(iplastpart) # 拼接ip
# 创建线程对象,存为thread。线程要执行的函数由target指定,args指定参数,可以是元组~。线程号从1开始
thread = threading.Thread(target=lanIp2Name, args=(targetip,))
thread_pool.append(thread)
thread.start()

# 阻塞主线程。collect all threads
pos = intstart
for pos in f:
threading.Thread.join(thread_pool[pos - intstart])

# 输出结果
hosts = range(0, len(list_of_name))
for host in hosts:
print(list_of_ip[host], ' ====> ', list_of_name[host])
print('Find ', len(list_of_name), ' Hosts.Done!')


def lanIp2Name(ip):
try:
(name, aliaslist, addresslist) = socket.gethostbyaddr(ip)
except:
return

global g_mutex # 再次声明
g_mutex.acquire()
######################受互斥量保护区代码##################################
list_of_name.append(name)
list_of_ip.append(ip)
########################################################################
g_mutex.release()


def lanIpToName(ip):
try:
(name, aliaslist, addresslist) = socket.gethostbyaddr(ip)
except:
return
print("%s====>%s" % (addresslist, name))


def lanName2Ip(name):
targetip = socket.gethostbyname(name)
print(name, " ====> ", targetip)


if '__main__' == __name__:
'''
sys.argv[]是用来获取命令行参数的,sys.argv[0]表示代码本身文件路径
array.count(x) 返回出现的x的次数
'''
if len(sys.argv) < 3:
print("参数错误")
showInfo()
exit(1)

cmds = ['-all', '-ip', '-hostname']

cmd = sys.argv[1] # 命令格式
target = sys.argv[2] # ip地址

if 0 == cmds.count(cmd): # 判断参数数量
print("参数错误啊")
showInfo()
exit(1)
else:
print('Start working,Please waiting...')
if cmd == '-all': # 输出所有IP和主机名
lanAll(target)

elif cmd == '-ip': # 根据当前IP输出主机名
lanIpToName(target)

elif cmd == '-hostname': # 根据当前主机名输出IP
lanName2Ip(target)
  1. 扩展学习

5.1 必背必记

  1. IP 协议 IP 协议负责把数据从一台计算机通过网络发送到另一台计算机。

  2. TCP 协议 TCP 协议则是建立在 IP 协议之上的。TCP 协议负责在两台计算机之间建立可靠连接,保证数据包按顺序到达。TCP 协议会通过 3 次握手建立可靠连接。

  3. UDP 协议 UDP 协议是面向无连接的协议。使用 UDP 协议时,不需要建立链接,只需要知道对方的 IP 地址和端口号,就可以直接发数据包。但是,数据无法保证一定到达。虽然用 UDP 传输数据不可靠,但它的优点是比 TCP 协议速度快。对于不要求可靠到达的数据,就可以使用 UDP 协议。

  4. bind() 方法 将 socket 对象绑定到一个地址,但这个地址必须是没有被占用的,否则会连接失败。这里的 address 一般是一个 ip,port 对,如 (‘localhost’, 10000)

  5. listen() 方法 监听,使得服务器能接收服务端连接,如果 backlog 指定了 (最少是 0,如果比 0 小,系统默认改成 0),限制可以连接的数量,如果没有指定,将指派一个默认的合理值。

  6. accept() 方法 接受一个连接,但前提是 socket 必须已经绑定了一个地址,在等待连接。返回值是一个 (conn, addresss) 的值对,这里的 conn 是一个 socket 对象,可以用来改送或接收数据。而 address 是连接另一端绑定的地址,socket.getpeername() 函数也能返回该地址。

  7. send() 方法 发送数据到 socket, 前提是已经连接到远程 socket, 返回值是发送数据的量,检查数据是否发送完。

  8. TFTP TFTP 是 TCP/IP 协议族中的一一个用来在客户端与服务器之间进行简单文件传输的协议。

  9. SNMP 简单网络管理协议 (SNMP),由一组网络管理的标准组成,包含一个应用层协议( application layer protocol)、数据库模型( database schema) 和一组 资源对象。

  10. RIP 路由信息协议 RIP (Routing Information Protocol) 是基于距离矢量算法的路由协议,利用跳数来作为计量标准。

  11. DNS DNS(DomainNameSystem, 域名系统),万维网上作为域名和 IP 地址相互映射的一个分布式数据库,能够使用户更方便的访问互联网,而不用去记住能够被机器直接读取的 IP 数串。

5.2 术语释疑

  1. 通信协议 通信协议是指双方实体完成通信或服务所必须遵循的规则和约定

  2. 网络协议 网络协议为计算机网络中进行数据交换而建立的规则、标准或约定的集合。

  3. 互联网协议簇 TCP/IP 协议簇是 Internet 的基础,也是当今最流行的组网形式。

  4. IP 包 IP 包的特点是按块发送,途径多个路由,但不保证都能到达,也不保证顺序到达。

  5. HTTP 协议 超文本传输协议 (HTTP, HyperText Transfer Protocol) 是互联网上应用最为广泛的一种网络协议。

  6. SMTP 协议 SMTP(SimpleMailTransferProtocol) 即简单邮件传输协议, 它是一组用于由源地址到目的地址传送邮件的规则,由它来控制信件的中转方式。

  7. 源端口 源端口 (Source Port) 是指一个发送的分组来自的 TCP/UDP 端口,当目的地端口是接收的一个分组的 TCP/UDP 端口。

  8. 目标端口 就是远程端口, 对方主机用哪个端口接收。

  9. 网络程序. 网络应用程序设计也叫网络编程,就是对信息的发送到接收, 中间传输为物理线路的作用。

  10. 客户端 客户端 (Client) 或称为用户端,是指与服务器相对应,为客户提供本地服务的程序。

  11. 服务器. 服务器,也称伺服器,是提供计算服务的设备。

原文地址:https://www.cnblogs.com/Gaimo/p/15435496.html