计算机网络计算机网络是指将地理位置不同的具有独立功能的多台计算机及其外部设备,通过通信线路连接起来,在网络操作系统,网络管理软件及网络通信协议的管理和协调下,实现资源共享和信息传递的计算机系统。网络编程的目的:传播交流选项,数据交换,通信。
编写项目之前需要遵循的代码层面上的一些规范(例如,运行的步骤,流程等)
c/s架构
c:client客户端s:server服务端计算机或手机下载的各类app本质上就是客户端优势是:下载对应的客户端,可以在客户端体验高度定制服务劣势是:使用必须下载!!b/s架构
b:broswer浏览器s:server服务器通过浏览器来充当各个服务器的客户端,用于想要体验服务又不用下载客户端劣势是:网页的各种功能花里胡哨,观看效果不佳架构发展趋势
1.统一接口原则(可以通过一个接口去往其他程序)2.cs和bs交叉使用,避免各自的劣势二、网络编程基于网络编写代码,程序可以实现远程数据交互。
规定了计算机涉及到远程交互的时候必须要经过相同的流程数据发送出去的时候是从上往下走数据接收回来的时候是从下往上走应用层
主要是程序员自己编写代码的地方,有什么协议和规范取决于程序员自己常见协议有:HTTP,HTTPS等等表示层
会话层
传输层
1、port协议
1.三次握手(建连接)
基于UDP发送的数据没有任何通道也没有任何的限制,缺点是数据容易丢失(可以基于发送过程做一些优化操作)服务端代码importsocketres=socket.socket(type=socket.SOCK_DGRAM)res.bind(('127.0.0.1',8080))msg,address=res.recvfrom(1024)#接收客户端发送过来的消息print('msg>>>%s'%msg.decode('utf8'))#由于UDP没有双向通道所以每次发送消息都会带着它的地址print('address>>>>:',address)res.sendto('服务端'.encode('utf8'),address)客户端代码importsocketc=socket.socket(type=socket.SOCK_DGRAM)server_address=('127.0.0.1',8080)c.sendto('客户端'.encode('utf8'),server_address)msg,address=c.recvfrom(1024)print('msg>>>"%s'%msg.decode('utf8'))print('address>>>>>:',address)
ip协议:规定了任何接入互联网的计算机都必须有IP地址每个ip地址都自带定位IP地址:IPV4:点分十进制最小:0.0.0.0最大:255.255.255.255IPV6:无限大了能够给地球上的每一粒沙子都分一个ip地址IP
IP地址能定位到唯一的一台计算机127.0.0.1本机地址ip地址分类IPV4/IPV6IPV4127.0.0.14个字节组成,0-255总共42亿已用尽IPV6128位8个无符号整数(16进制)例如0000:1111:2222:3456:aaaa:bbbb:cccc:defg公网(互联网)-私网(局域网)ABCD类192.168.xxx.xxxInetAddress类常用方法
packagecom.taodou;importjava.net.InetAddress;importjava.net.UnknownHostException;publicclassTestInetAddress{publicstaticvoidmain(String[]args){try{//查询本机地址InetAddressinetAddress1=InetAddress.getByName("127.0.0.1");InetAddressinetAddress2=InetAddress.getByName("LocalHost");InetAddressinetAddress3=InetAddress.getLocalHost();System.out.println(inetAddress1);System.out.println(inetAddress2);System.out.println(inetAddress3);System.out.println("------------------------------------");//查询网址IP地址InetAddressinetAddress4=InetAddress.getByName("www.baidu.com");System.out.println(inetAddress4);System.out.println(inetAddress4.getCanonicalHostName());//规范名System.out.println(inetAddress4.getHostAddress());//IPSystem.out.println(inetAddress4.getHostName());//域名或主机名}catch(UnknownHostExceptione){e.printStackTrace();}}}端口
端口表示计算机上的一个程序的进程
不同的进程有不同的端口号端口号0~65536端口号有两种TCP,UDP65536*2单协议下端口号不能冲突端口分类公有端口(系统程序)0~1023HTTP:80HTTPS:443FTP:21程序注册端口1024~49151Tomcat:8080MySQL:3306动态,私有49152~65536packagecom.taodou;importjava.net.InetSocketAddress;publicclassTestInetSocketAddress{publicstaticvoidmain(String[]args){InetSocketAddressinetSocketAddress1=newInetSocketAddress("127.0.0.1",8080);InetSocketAddressinetSocketAddress2=newInetSocketAddress("LocalHost",8080);System.out.println(inetSocketAddress1);System.out.println(inetSocketAddress2);System.out.println(inetSocketAddress1.getAddress());System.out.println(inetSocketAddress1.getHostName());System.out.println(inetSocketAddress1.getPort());}}数据链路层
1.规定了电信号的分组方式2.规定了每台计算机都必须有一块网卡,网卡上必须有一串记录(电脑的以太网址也称mac地址,身份证号)3.mac地址:由12位16进制数组成前6位:厂商编号后6位:生产流水线号可以根据该地址查找到计算机,基于mac地址实现数据交互物理连接层
保证物理连接介质的条件,传递电信号(主要研究插网线情况)
1.交换机能够让接入交换机的多台计算机实现彼此互联2.以太网通信(mac通信)有了交换机以后,根据电脑的mac地址就可以实现数据交互广播:就是在交换机中发出请求,所有接入交换机的设备都能收到单播:只有被查找设备才会回复相应信息缺陷:1.mac地址通信仅限于局域网2.接入交换机的设备过多的话可能会造成广播风暴3.局域网有某个固定的区域组成的网络4.路由器将多个局域网连接到一起的设备三、socket套接字主要作用于软件层和应用层中间,作为一个接口。如果没有socket,那么我们就需要手动操作各个层之间的代码了!!
1.一开始,套接字被设计用在同一台主机上多个应用程序之间的通讯。这也被称进程间通讯,或IPC。套接字有两种(或者称为有两个种族),分别是基于文件型的和基于网络型的。2.基于文件类型的套接字家族:套接字家族的名字:AF_UNIX3.基于网络类型的套接字家族:套接字家族的名字:AF_INET,所有地址家族中,AF_INET是使用最广泛的一个socket实例
先运行服务端再运行客户端1.服务端importsocket#1.先创建一个socket对象server=socket.socket()#绑定一个固定的地址IP和portserver.bind(('192.168.1.169',8080))#半连接池server.listen(5)#开业等待接口sock,address=server.accept()print(sock,address)#sock是双向通道address是客户端地址65459#数据交互sock.send(b'hello')#朝客户端发送数据res=sock.recv(1024)#接收客户端发送的数据print(res)#断开连接sock.close()#断连接server.close()#关机------------2.客户端importsocket#1.产生一个socket对象client=socket.socket()#2.连接服务端(拼接服务端的ip和port)client.connect(('192.168.1.169',8080))#3.数据交互res=client.recv(1024)#接收服务端发送的数据print(res)client.send(b'hei')#朝服务端发送数据#关闭client.close()#直接把双向通道关闭代码优化
#服务端importsockets=socket.socket()s.bind(('192.168.1.169',8080))s.listen(5)sock,address=s.accept()sock.send(b'hello')sock.send(b'hai')sock.send(b'hahaha')------------#客户端importsocketres=socket.socket()res.connect(('192.168.1.169',8080))c=res.recv(1024)print(c.decode('utf8'))c=res.recv(1024)print(c.decode('utf8'))c=res.recv(1024)print(c.decode('utf8'))打印结果:hellohaihahaha#出现了粘包现象3.4struct模块简介
struct模块无论数据长度是多少都可以帮你打包成固定长度然后基于该固定长度还可以反向解析出真实长度ps:对于数据量特别大的模块,会直接报错粘包问题的解决方案
#服务端1.先将真实数据的长度制作成固定长度42.先发送固定长度的报头3.再发送真实数据#客户端1.先接收固定长度的报头42.再根据报头解压出真实长度3.根据真实长度接收即可代码实操
异步IO相比同步IO不会阻塞当前程序的执行,可以继续向下执行。即当应用程序发起一个IO操作后,调用者不会立刻得到结果,而是在内核完成IO操作后,通过信号或回调来通知调用者。
信号驱动IO是异步IO的一种实现,在异步IO中,当文件描述符上可以执行I/O操作时,进程可以请求内核为自己发送一个信号。之后进程就可以执行任何其他任务直到文件描述符可以执行I/O操作为止,此时内核会发送信号给进程。
使用信号驱动,程序需要按照如下步骤执行:
以上步骤完成后,进程可以去执行其他的任务,当I/O就绪时,内核会向进程发送一个SIGIO信号,当进程接收到信号时,会执行预先注册号的信号处理函数,这样就可以在信号处理函数中进行I/O操作了
使能O_ASYNC
调用open时无法通过指定O_ASYNC标志来使能异步I/O,但是可以通过fcntl()函数添加O_ASYNC标志来使能I/O:
fcntl(fd,F_SETOWN,getpid());//也可以传入其他进程的pid注册SIGIO信号的处理函数
通过signal()或sigaction()函数为SIGIO信号注册一个信号处理函数,当进程接收到内核发送过来的SIGIO信号时,会执行该函数。
代码实例:
#define_GNU_SOURCE//F_SETSIG#include
但是使用默认信号SIGIO会存在一些问题,SIGIO是标准信号,不可靠信号,非实时信号,不支持信号排队机制,不知道文描述符发生了什么事件,未判断文件描述符是否处于可读的就绪态,所以需要进一步优化(实时信号替换)。
1.使用实时信号替换默认信号SIGIO
比如使用SIGRTMIN信号替换SIGIO,比如:
fcntl(fd,F_SETSIG,SIGRTMIN);2.使用sigaction()函数注册信号处理函数
在应用程序中需要为实时信号注册信号处理函数,使用sigaction函数进行注册,sigaction原型:
#include
#define_GNU_SOURCE//F_SETSIG#include
LinuxNativeAIO是Linux支持的原生AIO,很多第三方的异步IO库,比如libeio和glibcAIO。很多三方库异步IO库不是真正的异步IO,而是通过多线程来模拟异步IO,比如libeio。
aio_*系列的调用是有glibc提供的,是glibc用线程+阻塞调用来模拟的,性能较差,为了能更多的控制io行为,可以使用更低级的libaio。
Ubuntu安装livaio:
sudoaptinstalllibaio-devLinuxAIO执行流程:
Linux原生AIO处理流程:
从上面流程可以看出,Linux异步IO操作主要由两个步骤组成:
实例代码:
ccaio_demo.c-laio运行结果:
目录下会出现一个aio.txt的文件,内容为1024个@字符
程序说明:
以上示例使用while检测,还可以使用epoll结合eventfd,结合事件驱动的方式来获取异步IO操作的结果。
一个IO操作其实分成了两个步骤:发起IO请求和实际的IO操作。
同步IO和异步IO的区别就在于第二个步骤是否阻塞,如果实际的IO读写阻塞请求进程,那么就是同步IO。
阻塞IO和非阻塞IO的区别在于第一步,发起IO请求是否会被阻塞,如果阻塞直到完成那么就是传统的阻塞IO,如果不阻塞,那么就是非阻塞IO。
同步和异步是针对应用程序和内核的交互而言的,同步指的是用户进程触发IO操作并等待或者轮询的去查看IO操作是否就绪,而异步是指用户进程触发IO操作以后便开始做自己的事情,而当IO操作已经完成的时候会得到IO完成的通知。而阻塞和非阻塞是针对于进程在访问数据的时候,根据IO操作的就绪状态来采取的不同方式,说白了是一种读取或者写入操作函数的实现方式,阻塞方式下读取或者写入函数将一直等待,而非阻塞方式下,读取或者写入函数会立即返回一个状态值。
所以,IO操作可以分为3类:同步阻塞(即早期的IO操作)、同步非阻塞(NIO)、异步(AIO)。
同步阻塞:在此种方式下,用户进程在发起一个IO操作以后,必须等待IO操作的完成,只有当真正完成了IO操作以后,用户进程才能运行。JAVA传统的IO模型属于此种方式。
同步非阻塞:在此种方式下,用户进程发起一个IO操作以后边可返回做其它事情,但是用户进程需要时不时的询问IO操作是否就绪,这就要求用户进程不停的去询问,从而引入不必要的CPU资源浪费。其中目前JAVA的NIO就属于同步非阻塞IO。
异步:此种方式下是指应用发起一个IO操作以后,不等待内核IO操作的完成,等内核完成IO操作以后会通知应用程序。