这是我参与 {第三届青训营-后端场}笔记创作活动的第3篇笔记~
基础知识
实现计算机间的通讯,首先要知道双方的IP地址。(连接到互联网的每台主机都需要分配32bit的全球唯一标识,即IP地址)IP地址是用来唯一标识计算机的一串编号,目前的主流版本是IPv4(通过点分十进制表示,形如192.168.0.1,占4B),但是由于科技的发展、时代的进步,终将会出现IP地址枯竭的可能性,因此亦要知道目前推广的IPv6版本。
那么问题来了,知道了IP地址就可以实现端对端的通讯了吗?其答案是否定的。试想以下,阁下的笔记本上安装了多个通信软件,那么从网络上接收到数据包时,如何实现正确交付给上层软件呢?其实,这一点也很好解决,引入端口号来标识应用进程。端口号不是什么玄乎的东西,就是为了解决以上场景问题的。端口号长度16bit(表示范围0-65535),端口只具有本地意义,是用来标识阁下笔记本应用程序的,互联网上的不同计算机端口号没有联系。根据端口号的范围,可以将端口分为两类:服务端使用的端口号和客户端使用的端口号。
1)服务端使用的端口号又分为两类,数值0-1023是熟知端口号,它们是IANA(互联网地址指派机构)已经指派给了TCP/IP最重要的应用程序,另一类称为登记端口号,数值为1024-49151,它们是提供给没有熟知端口号的应用程序使用的,但是使用之前应该在IANA登记,避免重复。
下面是一些常用的熟知端口号,应该牢记于心。
| 应用程序 | FTP | TELNET | SMTP | DNS | TFTP | HTTP | SNMP |
|---|---|---|---|---|---|---|---|
| 熟知端口 | 21 | 23 | 25 | 53 | 69 | 80 | 161 |
2)客户端用的端口号,数值为49152~65535。这类端口仅在客户端程序运行时动态选择的,又称为短暂端口号(临时端口号),通信结束后又可以分配给其他客户进程使用,非常方便。
上面已经介绍了IP和端口号,已经可以很好实现通信双方的数据正确交付问题,但是,我们再考虑以下场景:我们平时看电视、网络直播的时候是不是会出现卡顿?等到卡顿结束后是不是发现一部分页面丢失了?但是我们可能觉得丢失一部分页面没有关系,但是总体上要保持实时同步。又试想我们发送邮件,如果丢失了一部分信息,接收方是不是可能不知道我们的意图?这时候就需要保证数据不丢失、可靠。引入TCP和UDP这两种数据传输协议,目的就是为了适用于不同的场景。
UDP的特点如下:
1)UDP无须建立连接,因此不会引入建立连接的时延
2)无连接状态,不需要追踪收发缓存拥塞控制参数、序号和确认号
3)没有拥塞控制,网络状态不佳也不会影响主机的发送效率
TCP的特点如下:
1)提供可靠的交付服务,可以保证数据无差错、不丢失、不重复和有序
2)提供全双工通信,允许收发双方任意时刻发送数据,因此需要设有缓存来临时存放双向通信数据
TCP是面向连接的协议,因此每个TCP连接都需要有三个阶段:连接建立、数据传送和连接释放。
在现实中,UDP的传输方式更受到开发人员的青睐,就是因为UDP具有1)无需耗时建立连接;2)不用维持连接状态;
Socket介绍:
socket(套接字),是支持TCP/IP的网络通信的基本操作单元,可以看作不同主机之间进程进行双向通信的端点。
类比例子: IP和端口号就如同寄快递的目的地址,TCP/UDP如同挑选了快递公司,socket就相当于快递员,实现快递的收发。
实验
实验环境:Ubuntu20.10+Python3 & Windows10 + NetAssist(该链接提供软件下载)
NetAssist软件介绍:
Socket实现发送数据:
socket起源于Unix,对于Unix而言,一切皆文件,因而对于socket的操作亦有打开(创建)、编辑(执行)、关闭的类似操作。
# 1. 导入模块socket
# 2. 创建套接字socket.socket(AddressFamily,TransportType)
# 2.1 AddressFamily(地址族)
# socket.AF_INET -->IPv4 socket.AF_INET6 -->IPv6
# 2.2 socket.SOCK_STREAM -->TCP方式,因为TCP面向字节流
# socket.SOCK_DGRAM -->UDP方式,UDP面向数据包
# 3. sendTo(str,("IP",port))
# 3.1 str需要encode() 数据需要编码成二进制方式
# 3.2 ("IP",port) 使用元组方式指定接收方的IP和端口号
# 4. close()使用结束需要关闭
# 1.引入模块
import socket
# 2. 创建套接字
udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 3. 发送数据
udpsocket.sendto("What is up?".encode(),("192.168.150.1", 8080))
# 4. 关闭套接字
udp_socket.close()
socket接收数据: socket接收数据通过recvfrom()方法进行接受数据,返回的结果是一个元组,数据是二进制类型。
# 1. 导入模块 socket
import socket
# 2. 创建套接字
udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 3. 发送数据
udp_socket.sendto("what is up? Dear my friend.".encode(), ("192.168.150.1", 8080))
# 4. 接收数据
# 4.1 recvfrom(1024) 设置缓冲区大小为1kB, 就是读取1kB数据
udp_data = udp_socket.recvfrom(1024)
# 5. 打印输出
# 数据格式为(b'(content)',("IP",port)) 可以以udp_data[0].decode("GBK")方式解码
print(udp_data)
# 6. 关闭套接字
udp_socket.close()