七层模型实现过于复杂,所以是一种理想的模型
TCP/IP 协议很重要,TCP协议(三次握手,四次断开)的优点:可靠的,有链接的(三次握手的链接代价偏高,但通道稳定),主动检测是否断开的方式:心跳。
七层之间的数据转换:
Socket
Socket库底级的库(要对协议,硬件等都要有所了解),若为了效率,倒不如C,C++,所以不常用,但必学。开发中应该用开发效率高的东西,接口较为简单,例如RPC(远程过程调用)库
Socket(双向通讯)是一个简单,标准的,基于一些协议的通讯接口,两端各有一个Socket(可视为插座),通过中间的传输管道进行数据传输,通信(逻辑上的理解)。两个插座的通讯要告诉对方IP地址(已知是TCP/IP协议),端口等信息,才能进行准确的通信。
域名解析为IP地址:浏览器通过DNS解析,拿到IP地址,TCP端口默认是80。
把一个端口(1000以内尽量不要自己用,为了解决进程接通讯的问题,将IP地址(操作系统软管理)利用端口可以分成多份)绑定(或分配)到一个应用进程上,应用程序需要端口信息,要与Socket绑定 --> 文件描述服务是归进程管
异步编程(网络效率较高,异步库)与同步编程(效率不高)
进程间通讯:进程间如何共享数据的问题。
Socket编程是有端到端的,设计Server端与服务端,经典的CS编程
TCP中数据包出错可以进行重发,其中协议的sever和client端是相对的,数据是可以双向传输的,习惯上定义“我在远端,我想你要数据,你返回给我了”你(绑定一个稳定的端口,向别人提供数据的端口)为server。
绑定了一个端口号,端口与你的应用程序建立连接(作为Server端)
第一个请求过来,通过端口连接到了你,通过accept判断是否同意建立连接(进程间跨Socket或网络的链接),若同意则建立Socket通信要返回数据,Server会再建立一个新的Socket,让她去连接新的Socket(这个Socket与你的应用程序通信)。
Client端的Socket不需要绑定(无需处理),端口临时去分配(挑选一个闲置的端口与server通信),IP不能临时(但由于你的本机地址IPv4枯竭了,本机IP看到的是私网地址,然后通过net转化转为公网地址),要固定。多次关闭浏览器,每次建立连接的端口可能是变化的。
建立监听socket:
分配IP,端口,并绑定:
客户端的socket与server建立连接:
一个Socket是一个文件,他会占用一个文件描述符(fd)
一共三个Socket:
接收:
发送:
不发生阻塞的接收:
断开了s2的链接:
关闭server:
清理工作:(主要是归还文件描述符)
服务器端应该先将所有的与对端连接的socket逐个close,最后在将作监听的socket close。
先将监听断掉意味着,我再也不接收链接了,在逐个关闭与对端连接的socket
PyCharm
循环跑起来:
accept方法返回值是一个二元组(新的Socket对象(fd文件描述符,laddr自己的地址,raddr对端地址),对端地址)
Client断开,Server端抛出一个异常:
问题:只有一个accpet(),也就是只能与一个客户端相连,并且进入循环无法主动跳出;新来客户端连接请求不予理睬 --> 多线程解决!
两次绑定同一个监听端口:
做服务端的IP地址应该是不变的,不能是浮动的,否则无法访问,常提供服务的端口应该是用默认或者固定(利于使用)。
二次绑定:
原因:一个IP地址和端口只能对应一个应用程序,一个应用程序可以对应多个IP地址和端口
同步解决方案:
send(传送信息,只不过当前管道已连接,是通的,所以看起来没有阻塞),accept(等对方来连接),recv(等对方传送的信息) 的默认工作模式都是阻塞的
非阻塞的异步代码较困难:
群聊:
服务端应该对应一个类:(要注意循环,阻塞,线程的建立与消亡)
每一个server的初始化应该记录IP和Port,每个server应该监听在不同的端口上,所以每一个server是一个单独的socket(实例化一个socket) --> 与每个实例相关(可以给默认值,但每一个server的IP和Port应该是不同的)
上图所示,accept的特性是阻塞,写在start方法中启动后就会阻塞状态,其他的server也无法进行。应该更改为:
(注意TCP传输字节流,需要字符串转码为Bytes,要注意编码问题)
存在的问题:accept方法(两行代码)执行完再继续向下执行时,start中的线程消亡了,无法再继续与客户端建立连接
因为每个循环中的第一个语句都是阻塞的,不新开辟一个线程是不可以的。
测试再创建一个连接:
到此为止,实现了一个服务多个客户端的单独聊天,并且缺少socket回收(容器存储)。
解决一对多发送信息的问题:
退出:
主线程 输入quit,报错,但线程关闭:
原因是主线程的accept在阻塞的状态,程序强行关闭了所以报错。
子线程(客户端)强制断开连接:
更改recv()方法:
若在Linux服务器上跑,还要在调试一遍,不同操作系统的实现,抛异常可能会不同
最常用为recv和send:
大致意思:sendfile尽量使用零拷贝机制(直接在内核空间读取一次,其他都打标记,最后一次性推出,只有一个副本,无复杂的操作);send方式(一个字节一个字节读出,再一个字节一个字节传送)IO读出,到内核空间到用户空间,从用户空间到内核空间,再由内核空间发出。
将recv和send转换为read和write方法:
可以使用makefile是因为,socket对象有文件描述符,文件对象也有文件描述符,所以可以互相转换,一般由socket转为file,因为文件操作更简单(操作字符)
主函数封装:
f.fileno :实际上用的是当前socket的文件描述符,说以makefile传回的是一个类文件对象