1)咱们之前所说的网络分层就是因为说如果说使用一个协议太复杂了,那么我们就需要把这个协议分层,每一个协议都会简单一些,灵活替换也更方便;
2)咱们现在需要实现一个网络计算器;
1)上图是模拟一个计算器服务器和客户端的工作流程,在实际开发中非常常见;
2)进行运算,要吃CPU,进行传输,吃的是网络宽带;
客户端给服务器发送的请求:
字符串:第一个操作数,第二个操作数,运算符;
服务器给客户端返回的响应:计算结果
下面是实现运算服务器的代码
所谓的自定义协议,一定是在开发之前,就约定好的,开发过程中,就要让客户端和服务器,都能够严格遵守协议约定好的格式,直接使用文本+分隔符,假设传输的请求和响应中,各自有几十个字段,况且有些字段是可选的;
1)应用层会使用自己的现成的http协议,同时我们还可以自己约定一个应用层协议,上面的不孕不育搜索的结果后一定先传回的是广告(广告放送的钱多)既要考虑相关性,又要考虑广告商的出价
2)自己进行运算, 要使用大量的CPU资源,本是可以在客户端进行数据的运算的,但是站在占用CPU的资源角度上面考虑,还是搞一个服务器;
这是咱们的客户端的代码:
package demo2; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PrintStream; import java.net.Socket; import java.util.Scanner; public class TcpRequestRequest { public String serverIP; public int serverPort; private Socket socket=null; public TcpRequestRequest(String serverIP, int serverPort) throws IOException { this.serverIP=serverIP; this.serverPort=serverPort; this.socket=new Socket(serverIP,serverPort); } public void start(){ System.out.println("客户端开始进行启动"); try(InputStream inputStream=socket.getInputStream()) { try (OutputStream outputStream = socket.getOutputStream()) { while(true){ //1.先进行输入数据 Scanner scanner=new Scanner(System.in); System.out.println("请输入第一个操作数"); String s1=scanner.nextLine(); System.out.println("请输入操作符"); String s2=scanner.nextLine(); System.out.println("请输入第二个操作数"); String s3=scanner.nextLine(); String request=s1+"B"+s2+"B"+s3; //2.发送数据 //2.1再进行发送数据 PrintStream printStream = new PrintStream(outputStream); printStream.println(request); printStream.flush(); //2.2再进行接受服务器的响应 Scanner result = new Scanner(inputStream); String response = result.nextLine(); //2.3打印在控制台上面 System.out.println(response); System.out.println("是否接下来要继续进行计算?"); String next = scanner.nextLine(); if (next.equals("N")) { System.out.println("即将退出客户端"); break; } } } } catch (IOException e) { e.printStackTrace(); } } public static void main(String[] args) throws IOException { TcpRequestRequest request=new TcpRequestRequest("127.0.0.1",9999); request.start(); } }
下面这个是咱们的服务器的代码:
package demo2; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PrintWriter; import java.net.ServerSocket; import java.net.Socket; import java.util.Scanner; public class TcpClimentServer { public int serverPort; private ServerSocket listensocket=null; public TcpClimentServer(int serverPort) throws IOException { this.serverPort=serverPort; this.listensocket=new ServerSocket(serverPort); } public void start() throws IOException { System.out.println("开始启动我们的服务器"); while(true){ Socket socket=listensocket.accept(); System.out.printf("客户端的IP地址是%s,客户端的端口号是%d",socket.getInetAddress(),socket.getPort()); procession(socket); } } private void procession(Socket socket) { try(InputStream inputStream=socket.getInputStream()) { try(OutputStream outputStream=socket.getOutputStream()){ while(true){ //1.我们先进行读取客户端的请求 Scanner scanner=new Scanner(inputStream); String request=scanner.nextLine(); //2.根据请求进行处理业务逻辑 String response=Process(request); //3.根据业务逻辑来进行计算响应 PrintWriter writer=new PrintWriter(outputStream); writer.println(response); writer.flush(); //4.显示计算结果 System.out.printf("客户端的请求是%s 服务器返回的响应是%s",request,response); } } catch (IOException e) { e.printStackTrace(); } } catch (IOException e) { e.printStackTrace(); } } public String Process(String request){ //1.判断数据是否为空 if(request==null||request.equals("")){ return "当前传输数据有误,什么也没有传输过来"; } //2.按照标点符号来进行分割 String[] strings=request.split("B"); if(strings.length!=3){ return "您当前数据所进行输入的格式是存在错误的"; } //3.取出里面的数字和符号来进行运算 int result=0; Integer s1=Integer.parseInt(strings[0]); Integer s2=Integer.parseInt(strings[2]); String operate=strings[1]; if(operate.equals("+")){ result=s1+s2; }else if(operate.equals("-")){ result=s1-s2; }else if(operate.equals("*")){ result=s1*s2; }else{ result=s1/s2; } return String.valueOf(result); } public static void main(String[] args) throws IOException { TcpClimentServer server=new TcpClimentServer(9999); server.start(); } }
一:应用层协议:这个协议,实际上是和程序员打交道最多的协议了
1)其它四层都是操作系统,驱动,硬件实现好了的,咱们是不需要管
2)应用层:当我们继续进行写应用程序的时候,尤其是应用程序负责网络通信的时候,往往是需要自定义应用层协议的,我们要根据需求,明确要进行传递的信息,我们还要明确传递的数据格式;
3)在我们的应用层这里面,最重要的事情,就是说自己实现一个应用层协议,比较简单也是在工作中经常做的事情;
FTP,SSH,TELNET,DNS(域名解析的协议)
1)使用现成的应用层协议来进行开发
2)程序员自己约定一个协议,来进行开发,只要客户端和服务器自己进行开发的;例如我在网页上面给服务器发送了一个请求,请求中包含着一个链接,然后服务器给浏览器返回了一个响应,这个响应就是一个网页;在这个过程中,使用的协议就是HTTP协议这时我们就可以
使用自定义协议,来约定好请求是什么格式,响应是什么格式,客户端和服务器就可以按照这样的约定来进行开发了;3)总而言之,自定义协议,其实是一个很简单的事情,我们只需要约定请求和响应的详细格式即可,越详细越好,要把各种细节都能够交代到,能够更好的表示当前的信息,咱们也可以自己来约定好格式,也可以基于xml,Json来约定数据格式,甚至还可以通过一些其他的二进制的方式来进行约定数据
回显服务器:无法支持多个客户端并发执行
多线程回显服务器:针对每一个连接,每一个客户端,创建一个线程;
线程池回显服务器:避免频繁创建销毁线程
1)例如我们要提供一个新的需求,要求在外卖软件的首页,显示一个优惠活动,用户参与活动,就可以参加抢红包的操作;
2)客户端:修改界面,可以显示优惠活动的详情;
3)服务器:修改后台逻辑,针对啥样的用户可以参加优惠,具体怎么做可以领到红包,红包金额是多少;
4)当我们的客户端启动的时候,就要向服务器进行查询,当前用户是否可以进行参加活动,服务器就要返回是或者否
5)这个时候在开发功能之前,就要约定好,客户端发的查询请求的功能是怎么样的(带有用户的身份信息),在约定好服务器返回的响应是什么样的(1表示可以表示能参与,0表示不能参与,true表示能参与,false表示不能参与)
1)假设我们现在自己实现一个功能叫做获取用户的订单历史(这个订单历史在数据库里面,我们要从服务器操作数据库来拿,这样的功能就会涉及到前后端的一个交互过程),咱们的前端和后端就是通过网络来进行交互的;
2)在这个交互的过程中我们就需要约定好,前端发送什么样子的数据,后端要返回什么样子的数据,本质上来说就是在进行规划请求和响应之间的信息,以及传数据的格式;
咱们进行查历史订单,查找谁的订单,以及查找哪一个时间段的订单,以及数据太多,你要不要截取一部分;
1)我们前端发送的请求类似于:
{
用户的ID,
查询的起始时间,
查询的结束时间,
显示的条数;
}
2)我们后端返回的响应:
2.1)查询是否成功?如果失败,原因都有哪些?
2.2)返回一个订单数组:
数组里面的每一个元素:
{
商品名,
商品单价,
商品数量,
店铺名称,
总的金额
}
像上面的过程就相当于是设计应用层协议,咱们设计应用层协议,本质上就是做两个工作
1)明确我们要传输的信息
2)明确数据的组织格式,请求的格式是啥样子的,响应是啥样子的
1)当前我们只是给出了一种可能的格式,此处我们这里面的数据的格式,也是可以进行随心所欲的约定的
2)相比之下我们只需要让我们的客户端和服务器都按照一定的格式来进行交互就可以了;
3)上面的这一种设计十分不好,咱们的输出传输效率比较低,这也就说明了是运行效率低;
4)咱们的这个代码可读性也不好,本身开发效率也比较低,正因为如此,大佬们发明了一些比较好的协议的模板,让我们向上套;
XML,JSON,protobuffer,前两种可读性比较好,但是运行效率不高,咱们的第三种可读性不好,但是运行效率比较高;
——————————————————————————————————————————
1)咱们的XML就是一种老牌的数据格式了,虽然现在也在用,但是越来越少了,咱们的这些标签本身占据的空间已经超过了数据本身所占用的空间,也就说引入了更多的辅助信息,我们程序的效率就会受到很大的影响,对于一个服务器程序来说最贵硬件资源就是网络带宽,对于XML来说,虽然提高了可读性,但是又引入了太多的辅助信息,比如说标签名字,这些辅助信息所进行占用的空间甚至已经超过了我们数据本身的一个空间,因为在XML里面要经常表示这些辅助信息,在传输相同的网络数据的时候,这就会导致传输相同数目的请求的时候,占用的网络带宽是更高的,如果说带宽固定,那么相同时间内能够传输的请求数目就是更少的,所以说我们程序的效率就会收到很大的影响;
2)咱们现在看这个XML标签,标签名就是key,标签值就是value,就构成了一个键值对,通过这些标签,就更好的体现了我们数据的可读性,尤其是哪一个部分代表是什么意思,非常一目了然,咱们的一个服务器程序最贵的硬件资源就是网络带宽,因此我们的XML现在已经很少作为应用层协议的设计模板了,我们现在使用XML主要是为了做配置文件;
3)JSON的传输效率虽然要比XML要好一些,但是还是要多进行传递一些冗余信息,就是Key的名字,但是Json格式的数据也是有非常大的缺点,尤其是在进行表示数组的时候,这里面的Key是在不断重复的这也会占用大量的带宽资源但是咱们的JSON是一种最重要的设计模式
4)因此咱们的protobuffer应运而生,这是一种二进制格式的数据,在这种格式的数据里面,不会再包含上面的key的名字了,而是通过顺序或者是一些特殊的符号,来进行区分每一个字段的含义,同时在通过一个IDL文件,来进行描述这个数据格式(来进行描述每一个部分是啥意思),IDL只是起到一个辅助性开发的效果,并不会真正的进行传输,传输的只是二进制的纯粹的数据,似乎传输效率高了,但是开发效率低,可能是把对应的文字换成了对应的二进制数据,咱们通过二进制的数据对这里面的内容重新地进行了编排,甚至有可能做出一些数据压缩,只进行传递一些必要的信息,传输效率会更高,但是这些数据肉眼难以观察;
5)开发效率低,是包含了开发和调试,调试很不方便,如果说咱们的线上环境出现了问题,如果说咱们用JSON,出问题的请求和响应,一目了然(一看就知道哪里出现了问题),如果使用protobuffer,二进制的数据,根本没法用肉眼看,我们就需要自己开发一个专门的程序来进行解析这里面的数据,来进行分析这里面的问题,这就会比较麻烦;
咱们广论开发:咱们的protobuffer里面有IDL,这个就是说咱们的protobuffer约定的一种文件格式,类似于C语言结构体的写法
class message Response{ bool ok=1; string reason=2; repeated Data data=3; }
咱们来进行设计应用层协议,是一件非常普遍的事情,也是一种并不复杂的事情;
设计应用层协议,我们要做的工作就是说:
1)我们要根据需求,明确传输的信息
2)我们要进行明确数据传输的格式,我们要参考现有模板,比如说JSON,XML,protobuffer
1)上面写错了 ,把上面的等于号全部换成,所以说json格式的字符串,是一个键值对结构,键和值之间使用:分割,键与键之间,使用,来进行分割,最外面加上一个{};
2)咱们的Json格式的数据要求key一定要是字符串,因此这里面的key值一定是字符串类型,咱们的值可以是很多种类型,因此这里面的key的引号是可以省略的,但是如果说key包含了一些特殊的符号,比如说像空格,或者一些特殊字符,那么一定要加上引号,值是可以是很多种的,比如说数字,字符串,布尔类型,数组,另一个JSON;
3)在咱们的JSON里面想要表示一个字符串,我们使用单引号,双引号都是可以的,况且说在我们的最后一个键值对后面,可以有逗号也可以没有逗号,相比于咱们的XML,咱们的JSON同样是可以保证可读性的,同时又没有像XML那样繁琐,占用的带宽更少一些;
{ {
num1:10, result=30,
num2:20, }
operator:"+"
}
端口号的用途:标识一个进程,就可以区分当前数据要交给哪个进程来处理
端口号:端口号是一个整数,用来区分进程;但是PID(用于进程的身份标识)也是一个整数(PCB中的一个特性),用来区分进程,为什么在网络编程中,不直接使用PID,而是直接用到端口号这样的概念呢?
端口号是固定不变的,端口号我们可以手动指定,但是PID每次进程启动之后都会发生改变,这是系统自动分配的,我们无法控制;
1)例如客户端要连接一个服务器,客户端就要先知道服务器的IP与PID,一旦服务器进行重启,PID就会发生改变
中国移动:10086--类似于端口号,这是固定不变的
转换人工:工号xxx为您服务
他就类似于PID,每次接通电话可能都不一样
同样的,两个进程无法绑定到同一个端口号
2)系统提供的原生的socket API其中有一个方法就叫做bind,功能就是把端口号和一个socket关联起来,一个进程是可以绑定多个端口的
1)例如在开发服务器的时候,首先会让服务器提供一个业务端口,通过这个端口,就可以提供一些广告搜索的服务(上游服务器,就可以通过这个端口来获取到广告数据)
2)其次服务器还会提供一个调试端口,在服务器运行过程中,其实会涉及到很多很多的数据,有时候为了定位一些问题,就需要查看一些内存中的数据,通过这些调试端口给服务器发送一些调试请求,于是服务器解耦可以返回一些对应的结果;3)直接拿调试器打一些断点可以吗?
如果拿调试器来进行断住程序,此时整个进程就会处于一种阻塞的状态的,这就意味着这个服务器是无法响应正常的业务请求的4)咱们的很多网络服务是进行使用非常常用,非常广泛的服务,为了更好地进行管理,我们就给这些服务分配一些专门的端口号
80---->http服务器
443---->https服务器
23 ------>ssh
23------>ftp服务器
咱们自己的部署http服务器,就可以把他绑到80,也可以把它绑到其他转口