WAN:
一种跨越大的、地域性的计算机网络的集合。通常跨越省、市,甚至一个国家。广域网包括大大小小不同的子网,子网可以是局域网,也可以是小型的广域网
LAN:
顾名思义,LAN是指在某一区域内由多台计算机互联成的计算机组。一般是方圆几千米以内。局域网可以实现文件管理、应用软件共享、打印机共享、工作组内的日程安排、电子邮件和传真通信服务等功能。局域网是封闭型的,可以由办公室内的两台计算机组成,也可以由一个公司内的上千台计算机组成。
IP地址的作用是唯一标识网络中的设备,使得设备能够在网络中进行通信。它类似于门牌号码,用于确定设备的位置和身份。IP地址可以用于寻找和连接其他设备,发送和接收数据包,并在Internet上进行通信。IP地址的作用还包括路由选择、网络管理和安全控制等方面。
一句话的话ip 地址就是用来在网络中标记一台电脑,比如 192.168.1.1在本地局域网上是唯一的。
每一个 IP 地址包括两部分:网络地址和主机地址
在这么多网络 IP 中,国际规定有一部分 IP 地址是用于我们的局域网使用,也就是属于私网 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
值得关注的是:
IP 地址 127.0.0.1~127.255.255.255 用于回路测试,
如:127.0.0.1 可以代表本机 IP 地址,用 http://127.0.0.1 就可以测试本机中配
置的 Web 服务器
子网掩码:互联网是由许多小型网络构成的,每个网络上都有许多主机,这样便构成了一个有层次的结构。IP地址在设计时就考虑到地址分配的层次特点,将每个IP地址都分割成网络号和主机号两部分,以便于IP地址的寻址操作。
子网掩码有两个功能:
广播地址是专门用于同时向网络中所有工作站进行发送的一个地址。
在使用TCP/IP 协议的网络中,主机标识段host ID 为全1 的IP 地址为广播地址,广播的分组传送给host ID段所涉及的所有计算机。例如,对于10.1.1.0 (255.255.255.0 )网段,其广播地址为10.1.1.255 (255 即为2 进制的11111111 ),当发出一个目的地址为10.1.1.255 的分组(封包)时,它将被分发给该网段上的所有计算机。
通常用 ping 来检测网络是否正常,直接ping (网址/ip)即可
route -n
内核 IP 路由表
目标 | 网关 | 子网掩码 |
---|---|---|
0.0.0.0 | 192.168.19.2 | 0.0.0.0 |
169.254.0.0 | 0.0.0.0 | 255.255.0.0 |
192.168.19.0 | 0.0.0.0 | 255.255.255.0 |
0.0.0.0 代表任意目的地
网关就是转发数据的设备
路由表是从上往下匹配的
在unix/linux的使用中,实际上网络沟通是进程与进程之间进行的沟通,举个例子就是,我电脑上的QQ进行和别人电脑上的QQ进程进行沟通,而端口负责的就是在同一ip下的,关于具体到进程这一方面的交互。
端口是通过端口号来标记的,端口号只有整数,范围是从 0 到 65535
注意:端口数不一样的 unix/linix 系统不一样,还可以手动修改
端口号不是随意使用的,而是按照一定的规定进行分配。端口的分类标准有好几种,我们这里不做详细讲解,只介绍一下知名端口和动态端口
知名端口是众所周知的端口号,范围从 0 到 1023
端口号 | 说明 |
---|---|
80 | 端口分配给 HTTP 服务 |
21 | 端口分配给 FTP 服务 |
22 | 端口分配给 ssh |
443 | 端口分配给https |
一般情况下,如果一个程序需要使用知名端口的需要有 root 权限
动态端口的范围是从 1024 到 65535。之所以称为动态端口,是因为它一般不固定分配某种服务,而是动态分配。
动态分配是指当一个系统程序或应用程序程序需要网络通信时,它向主机申请一个端口,主机从可用的端口号中分配一个供它使用。当这个程序关闭时,同时也就释放了所占用的端口号。
netstat [-an]
查看端口状态
例子:
此时就可以简单看出在我们本地的widows是59173端口和服务器的22号端口进行相连。
sudo lsof -i [tcp/udp]:2425 必须是 root 才能查看
sudo lsof -i tcp:22 查看哪一个进程用了这个端口
ps -elf |grep udp_server 查看某个进程是否还在
参考链接:
lsof
ps
使用方式:
# window下
tracert ip地址
# linux下
traceroute ip地址
端口有什么用呢 ? 我们知道,一台拥有 IP 地址的主机可以提供许多服务,比如HTTP(万维网服务)、FTP(文件传输)、SMTP(电子邮件)等,这些服务完全可以通过 1 个 IP 地址来实现。那么,主机是怎样区分不同的网络服务呢?显然不能只靠 IP 地址,因为 IP 地址与网络服务的关系是一对多的关系。实际上是通过“IP 地址+端口号”来区分不同的服务的。 需要注意的是,端口并不是一一对应的。比如你的电脑作为客户机访问一台 WWW 服务器时,WWW 服务器使用“80”端口与你的电脑通信,但你的电脑则可能使用“3457”这样的端口。
先简单介绍一下tcp和udp,后面部分会再进行详细的介绍:
TCP是面向连接的协议,在收发数据前必须和对方建立可靠的连接,建立连接的3次握手、断开连接的4次挥手,为数据传输打下可靠基础;UDP是一个面向无连接的协议,数据传输前,源端和终端不建立连接,发送端尽可能快的将数据扔到网络上,接收端从消息队列中读取消息段。
由此可以看出:UDP 服务器端不需要调用监听(listen)和接收(accept)客户端连接,而客户端也不需要连接服务器端(connect)。UDP协议中,任何一方建立socket后,都可以用sendto发送数据、用recvfrom接收数据,不必关心对方是否存在,是否发送了数据。
为了实现TCP网络通信的可靠性,增加校验和、序号标识、滑动窗口、确认应答、拥塞控制等复杂的机制,建立了繁琐的握手过程,增加了TCP对系统资源的消耗;TCP的重传机制、顺序控制机制等对数据传输有一定延时影响,降低了传输效率。TCP适合对传输效率要求低,但准确率要求高的应用场景,比如万维网(HTTP)、文件传输(FTP)、电子邮件(SMTP)等。
UDP是无连接的,不可靠传输,尽最大努力交付数据,协议简单、资源要求少、传输速度快、实时性高的特点,适用于对传输效率要求高,但准确率要求低的应用场景,比如域名转换(DNS)、远程文件服务器(NFS)等。
首要解决的问题是如何唯一标识一个进程,否则通信无从谈起!在 1 台电脑上可以通过进程号(PID)来唯一标识一个进程,但是在网络中这是行不通的。其实 TCP/IP 协议族已经帮我们解决了这个问题,网络层的“ip 地址”可以唯一标识网络中的主机,而传输层的“协议+端口”可以唯一标识主机中的应用进程(进程)。这样利用协议,ip 地址,端口就可以标识网络的进程了,网络中的进程通信就可以利用这个标志与其它进程进行交互。
socket(简称 套接字) 是进程间通信的一种方式,它与其他进程间通信的一个主要不同是:它能实现不同主机间的进程间通信,我们网络上各种各样的服务大多都是基于Socket 来完成通信的:
例如我们每天浏览网页、QQ 聊天、收发 email 等等
在 Python 中 使用 socket 模块的函数 socket 就可以完成:
import socket
socket.socket(AddressFamily, Type)
说明:
函数 socket.socket 创建一个 socket,该函数带有两个参数:
创建一个 tcp socket(tcp 套接字)
import socket
# 创建 tcp 的套接字
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# ...这里是使用套接字的功能(省略)...
# 不用的时候,关闭套接字
s.close()
创建一个 udp socket(udp 套接字)
import socket
# 创建 udp 的套接字
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# ...这里是使用套接字的功能(省略)...
# 不用的时候,关闭套接字
s.close()
由此可以看出一个比较不一样的地方就是使用的常数是很不一样的。socket.SOCK_STREAM和socket.SOCK_DGRAM
str->bytes:encode 编码
bytes->str:decode 解码
根据上文的流程图可以写出下文的代码
import socket
# 1.创建一个udp的服务器名为server
server=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
# 2.服务器绑定ip地址与端口
addr=('本地的地址',2000) #写1024以上端口
server.bind(addr) #失败直接抛异常
# 3.阻塞接受客户端的消息
# recvfrom函数返回的list中 list[0]返回的是客户端传回的数据,list[1]传回的是客户端的ip地址端口的消息
data,client_addr=s.recvfrom(5) # 5代表接的字节大小
print(data.decode('utf8'))
print(client_addr)
# 4.根据返回的消息处理请求。略
# 5.返回消息给客户端
server.sendto('很牛'.encode('utf8'),client_addr)
# 6.关闭服务器
server.close()
小补充:
实际上当你运行服务端的时候,你去自己电脑命令窗口下netstat -an实际上就会发现你的对应的端口就会打开:
根据上文的流程图可以写出下文的代码
import socket
# 1.创建一个udp客户端命名为client
client=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
# 2.发送消息给服务端
dest_addr=('192.168.0.100',2000)
# client.sendto(b'hello',dest_addr)
client.sendto('howare'.encode('utf8'),dest_addr) #放汉字还是不放都行
# 3.阻塞等待服务端发消息给客户端
data,_=client.recvfrom(100)
print(data.decode('utf8'))
# 4.后续处理,略
# 5.关闭客户端
client.close() #关闭时端口会释放
首先先配置安全组:
然后将服务器文件拖动到服务器上,并修改服务器的那个地方对应的内网ip:
先输入ifconfig:
然后修改ip:
运行服务端:
修改客户端ip:
不暴露信息,自己取修改吧,从阿里云服务器上了解其公网ip,然后在客户端上进行修改即可
端口占用怎么办?
ps -elf 查看进程
kill -9 进程 i
给个样例:
ps -elf|grep 2000 #查看2000号端口的使用者是谁
kill -9 对应的PID
TCP 协议,传输控制协议是一种面向连接的、可靠的、基于字节流的传输层通信协议
TCP 通信模型中,在通信开始之前,一定要先建立相关的链接,才能发送数据,类似于生活中,“打电话”“,而udp 通信模型中,在通信开始之前,不需要建立相关的链接,只需要发送数据即可,类似于生活中,“写信””
通信双方必须先建立连接才能进行数据的传输,双方都必须为该连接分配必要的系统内核资源,以管理连接的状态和连接上的传输。双方间的数据传输都可以通过这一个连接进行。完成数据交换后,双方必须断开此连接,以释放系统资源。
这种连接是一对一的,因此 TCP 不适用于广播的应用程序,基于广播的应用程序请使用 UDP 协议。
• 面向连接(确认有创建三方交握,连接已创建才作传输。)
• 有序数据传输
• 重发丢失的数据包
• 舍弃重复的数据包
• 无差错的数据传输
• 阻塞/流量控
根据上文的流程图可得下面的代码:
import socket
def tcp_server():
# 1.创建tcp的socket服务端
# SOCK_STREAM代表tcp的socket
server=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
# 2.绑定地址,注意和udp不同的是,此时我们去查看端口,此端口是没有激活的
addr=('192.168.5.7',2000)
server.bind(addr) #绑定,端口并没有激活
# 3.监听端口
server.listen(128) #listen时,端口才激活
# 4.接受沟通,创建与相对应客户端相连接的套接字
new_client,client_addr=server.accept()
print(client_addr)
# 5.截下来就是相对应的别的操作了可以send也可以recv,不过需要和客户端相对应。
# 接下来就可以进行send,recv操作
new_client.send('helloworld'.encode('utf8'))
data=new_client.recv(100)
print(data.decode('utf8'))
# 6.关闭服务器,注意要先关闭client再关闭server
new_client.close()
server.close()
if __name__ == '__main__':
tcp_server()
在socket中,listen函数的参数数字表示等待连接队列的最大长度。具体来说,它指定了服务器端能够同时处理的最大连接数。
当一个服务器端的socket调用listen函数时,它会开始监听指定的端口号,并等待客户端的连接请求。listen函数的参数指定了服务器端能够接受的最大连接数。如果连接请求的数量超过了这个限制,那些超出的连接请求将被服务器端忽略或拒绝。
例如,如果listen函数的参数设置为5,那么服务器端最多能够同时处理5个连接请求。如果有第6个连接请求到达,它将被服务器端忽略或拒绝。
需要注意的是,listen函数的参数值并不代表服务器端能够处理的并发连接数。实际上,服务器端能够处理的并发连接数取决于多个因素,如服务器的硬件性能、操作系统的限制等。listen函数的参数值只是指定了服务器端能够接受的最大连接数,而并不代表服务器端能够同时处理这么多连接。
根据上文的流程图可得下面的代码:
import socket
def tcp_client():
# 1.创建socket对象--客户端
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 2.将客户端和服务端连接,与udp不同的是,udp不需要连接,udp使用sendto加上地址进行传送数据
dest_addr = ('192.168.5.7', 2000)
client.connect(dest_addr)
# 3.后续的recv和send操作,与服务器对应
data=client.recv(5)
print(data.decode('utf8'))
client.send('我是男生abc123'.encode('utf8'))
# 4.关闭客户端
client.close()
if __name__ == '__main__':
tcp_client()
补充知识:
import sys
for i in sys.argv:
print(i)
此时转到终端输入:
python 这个文件
得到的输出是:
这个文件
也就是说,我们可以通过后续多加几个str类型数据,从而得到相对应的配置属性输入:
python 这个文件 ip port
输出:
这个文件 ip port
服务端:
from socket import *
import sys
def get_file_content(file_name):
"""获取文件的内容"""
try:
with open(file_name, "rb") as f:
content = f.read()
return content
except:
print("没有下载的文件:%s" % file_name)
def main():
if len(sys.argv) != 2:
print("请按照如下方式运行:python3 xxx.py 7890")
return
else:
# 运行方式为python3 xxx.py 2000
port = int(sys.argv[1])
# 创建socket
tcp_server_socket = socket(AF_INET, SOCK_STREAM)
# 本地信息
address = ('', port)
# 绑定本地信息
tcp_server_socket.bind(address)
# 将主动套接字变为被动套接字
tcp_server_socket.listen(128)
# 值得关注的是1->多体现在这里
while True:
# 等待客户端的链接,即为这个客户端发送文件
client_socket, clientAddr = tcp_server_socket.accept()
# 接收对方发送过来的数据
recv_data = client_socket.recv(1024) # 接收1024个字节
file_name = recv_data.decode("utf-8")
print("对方请求下载的文件名为:%s" % file_name)
file_content = get_file_content(file_name)
# 发送文件的数据给客户端
# 因为获取打开文件时是以rb方式打开,所以file_content中的数据已经是二进制的格式,因此不需要encode编码
if file_content:
client_socket.send(file_content)
# 关闭这个套接字
client_socket.close()
# 关闭监听套接字
tcp_server_socket.close()
if __name__ == "__main__":
main()
客户端:
from socket import *
def main():
# 创建socket
tcp_client_socket = socket(AF_INET, SOCK_STREAM)
# 目的信息
server_ip = '192.168.5.7'
server_port = 2000
# 链接服务器
tcp_client_socket.connect((server_ip, server_port))
# 输入需要下载的文件名
file_name = input("请输入要下载的文件名:")
# 发送文件下载请求
tcp_client_socket.send(file_name.encode("utf-8"))
# 接收对方发送过来的数据,最大接收1024个字节(1K)
recv_data = tcp_client_socket.recv(1024)
# print('接收到的数据为:', recv_data.decode('utf-8'))
# 如果接收到数据再创建文件,否则不创建
if recv_data:
with open("[接收]"+file_name, "wb") as f:
f.write(recv_data)
# 关闭套接字
tcp_client_socket.close()
if __name__ == "__main__":
main()
三次握手是TCP协议中建立可靠连接的过程。在客户端和服务器之间建立连接之前,必须进行三次握手来确保双方都能够正常通信。下面是三次握手的详细步骤:
第一次握手(SYN):客户端发送一个SYN(同步)包给服务器端,请求建立连接。这个包包含一个随机生成的初始序列号(ISN)用于标识数据流的起始位置。
第二次握手(SYN-ACK):服务器接收到客户端的SYN包后,会发送一个SYN-ACK(同步-确认)包给客户端。这个包中会确认客户端的SYN,并且也会包含一个随机生成的ISN作为回应。
第三次握手(ACK):客户端接收到服务器的SYN-ACK包后,会发送一个ACK(确认)包给服务器。这个包会确认服务器的SYN,并且也会包含服务器发送的ISN加1作为回应。
完成了这三次握手之后,双方就建立了可靠的连接,可以开始进行数据传输。此时,双方都可以相互发送数据包,并且可以保证数据的可靠传输。
三次握手的目的是确保双方都能够正常收发数据,并且能够彼此接收到对方发送的数据。通过三次握手,双方可以建立起一个可靠的连接,以便进行可靠的数据传输。同时,三次握手也可以防止已失效的连接请求被服务器端误认为是新的连接请求,从而提高了连接的可靠性和安全性。
如果只存在两次握手,会出现两种可能导致死锁的情况:
死锁情况一(客户端发起连接失败):客户端发送SYN包给服务器端,但由于网络延迟或其他原因,服务器端没有收到该包。此时,客户端认为连接已经建立,进入等待状态,而服务器端并不知道客户端的存在。由于缺乏第三次握手的确认,服务器端无法确定客户端是否成功接收到SYN-ACK包,并且无法发送ACK包。因此,客户端和服务器端都处于等待状态,形成了死锁。
死锁情况二(服务器端发起连接失败):服务器端发送SYN-ACK包给客户端,但由于网络延迟或其他原因,客户端没有收到该包。此时,服务器端认为连接已经建立,进入等待状态,而客户端并不知道服务器端的存在。由于缺乏第三次握手的ACK确认,客户端无法确定服务器端是否成功接收到ACK包,并且无法发送数据。因此,客户端和服务器端都处于等待状态,形成了死锁。
在这两种情况下,由于缺乏第三次握手的确认,双方无法确定连接是否成功建立,进入了死锁状态。为了避免这种情况,TCP协议采用了三次握手的机制,确保双方都能够正常通信并建立可靠的连接。
四次挥手是TCP协议中关闭连接的过程。在双方通信结束后,需要通过四次挥手来正常关闭连接,确保双方都能够完成数据的传输并释放相关资源。下面是四次挥手的详细步骤:
第一次挥手(FIN):当一个端口(通常是客户端)确定不再发送数据时,它会发送一个FIN(结束)包给另一个端口(通常是服务器端)。这个FIN包表示它已经完成了数据的发送。
第二次挥手(ACK):接收到FIN包的端口(通常是服务器端)会发送一个ACK(确认)包给对方,表示已经收到了FIN包。此时,服务器端进入半关闭状态,即不再接收来自客户端的数据,但仍可以向客户端发送数据。
第三次挥手(FIN):当另一个端口(通常是服务器端)也确定不再发送数据时,它会发送一个FIN包给另一个端口(通常是客户端)。这个FIN包表示它已经完成了数据的发送。
第四次挥手(ACK):接收到FIN包的端口(通常是客户端)会发送一个ACK包给对方,表示已经收到了FIN包。此时,客户端进入TIME_WAIT状态,等待一段时间后关闭连接,确保服务器端收到了ACK包。
完成了这四次挥手之后,双方都完成了数据的传输并释放了相关资源,连接被正常关闭。
四次挥手的目的是确保双方都能够完成数据的传输并释放相关资源。通过四次挥手,双方可以协商关闭连接,并确保对方收到了关闭连接的请求。这样可以避免数据的丢失或不完整,并且保证连接的可靠关闭。
补充:
TIME_WAIT状态的主要目的是确保在网络中所有的数据包都已经被接收和处理完毕,以避免数据的重复传输或混乱。在TIME_WAIT状态下,端口会保持开放并等待一段时间(通常是2倍的最大报文段生存时间(MSL)),以确保网络中已经没有挂起的数据包。
TCP 在真正的读写操作之前,server 与 client 之间必须建立一个连接,
当读写操作完成后,双方不再需要这个连接时它们可以释放这个连接,
连接的建立通过三次握手,释放则需要四次握手,
所以说每个连接的建立都是需要资源消耗和时间消耗的。
短链接实际上就是在通信的过程中只进行一次send和recv,而长连接进行多次
建立连接——数据传输——关闭连接…建立连接——数据传输——关闭连接
长连接多用于操作频繁,点对点的通讯,而且连接数不能太多情况。每个 TCP 连接都需要三次握手,这需要时间,如果每个操作都是先连接,再操作的话那么处理速度会降低很多,所以每个操作完后都不断开,再次处理时直接发送数据包就 OK 了,不用建立 TCP 连接。例如:数据库的连接用长连接,如果用短连接频繁的通信会造成 socket 错误,而且频繁的 socket 创建也是对资源的浪费。
而像 WEB 网站的 http 服务一般都用短链接,因为长连接对于服务端来说会耗费一定的资源,而像 WEB 网站这么频繁的成千上万甚至上亿客户端的连接用短连接会更省一些资源,如果用长连接,而且同时有成千上万的用户,如果每个用户都占用一个连接的话,那可想而知吧。所以并发量大,但每个用户无需频繁操作情况下需用短连好。