1. 网络编程
# a. 软件 # 客户端:CS架构, client -> server # 浏览器:BS架构, browser -> server # b. 如何实现相互通信。 # 需求一:编写两个软件,打开一个文件,相互通信。 # 需求二:两个人直接连接 (网线) # 需求三:教室相互通信(交换机) 查看IP地址:ipconfig/ifconfig ex : 电脑1(源)要找电脑2(目标) (1)电脑1首先发送一个请求帧,期中包含(我的ip是192.168.1.1,我的mac地址是xxxxxxxx,我要找ip地址为192.168.1.2的主机),将此请求发送给交换机. (2)交换机要广播这条消息给其他所有的主机 (3)目标主机接收到消息后,对比发现自己就是被找的主机,回复给交换机信息(我的ip地址是192.168.1.2,我的mac地址是yyyyyyyyy,请回复给ip地址为192.168.1.1,mac地址为xxxxxxx的主机) (4)交换机单播形式返回给源主机 # 需求四:和三亚的女友通信(交换机+n*路由器) 三亚女朋友:192.168.13.43 租公网IP # 总结: # 1. 相互通信本质发送 0101010101 # 2. 交换机作用 # 3. 通过ipconfig查看自己的内网IP # 4. 公网IP,掏钱。 # 知识点 : 1 mac地址 : 是一个物理地址,全球唯一, 类似于身份证() 2 ip地址: 是一个四位点分十进制,它标识了计算机在网络中的位置.类似于学号 3 交换机的通信方式: 广播 : 吼一嗓子 单播 : 一对一 组播 : 一对多 4 arp协议 : 通过目标ip地址获取目标mac地址的一个协议. 5 端口 : 操作系统为本机上每一个运行的程序都随机分配一个端口,其他电脑上的程序可以通过端口获取到这个程序 ip地址 + 端口 能唯一找到某台电脑上的某一个服务程序 6 路由器 : 连接不同网段 , 路由 7 网关 : 类似于一个局域网的出口和入口 8 网段 : 一个局域网内的ip地址范围 9 子网掩码 : 子网掩码 & ip地址 得到网段 10 osi 五层模型: 应用层 : http,https,ftp 传输层 : tcp / udp 四层交换机 四层路由器 网络层 : ip协议 路由器 三层交换机 数据链路层 : arp协议 以太网交换机 网卡 网桥 物理层 : 传输电信号 集线器 网线 光纤
2. 网络基础
2.1 一个程序如何在网络上找到另一个程序? # 首先,程序必须要启动,其次,必须有这台机器的地址,我们都知道我们人的地址大概就是国家省市区街道楼门牌号这样字。那么每一台联网的机器在网络上也有自己的地址,它的地址是怎么表示的呢? 就是使用一串数字来表示的,例如:100.4.5.6 (1) ip地址与ip协议 规定网络地址的协议叫ip协议,它定义的地址称之为ip地址,广泛采用的v4版本即ipv4,它规定网络地址由32位2进制表示 范围0.0.0.0-255.255.255.255 一个ip地址通常写成四段十进制数,例:172.16.10.1 (2)端口 我们知道,一台拥有IP地址的主机可以提供许多服务,比如Web服务、FTP服务、SMTP服务等,这些服务完全可以通过1个IP地址来实现。那么问题来了,主机是怎样区分不同的网络服务呢?举个栗子,For Example,当你和你的朋友聊QQ的时候,你把一条信息发送给对方,为什么那条消息能显示到对方电脑上的QQ对话框,而不是显示到微信的对话框??显然不只有一个ip地址是不行的,因为IP 地址与网络服务的关系是一对多的关系。实际上是通过“IP地址+端口号”来区分不同的服务的。ip找到对应主机,端口找到主机中某一个服务 总结: 因此ip地址精确到具体的一台电脑,而端口精确到具体的程序 2.2 osi七层模型 # 一个完整的计算机系统是由硬件、操作系统、应用软件三者组成,具备了这三个条件,一台计算机系统就可以自己跟自己玩了(打个单机游戏,玩个扫雷啥的) # 如果你要跟别人一起玩,那你就需要上网了,什么是互联网? # 互联网的核心就是由一堆协议组成,协议就是标准,比如全世界人通信的标准是英语,如果把计算机比作人,互联网协议就是计算机界的英语。所有的计算机都学会了互联网协议,那所有的计算机都就可以按照统一的标准去收发信息从而完成通信了。 osi五层模型 人们按照分工不同把互联网协议从逻辑上划分了层级:
2.3 socket概念 socket层:
其实在我们来看,socket就是一个模块。我们通过调用模块中已经实现的方法建立两个进程之间的连接和通信。
3. 什么是socket
2.1 理解socket Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。 2.2 socket的家族 套接字有两种(或者称为有两个种族),分别是基于文件型的和基于网络型的。 (1)基于文件类型的套接字家族 套接字家族的名字:AF_UNIX unix一切皆文件,基于文件的套接字调用的就是底层的文件系统来取数据,两个套接字进程运行在同一机器,可以通过访问同一个文件系统间接完成通信 (2)基于网络类型的套接字家族 套接字家族的名字:AF_INET
3. TCP/UDP
TCP(Transmission Control Protocol)可靠的、面向连接的协议(eg:打电话)、传输效率低全双工通信(发送缓存&接收缓存)、面向字节流。使用TCP的应用:Web浏览器;电子邮件、文件传输程序。 UDP(User Datagram Protocol)不可靠的、无连接的服务,传输效率高(发送前时延小),一对一、一对多、多对一、多对多、面向报文,尽最大努力服务,无拥塞控制。使用UDP的应用:域名系统 (DNS);视频流;IP语音(VoIP)。
上图:
# 基于文件类型的套接字家族 -> AF_LINUX # 基于网络类型的套接字家族 -> AF_INET # TCP协议 -> SOCK_STREAM # UDP协议 -> SOCK_DGRAM 创建socket对象时默认是网络类型套接字,TCP协议: family = AF_INET, type = SOCK_STREAM
4. 基于TCP协议的socket
tcp是基于链接的,必须先启动服务端,然后再启动客户端去链接服务端
基本用法: ----------------------------- 服务端 -------------------------------- import socket # 创建socket对象 server = socket.socket() # 绑定IP和端口 server.bind(('192.168.3.6', 8000)) # 后边最多可以等5个人,第六个直接拒绝连接请求 server.listen(5) print('服务端准备开始接收客户端的连接') # 等待客户端来连接,如果没人来就傻傻的等待. # conn是客户端和服务端连接的对象(伞), 服务端以后要通过 # addr是客户端的地址信息 # #### 阻塞, 只有有客户进行连接, 则获取客户端连接然后开始进行通信 conn, addr = server.accept() print('已经有人连接上了', conn, addr) # 通过对象去获取(王晓东通过伞给我发送的消息) # 1.24表示, 服务端通过伞获取数据是一次性最多拿1024字节 data = conn.recv(1024) print('硬件有人发来消息', data) # 服务端通过连接对象(伞)给客户端回复了一个消息 conn.send(b'stop') # 与客户端断开连接(放开那把伞) conn.close() # 关闭服务端的服务 server.close() ----------------------------- 客户端 -------------------------------- import socket client = socket.socket() # 王晓东向服务端发起连接请求(递伞) # 阻塞, 去连接, 知道连接成功后才会继续向下走 client.connect(('192.168.3.6', 8000)) # 链接上服务端后, 向服务端发送一个消息 client.send(b'hello') # 王晓东等待服务端给他发送消息 data = client.recv(1024) print(data) # 关闭自己 client.close()
# --------------------- 示例1:持续问答(简单) -------------------- # 执行流程: (1)客户端输入的字符串不是exit就发送过去,服务端判断接收的如果不是exit就返回字符串+sb (2)客户端输入的字符串是exit就先发送过去,然后断开连接,服务端判断接收的是exit,那么就直接断开本次连接 (3)客户端没有走正常逻辑异常退出断开连接,服务端捕获异常,然后断开连接 # ### 服务端 ### import socket server = socket.socket() server.bind(('192.168.3.6', 8000)) server.listen(5) while 1: conn, addr = server.accept() # 阻塞:永远等待连接 # 字节类型 while 1: try: # windows中需要用try,except捕捉客户端强制关闭的异常 data = conn.recv(1024) # 阻塞:永远等待接收 if data == b'exit': break response = data + b' SB' conn.send(response) except ConnectionResetError: break conn.close() # 与客户端断开连接(放开那把伞 # ### 客户端 ### import socket sk = socket.socket() sk.connect(('192.168.3.6', 8000)) while 1: name = input("请输入姓名:") sk.send(name.encode('utf-8')) # 字节 if name == 'exit': break response = sk.recv(1024) # 字节 print(response.decode('utf-8')) sk.close() # 关闭自己
# ------------------- 示例1:持续问答(进阶) ----------------- # 执行流程: (1) 客户端输入,如果是exit,就发送过去,然后自己关闭,服务端接收到exit就断开连接. (2) 服务端输入,如果是exit,就先发送,然后自己断开连接,客户端接收到exit,就关闭自己. (3)此处用到了flag来关闭服务端(处理服务端关闭的异常), 服务端输入,如果是exitall,改变标识,直接跳出两层循环,断开了连接也关闭了自己,没有发送,这时候客户端从通道接收到的为空,打印出来空白,继续输入,输入完发送过去,但是这时候通道不存在了,sk.recv(1024)报错,然后用捕获异常来处理,捕获到打印服务端关闭,然后跳出 # ### 服务端 ### import socket server = socket.socket() server.bind(('192.168.3.21', 8000)) server.listen(5) flag = True while flag: print("server is working") conn, addr = server.accept() # 阻塞:永远等待连接 # 字节类型 while 1: try: # windows中需要用try,except捕捉客户端强制关闭的异常 data = conn.recv(1024) # 阻塞:永远等待接收 if data.decode('utf-8') == 'exit': print("客户端断开连接") break print(data.decode('utf-8')) msg_s = input(">>>") if msg_s == 'exit': conn.send(msg_s.encode("utf-8")) break elif msg_s == 'exitall': flag = False break conn.send(msg_s.encode("utf-8")) except ConnectionResetError: break conn.close() # 与客户端断开连接(放开那把伞) server.close() print("服务端关闭") # ### 客户端 ### import socket sk = socket.socket() sk.connect(('192.168.3.21', 8000)) while 1: msg_c = input(">>>") sk.send(msg_c.encode('utf-8')) # 字节 if msg_c == 'exit': break try: msg_back = sk.recv(1024) if msg_back.decode('utf-8') == 'exit': print("服务端断开连接") break # if len(msg_back) == 0: # print("服务端关闭") # break print(msg_back.decode('utf-8')) except Exception as e: print(e, "服务端关闭") break sk.close() # 关闭自己
# 总结: # 为什么要网络通信发送的是字节?而不是字符串? # py3, send/recv 都是字节 # py2, send/recv 都是字符串 # 个人理解: # (1)python3中的字符串默认是unicode类型,传输和存储时候很耗费网络流量或者很占用空间,所以转换为字节(目的是为了压缩),变成utf-8或者gbk,节省空间. # (2)python2中字符串的类型默认就是utf-8或者gbk. # 服务端: # accept,阻塞:等待客户端来连接. # recv, 阻塞:等待客户端发来数据. # 客户端: # connect,阻塞:一直在连接,直到连接成功才往下运行其他代码. # recv, 阻塞:等待服务端发来数据.
5.基于UDP协议的socket
udp是无链接的,启动服务之后可以直接接受消息,不需要提前建立链接,先启动哪一端都不会报错 基本用法 # ---------------------------- 服务端 ----------------------------- import socket # 指定参数type=socket.SOCK_DGRAM,创建UDP协议的socket服务器对象 udp_sk = socket.socket(type=socket.SOCK_DGRAM) # 给服务器对象绑定ip和端口 udp_sk.bind(('192.168.3.21',9000)) # 不需要等待连接,recvfrom直接接收到:消息和消息的地址(ip,端口号) msg, addr = udp_sk.recvfrom(1024) # 打印消息和消息的来源(ip,端口号) print(msg.decode('utf-8'), addr) # sendto根据地址(ip,端口号)发送信息 udp_sk.sendto(b'hi', addr) # 关闭客户端 udp_sk.close() # ---------------------------- 客户端 ----------------------------- import socket # ip和端口号 ip_port = ('192.168.3.21',9000) # 指定参数type=socket.SOCK_DGRAM,创建UDP协议的socket客户端对象 udp_sk = socket.socket(type=socket.SOCK_DGRAM) # 不需要连接,sendto根据地址(ip,端口号)直接发送消息 udp_sk.sendto(b'hello',ip_port) # recvfrom接收到:消息和消息的地址(ip,端口号) back_msg, addr = udp_sk.recvfrom(1024) # 打印消息和消息的地址(ip,端口号) print(back_msg.decode('utf-8'), addr)
# ---------------------------基于UDP的聊天--------------------------- # 这个服务端可以喝多个客户端聊,客户端输入q退出自己会退出,不影响,服务端,但是服务端输入q退出,所有客户端都会显示服务端断开连接,然后自己退出. # 一对多聊天,你断对我没影响,我断那么跟我聊的会收到q断开,其他在聊的会捕获异常断开. # ### 服务端 ### import socket # 指定参数type=socket.SOCK_DGRAM,创建UDP协议的socket服务器对象 udp_sk = socket.socket(type=socket.SOCK_DGRAM) # 给服务器对象绑定ip和端口 udp_sk.bind(('192.168.3.21',9000)) while 1: # 接收来自谁的消息 msg, addr = udp_sk.recvfrom(1024) # 打印消息和消息的来源(ip,端口号) print(msg.decode('utf-8'), addr) msg_c = input(">>>") # 发送给谁的消息 udp_sk.sendto(msg_c.encode("utf-8"), addr) if msg_c == 'q': break # 关闭服务端 udp_sk.close() # ### 客户端 ### import socket # ip和端口号 ip_port = ('192.168.3.21',9000) # 指定参数type=socket.SOCK_DGRAM,创建UDP协议的socket客户端对象 udp_sk = socket.socket(type=socket.SOCK_DGRAM) while 1: msg_s = input(">>>") if msg_s == 'q': break # 不需要连接,sendto根据地址(ip,端口号)直接发送消息 udp_sk.sendto(msg_s.encode("utf-8"),ip_port) try: # recvfrom接收到:消息和消息的地址(ip,端口号) back_msg, addr = udp_sk.recvfrom(1024) except Exception as e: print(e, "服务端断开连接") break if back_msg.decode('utf-8') == 'q': break # 打印消息和消息的地址(ip,端口号) print(back_msg.decode('utf-8'), addr) udp_sk.close()
...