一、IP地址
IP地址用来标记网络上的一台电脑,在本地局域网上是唯一的;
Linux中用ifconfig命令来查看IP地址,Windows中用ipconfig命令;
禁用网卡:sudo ifconfig ens33 down
开启网卡:sudo ifconfig ens33 up
IP地址的分类:IPV4、IPV6
每个IP地址由两个部分组成:网络地址、主机地址
A类IP地址
一个A类IP地址由1字节的网络地址和3字节主机地址组成,网络地址的最高位必须是“0”,
地址范围1.0.0.1-126.255.255.254
二进制表示为:00000001 00000000 00000000 00000001 - 01111110 11111111 11111111 11111110
可用的A类网络有126个,每个网络能容纳1677214个主机
B类IP地址
一个B类IP地址由2个字节的网络地址和2个字节的主机地址组成,网络地址的最高位必须是“10”,
地址范围128.1.0.1-191.255.255.254
二进制表示为:10000000 00000001 00000000 00000001 - 10111111 11111111 11111111 11111110
可用的B类网络有16384个,每个网络能容纳65534主机
C类IP地址
一个C类IP地址由3字节的网络地址和1字节的主机地址组成,网络地址的最高位必须是“110”
范围192.0.1.1-223.255.255.254
二进制表示为: 11000000 00000000 00000001 00000001 - 11011111 11111111 11111110 11111110
C类网络可达2097152个,每个网络能容纳254个主机
D类地址用于多点广播
D类IP地址第一个字节以“1110”开始,它是一个专门保留的地址。
它并不指向特定的网络,目前这一类地址被用在多点广播(Multicast)中
多点广播地址用来一次寻址一组计算机 s 地址范围224.0.0.1-239.255.255.254
E类IP地址
以“1111”开始,为将来使用保留
E类地址保留,仅作实验和开发用
私有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服务器。
二、端口
标识主机中的应用进程。
端口是通过端口号来标记的,端口号只有整数,范围是从0到65535
注意:端口数不一样的*nix系统不一样,还可以手动修改
知名端口(Well Known Ports)
知名端口是众所周知的端口号,范围从0到1023
80端口分配给HTTP服务
21端口分配给FTP服务
一般情况下,如果一个程序需要使用知名端口的需要有root权限
动态端口(Dynamic Ports)
动态端口的范围是从1024到65535
之所以称为动态端口,是因为它一般不固定分配某种服务,而是动态分配。
动态分配是指当一个系统程序或应用程序程序需要网络通信时,它向主机申请一个端口,主机从可用的端口号中分配一个供它使用。
当这个程序关闭时,同时也就释放了所占用的端口号。
三、socket
socket(简称 套接字
) 是进程间通信的一种方式,它与其他进程间通信的一个主要不同是:
它能实现不同主机间的进程间通信,我们网络上各种各样的服务大多都是基于 Socket 来完成通信的
例如我们每天浏览网页、QQ 聊天、收发 email 等等
创建socket
import socket socket.socket(AddressFamily, Type) 返回值是一个套接字对象
import socket # 创建udp的套接字 s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # ...这里是使用套接字的功能(省略)... # 不用的时候,关闭套接字 s.close()
import socket # 创建tcp的套接字 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # ...这里是使用套接字的功能(省略)... # 不用的时候,关闭套接字 s.close()
函数 socket.socket 创建一个 socket,该函数带有两个参数:
- Address Family:可以选择 AF_INET(用于 Internet 进程间通信) 或者 AF_UNIX(用于同一台机器进程间通信),实际工作中常用AF_INET
- Type:套接字类型,可以是 SOCK_STREAM(流式套接字,主要用于 TCP 协议)或者 SOCK_DGRAM(数据报套接字,主要用于 UDP 协议)
四、UDP网络程序
User Datagram Protocol的简称, 中文名是用户数据报协议
主机使用网络调试助手接收消息,虚拟机网络需要设置为桥接模式(IP在一个网段)
我遇到的问题:
发消息:NAT模式也可以发消息,但因为主机防火墙没有关,虚拟机ping不通主机
收消息:改完桥接就断网,主机网络设置中两个VM的禁用就可以顺利改为桥接
def main(): # 创建一个udp套接字 udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # 发送数据到指定电脑的指定程序中 # b表示以字节bytes类型发送,否则会报错 udp_socket.sendto(b"hahahah000000", ("192.168.0.105", 8080)) # 关闭套接字 udp_socket.close() if __name__ == "__main__": main()
编码转换:
str->bytes:encode编码
bytes->str:decode解码
import socket def main(): udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) data = input("需要发送的数据:") udp_socket.sendto(data.encode("utf-8"), ("192.168.0.105", 8080)) udp_socket.close() if __name__ == "__main__": main()
循环发送数据:
import socket def main(): udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) while True: data = input("请输入要传输的数据:") if data == "exit": break udp_socket.sendto(data.encode("utf-8"), ("192.168.0.105", 8080)) udp_socket.close() if __name__ == '__main__': main()
以上测试我主机的ip为192.168.0.105:8080,虚拟机IP为192.168.0.115:54321(NAT模式时为192.168.152.128 可以发送不能接收)
接收udp数据:
from socket import * # 创建套接字 udp_socket = socket(AF_INET, SOCK_DGRAM) # 绑定本地的相关信息,如果一个网络程序不绑定,系统会随机分配 local_addr = ("", 54321) # ip一般不用写,表示本机的任何一个ip udp_socket.bind(local_addr) # 接收数据 recv_data = udp_socket.recvfrom(1024) # 1024表示本次接收的最大字节数 # recv_data中存储的是一个元组(接收的数据,(发送方的ip,发送方的端口)) # 接收中文需要解码:utf-8不能解码bytes,因为windows默认编码是gbk print("%s : %s" % (recv_data[1], recv_data[0].decode("gbk"))) udp_socket.close()
发送数据的流程:
(若没有绑定端口,每一次系统随机分配的端口号不一样)
创建套接字
发送数据
关闭
接收数据的流程:
创建套接字
绑定IP和端口
接收数据
关闭
五、UDP聊天器
单工:单向,如收音机
半双工:双向,同一时刻只能有一个方向,如对讲机
全双工:双向,同一时刻可以两方传输,如打电话
socket套接字是全双工;
import socket def main(): udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) udp_socket.bind(('', 8765)) while True: print("1.发送消息 2.接收消息 0.退出系统") op = input("请输入功能") if op == "1": send_mess = input("要发送的数据:") udp_socket.sendto(send_mess.encode('utf-8'), ('192.168.0.105', 8080)) elif op == "2": recv_mess = udp_socket.recvfrom(1024) print("接收到的数据:%s" % recv_mess[0].decode('gbk')) elif op == "0": break else: print("输入有误") udp_socket.close() if __name__ == "__main__": main()
六、TCP协议
TCP协议,传输控制协议(英语:Transmission Control Protocol,缩写为 TCP)是一种面向连接的、可靠的、基于字节流的传输层通信协议,由IETF的RFC 793定义。
TCP通信需要经过创建连接、数据传送、终止连接三个步骤。
TCP通信模型中,在通信开始之前,一定要先建立相关的链接,才能发送数据,类似于生活中,"打电话""
1. 面向连接
通信双方必须先建立连接才能进行数据的传输,双方都必须为该连接分配必要的系统内核资源,以管理连接的状态和连接上的传输。
双方间的数据传输都可以通过这一个连接进行。
完成数据交换后,双方必须断开此连接,以释放系统资源。
这种连接是一对一的,因此TCP不适用于广播的应用程序,基于广播的应用程序请使用UDP协议。
2. 可靠传输
1)TCP采用发送应答机制
TCP发送的每个报文段都必须得到接收方的应答才认为这个TCP报文段传输成功
2)超时重传
发送端发出一个报文段之后就启动定时器,如果在定时时间内没有收到应答就重新发送这个报文段。
TCP为了保证不发生丢包,就给每个包一个序号,同时序号也保证了传送到接收端实体的包的按序接收。然后接收端实体对已成功收到的包发回一个相应的确认(ACK);如果发送端实体在合理的往返时延(RTT)内未收到确认,那么对应的数据包就被假设为已丢失将会被进行重传。
3)错误校验
TCP用一个校验和函数来检验数据是否有错误;在发送和接收时都要计算校验和。
4) 流量控制和阻塞管理
流量控制用来避免主机发送得过快而使接收方来不及完全收下。
udp与tcp区别:
udp通信模型中,在通信开始之前,不需要建立相关的链接,只需要发送数据即可,类似于生活中,"写信""
udp通信模型中,在通信开始之前,一定要先建立相关的链接,才能发送数据,类似于生活中,"打电话""
七、TCP客户端
tcp的客户端要比服务器端简单很多,如果说服务器端是需要自己买手机、查手机卡、设置铃声、等待别人打电话流程的话,那么客户端就只需要找一个电话亭,拿起电话拨打即可,流程要少很多
import socket def main(): # 创建套接字 tcp_client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 建立连接 tcp_client.connect(('192.168.0.105', 8080)) send_mess = input("要发送的数据:") # 发送消息 tcp_client.send(send_mess.encode('utf-8')) # 接收消息 recv_mess = tcp_client.recv(1024) print(recv_mess.decode('gbk')) # 关闭套接字 tcp_client.close() if __name__ == "__main__": main()
八、TCP服务器
在程序中,如果想要完成一个tcp服务器的功能,需要的流程如下:
- socket创建一个套接字
- bind绑定ip和port
- listen使套接字变为可以被动链接
- accept等待客户端的链接
- recv/send接收发送数据
import socket def main(): # 创建套接字(买手机 tcp_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 绑定ip和端口(插手机卡 tcp_server.bind(('', 7788)) # 使套接字变为被动套接字,套接字默认是主动的(设为正常接听状态 tcp_server.listen(128) # 等待客户端的链接,返回值是元组(等待电话打入 # client_socket用来为这个客户端服务,tcp_server继续等待新客户端的连接 client_socket, client_addr = tcp_server.accept() print(client_addr) # 接收消息 recv_data = client_socket.recv(1024) print(recv_data.decode('gbk')) # 发送消息 client_socket.send("hahahaha...".encode('utf-8')) # 关闭套接字 client_socket.close() tcp_server.close() if __name__ == "__main__": main()
循环为多个客户端服务:
import socket def main(): tcp_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) tcp_server.bind(('', 9876)) tcp_server.listen(128) while True: print("等待一个新的客户端到来...") client_socket, client_addr = tcp_server.accept() recv_data = client_socket.recv(1024) print("来自%s : %s" % (client_addr, recv_data.decode('gbk'))) client_socket.close() print("服务完毕。") tcp_server.close() if __name__ == "__main__": main()
循环为多个客户端服务并多次服务一个客户端:
import socket def main(): tcp_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) tcp_server.bind(('', 7788)) tcp_server.listen(128) while True: print("等待一个新的客户端到来...") client_socket, client_addr = tcp_server.accept() print("客户端%s:%s连接成功" % client_addr) while True: recv_data = client_socket.recv(1024) # 如果recv解堵塞,有两种可能: # 客户端发过来数据 # 客户端调用了close(recv_data为空) if recv_data: print("来自%s : %s" % (client_addr, recv_data.decode('gbk'))) else: break client_socket.close() print("-------服务完毕-------") tcp_server.close() if __name__ == "__main__": main()
listen中的值代表多少个客户端可以同时链接
tcp注意点:
-
tcp服务器一般情况下都需要绑定,否则客户端找不到这个服务器
-
tcp客户端一般不绑定,因为是主动链接服务器,所以只要确定好服务器的ip、port等信息就好,本地客户端可以随机
-
tcp服务器中通过listen可以将socket创建出来的主动套接字变为被动的,这是做tcp服务器时必须要做的
-
当客户端需要链接服务器时,就需要使用connect进行链接,udp是不需要链接的而是直接发送,但是tcp必须先链接,只有链接成功才能通信
-
当一个tcp客户端连接服务器时,服务器端会有1个新的套接字,这个套接字用来标记这个客户端,单独为这个客户端服务
-
listen后的套接字是被动套接字,用来接收新的客户端的链接请求的,而accept返回的新套接字是标记这个新客户端的
-
关闭listen后的套接字意味着被动套接字关闭了,会导致新的客户端不能够链接服务器,但是之前已经链接成功的客户端正常通信。
-
关闭accept返回的套接字意味着这个客户端已经服务完毕
-
当客户端的套接字调用close后,服务器端会recv解堵塞,并且返回的长度为0,因此服务器可以通过返回数据的长度来区别客户端是否已经下线
九、文件下载器
with as 操作文件
# 不用close with open("文件名", "wb") as f: f.write("111222333".encode('gbk')) # 相当于这种写法,即使出现异常,也会close f = open("文件名", "w") try: f.write("aabbcc") except: f.close()
文件下载客户端
import socket def main(): # 创建套接字 tcp_client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 链接服务器 tcp_client.connect(("192.168.0.115", 9876)) # 发送下载的文件名 file_name = input("输入要下载的文件名:") tcp_client.send(file_name.encode('utf-8')) # 接收文件数据 recv_data = tcp_client.recv(1024) # 将接收到的数据包存到文件 if recv_data: with open("[接收]"+file_name, "wb") as f: f.write(recv_data) # 关闭套接字 tcp_client.close() if __name__ == "__main__": main()
文件下载服务器
import socket def send_file(client_socket, client_addr): # 接收客户端需要下载的文件名 file_name = client_socket.recv(1024) print("客户端%s需要下载的文件是:%s" % (client_addr, file_name.decode('utf-8'))) file_content = None # 打开文件 读取数据 # with的前提是打得开的前提下,处理读写异常,这里不能用with try: f = open(file_name, 'rb') file_content = f.read() f.close() except Exception as ret: print("没有要下载的文件%s:%s" % (file_name, ret)) # 发送文件数据给客户端 if file_content: client_socket.send(file_content) print("文件发送成功") def main(): # 创建套接字 tcp_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 绑定ip和端口 tcp_server.bind(('', 9876)) # 开启监听 tcp_server.listen(128) # 接收客户端连接 client_socket, client_addr = tcp_server.accept() # 发送文件 send_file(client_socket, client_addr) # 关闭套接字 client_socket.close() tcp_server.close() if __name__ == "__main__": main()