一)熟悉TCP/IP五层协议:
1)封装:就是在数据中添加一些辅助传输的信息;
2)分用:就是解析这些信息
3)发送数据的时候,上层协议要把数据交给下层协议,由下层协议来添加一些信息
4)接收数据的时候,下层协议要把数据交给上层协议,有上层协议来进行进一步的解析
5)传输层和网络层是操作系统的内核实现的,数据链路层是驱动实现的,物理层是硬件实现的,应用层就是应用程序实现的,层层封装,层层分用,最终成为了物理层上面的01这样的数据流,转化成高低电平或者是高频低频电磁波,这些数据通过物理层的介质进行传输
咱们的传输层和网络层,是依靠操作系统内核实现的,数据链路层是依靠驱动实现的,物理层是依靠硬件来进行实现的
一)发送数据在TCP/IP五层协议的扭转过程:
以QQ的发送方和接收方为例,此时发送方发送一个hello
1)QQ软件会按照一定的规则,QQ程序员约定的通信协议把根据用户输入的字符串,并把这个字符串构造成一个应用层数据包,也是一个应用层的协议报文,咱们在这里面所说的协议是一种约定,报文就是说遵循了这个约定的一组数据;
2)咱们的QQ中的代码就会根据程序员所进行设计的应用层协议,来进行构建出一个应用层的数据报文,咱们的这个协议长成什么样子,都是由程序员来自己进行约定的
咱们的QQ使用的应用层协议,完全是由开发QQ的程序员来进行约定的
咱们的LOL使用的应用层的协议,完全是由LOL的程序员来进行约定的
咱们的淘宝使用的应用层的协议,完全是由淘宝的程序员来进行约定的
换句话说咱们作为一个外人,是不知道QQ所进行使用的应用层协议,并且显然上面的这些不同程序中所进行使用的应用层的协议是不一样的,所以在工作中,也是需要按照需求来进行确定应用层的协议
3)其他传输层,网络层的协议都是现成的,都是操作系统,硬件,驱动已经设计好的,但是应用层的协议,是程序员自己设定的;
1.1)应用层:
咱们的应用层协议就会调用操作系统提供的API,我们称之为SocketAPI,把应用层的数据交给传输层,是用户态做的,进入到咱们的传输层,就是相当于进入到操作系统内核了;
1.2)传输层(操作系统内核)
咱们的传输层根据刚才传输过来的数据,基于当前的传输层协议,来构建出一个传输层的协议报文
在咱们的TCP报头里面,是存在着很多信息的,其中最重要的是,就是源端口和目的端口;
这个TCP包头其实就是字符串拼接的意思,报头中的信息和应用层数据包中打包的信息都是辅助网络传输的信息,传输层又把这个数据交给网络层;
1.3)网络层:操作系统的内核
1)网络层又会把刚才传输层包装好的数据再打包成一个网络层的数据包,此处使用的是IP协议,咱们的网络这里面的封装,主要是为了加上辅助传输的信息,是为了发送给正确的人
2)咱们这里面的网络层拿到了完整的传输层数据包,有会根据当前的网络层协议IP协议,再次封装,把咱们的TCP数据包构造成IP数据包,还是添加上一个协议报头;
咱们的IP数据包里面有有原IP和目的IP,此处的IP报头,也是一个字符串拼接,紧接着当前的网络层协议会把这个IP数据包交给数据链路层
1.4)数据链路层:(驱动程序)
1)在IP数据包的基础上面,会根据当前使用的的数据链路层的协议,给进行构造出一个数据链路层的数据包,典型的数据链路层的协议,就叫做以太网,就会构造出一个以太网数据帧;
2)数据链路层:数据链路层会把这个数据包打包成数据链路层的数据包,例如:在这层协议中使用的是以太网,就会把这个数据按照以太网的数据帧的格式进行储存,此处的桢,就是一个字符串拼接
3)先把IP数据包视为一个整体,我们就是类似于字符串拼接的方式来构造出数据帧头和数据帧尾
1)咱们的以太网数据帧的最重要的信息就是说接下来应该把这个数据传输给哪一个地址;
2)咱们的IP协议里面所写的地址是起点和终点,但是咱们的以太网数据帧祯头祯尾里面所写的地址,是接下来一个相邻节点的地址;
3)随着我们数据往下一个设备进行转发,咱们祯头中的地址,其实在一直发生着改变;
4)随着数据在往下一个设备进行转发,咱们的祯头祯尾里面写的地址一直在发生改变;
5)咱们的祯尾里面其实是校验和,咱们的数据链路层,又会把这个数据交给物理层
1.5)物理层(硬件设备)
1)物理层会把这个以太网数据祯的数据以二进制的形式,转换成0/1这样的光信号或电信号,并进行传输,通过网络/光纤/无线电网络发送,所以说发送方层层的构造拼装数据就叫做封装
2)物理层所做的工作,其实本质上来说就是我们把刚才的以太网数据祯,其实本质上就是一组0和1,我们会把这里面的0和1变成高低电平,通过网线传输出去,或者说我们把这里面的0和1变成高频或者是低频的电磁波,通过光纤或者无线的方式来进行传输出去
3)无论构造怎么样子的数据,基本上都是0和1,无非都是一些高电平低电平,一些高频低频的电磁波而已,所以说这是一种数据完全发送出去的过程
4)封装就是说从上到下,数据从上层协议交给下层协议,由下层协议来进行封装,这样子就可以来进行构造出了一个该层协议的报文,然后会一直往下,直到物理层才会真正地结束
1.6)中间链路的转发:
1)到了刚才这一步之后之后,我数据就已经离开了当前主机,前往了下一个设备,下一个设备,可能是路由器,也有可能是交换机,也有可能是其他设备;
2)比如说要把数据从A发送给B,A和B之间并不是网线直连的,在我们的中间的过程中就会有很多的路由器和交换机来进行完成路由的转发;
3)只要是路由器,我们就解析到三层然后进行封装,是为了拿到目的IP,然后进一步的会进行规划路径,咱们的交换机会进行解析到数据链路层,这里面会更新mac地址,咱们的路由器在这里面也是会更新mac地址的
4)无论网络环境多么的复杂,这里面的整体的传输过程都是类似的,只是我们在不断的进行封装和分用的过程;
1.7)物理层(硬件设备也是一个网卡)
1)光电信号到达了接收端主机后,不考虑中间的转发过程,最先到达接收端的物理层,物理层把这个光电信号转成0/1的二进制序列,也就是得到了那个以太网数据帧,把该数据帧交给数据链路层;
2)主机B的网卡感知到了一组高低电平,然后就会把这些电平翻译成0和1的一串数据,然后这一串0和1就是一个完整的以太网数据帧的格式,于是物理层就会把我们的这些数据交给数据链路层,开始向上进行交付;
1.8)数据链路层(驱动程序):
1)数据链路层接收到物理层的数据帧,
2)数据帧再对这个以太网数据帧进行分析,将数据帧去掉帧头和桢尾,把中间的负载获取到,交给上层协议(网络层);
3)咱们的数据链路层会针对这个数据进行解析,去掉以太网数据祯头和以太网数据祯尾,取出里面的IP数据包,然后再交给网络层协议;
1.9)网络层(操作系统内核)
1)网络层获取到数据链路层传递过来的数据包后,IP协议就会按照协议格式再来解析这个数据包,去掉IP报头,获取其中的负载部分,再把这个部分交给上层的传输层,这个负载部分就是一个完整的TCP的数据包;
2)咱们的网络层(IP协议)又会对这个数据包进行解析,会去掉IP协议报头,取出里面的TCP数据包,然后交给传输层来进行解析
2.0)传输层(操作系统内核)
传输层获取到了数据包,传输层的TCP协议再去解析这个数据,去掉TCP报头后,就交给应用层了
2.1)应用层(应用程序和QQ)
1)咱们的应用层就会调用操作系统的SocketAPI,于是就从内核中读取到了这个应用层数据包
2)咱们的应用程序就会按照应用层的协议再进行解析,解析这里面的数据都是什么意思,尤其是发件人信息和hello这个内容,时间,根据解析结果显示在屏幕上
二)网络编程初始
2.1)初始网络编程:
网络编程的定义:是指网络上面的主机,通过不同的进程,以编程的方式来实现网络通信,或者称之为网络数据传输;
1)本质上来说网络编程套接字是操作系统给应用程序提供的一组API,这个API咱们叫做SocketAPI,Socket的原意叫做插座,socket我们是可以视为应用层和传输层之间的通信桥梁
2)也就是说咱们的应用层和传输层之间想要进行传输数据,靠的就是操作系统提供的SocketAPI,因为咱们的传输层的核心协议有两种,一种是TCP,一种是UDP,那么此时对应的SocketAPI也是有两组,并且他们的差异是很大的;
3)由于咱们的TCP协议和UDP协议的,差别很大,因此这两组的API差别也是很大的;
咱们的socket本质上就是说我们的传输层和应用层之间进行调用的一个接口,也就是说咱们的操作系统内核给应用程序提供的一组API,我们一进行调用Socket就可以和我们的操作系统内核来进行交互;
4)本身操作系统提供的API只有两大类:
4.1)流套接字:底层是基于TCP协议来实现的
4.2)数据报套接字:底层是基于UDP来实现的
所以本质上来说Socket本身也是属于传输层上面的东西,在应用层里面,程序员直接使用SocketAPI,本身SocketAPI也是由传输层协议暴露给应用层的
1)通过代码来控制,让两个主机之间的进程可以进行数据交互,例如我使用了qq发送一个消息,这个消息就是通过我电脑上的QQ客户端的进程,先发送给了腾讯的服务器进程;
再由腾讯的服务器进程,把这个消息转发给对方电脑的QQ进程;
2)操作系统就把网络编程的一些相关操作封装起来了,提供了一组API供程序员使用,这时就可以进行网络通信的操作了,网络编程的各种操作是由操作系统提供的功能,访问网络的核心设备是网卡,网卡也是由操作系统来管理;
3)socket是一个插槽,是操作系统提供网络编程API的总称,进行网络编程的核心就是通过代码来操作网卡这个硬件设备,操作系统对于网卡这个硬件设备进行了抽象,进程想要操作网卡的时候,就会打开一个Socket文件,通过这个文件就可以读写网卡了
4)这组API是socket类,操作系统提供的API本质上是C语言风格的接口,在java中不可以直接使用的,JDK实际上是对C语言这里的socket这里的API进行了封装,在标准库中有一种类,这组类可以让我们实现网络编程,这组类本质上调用的是操作系统提供的socket提供的API,有点类似于跨语言调用;
5)这里面的socket本质上就是传输层和应用层之间相互调用的接口,调用socketAPI,就可以实现应用程序和内核进行交互;
咱们最常见的场景:客户端是指给用户使用的程序,服务器端是提供用户服务的程序
1)咱们的客户端先进行发送请求给服务器
2)咱们的服务器根据请求数据,执行相应的业务处理
3)服务器返回响应,发送业务处理结果
4)客户端根据响应数据,展示处理结果,展示获取的资源,或者是保存资源的处理结果
因为咱们服务器的定义就是说是被动接受请求的这一方主动发送请求的这一方就叫做客户端
2.2)TCP和UDP区别:
1)TCP:有连接 可靠运输 面向字节流 全双工 大小无限 2)UDP:无连接 不可靠传输 面向数据包 全双工 大小有限,最多一次64K
有连接:例如甲给乙打电话,会先拨号,过程中会发出得得的响声,直到对方接通我才可以说话,难道是我在拨号之后立即就会说同学你好吗?好像不能这么说,因为我这边说了也没啥用只有说对方把电话接起来我这里才可以说,所以说有连接,必须我们两方接通,我们才可以进行交互数据,但是还有可能是说发生不接通的情况,那么假设此时我们不进行接通,那么我们就无法进行相互之间传输数据
无连接:发微信,总而言之我都可以发出去,我不管你看不看,发出去即可,我们两方不需要进行接通,不需要进行连接,就直接可以进行交互数据;
TCP就是对方把电话接起来了,才会进行传输数据,UDP直接进行传输,不用直接建立连接
可靠运输:数据传输过程中,发送发知道接收方是否收到请求了,有没有接收到数据
不可靠运输:数据传输过程中,发送方不知道接收方是否收到了请求了,有没有接收到数据
错误理解:
1)可靠传输就是说数据发送过去之后可以被对方百分之百的可以接收到;
2)可靠传输就是安全的传输,安全是指你这个数据在进行传输的过程中,不容易被窃取,不容易被篡改;
可靠是指:我给对方发送过去的数据,我知道对方是有收到还是没有收到
比如说发微信就是不可靠传输,我发送的数据对方收没收到我就不知道;
比如说打电话就是可靠传输,我发送的数据根据对方的是否回话
可靠传输与不可靠传输,不是说发送的数据,100%就可以接收到,而是说可以感知我发送出去的数据对方是否接收到了? 看对方是否有回应?
1)举个例子:打电话就是一种可靠运输,例如A在给B打电话,B接通了之后,A说话,B一直哼哈答应着,此时A就知道了B收到信息了,但是过了一会A发现B不答应了,此时就可知发送方知道接收方收不到消息了
2)但是对于发微信来说,不知道是不是对方的手机收到的,B看没看A发送的消息无从得知;
注意:可靠性并不等于安全性,安全性是指发的内容不可以被截获,就算截获了也不可以知道里面是什么;
面向字节流:为了发送100个字节,一次发一个字节,重复100次,也可以一次发10个字节,重复10次,也可以一次全部发完,非常灵活地完成发送和接受,一次传输的数据是字节的整数倍就可以了,文件的读写与TCP都是面向字节流的
面向数据报:以一个一个的数据报单位为基本发射和接收单位,每个数据报多大,不同的协议有不同的规定,一个数据包都会明确大小,我们的一次发送和一次接收都是一个完整的数据包,不能是半个,也不能是一个半,对于数据包来说 ,传输数据是一块一块的,发送一次数据假如说是100个字节,必须要一次进行发送,接受也是必须一次性接收100个字节,而是不能分100次,每一次接受一个字节
发送的时候,一次至少发送一个数据报,如果一次尝试一次发一个半,结果只能发送一个;接受的时候,一次至少接收一个数据报,如果尝试接收半个,就会接受失败;
全双工:双向通讯,A和B可以互相发送数据
半双工:单向通信,要么A给B打,要么B给A打,做过网线,一个网线里面有8根线,就可以实现全双工咱们的全双工是指一条链路双向通信,咱们的半双工是指一条链路,单向通信
1)Socket使用的tcp连接,需要先连接之后才能发送数据。
2)DatagramSocket使用的UDP连接,客户端不需要先连接数据,可以直接发送给指定服务端,可以直接通过receive命令来接受数据
2.3)UDP网络编程:
操作系统中将网卡这种硬件设备也抽象成了文件进行处理, 这个Soket对象也就成了文件描述表上面的一项,通过操作这个Socket文件来间接的操作网卡, 就可以通信了
1)DatagramPacket就表示一个UDP数据包,发送一次数据就相当于是发送一次DatagramPacket,接受一个UDP数据包,就相当于是接受一次DatagramPacket,在这个过程中都是以一个DatagramPacket为基本单位来进行接收和发送数据的;
DatagramPacket代表了一个UDP数据包,也就是说使用UDP来进行传输数据的基本单位,每一次我们进行发送或者接收数据,都是在传输一个具体的DatagramPacket对象;
2)DatagramPacket在构造的时候,需要指定一个缓冲区,也就是说,数据放在哪里需要开辟一个内存空间,receive方法中的参数是DatagramPacket是一个输出参数,socket读到的数据会设置到这个参数的对象中也就是说DatagramSocket接收到的数据会交给receive中的接收缓冲区
2)DatagramSocket是客户端和服务器发送网络数据的类,有了这个Socket对象就可以和另一台主机通信了,但是要想和多个不同的主机进行通信,就需要创建多个Socket对象,使用DatagramSocket既可以发送数据也可以接受数据,体现了UDP全双工的特点
创建了一个DatagramSocket,表示创建了一个UDP版本的socket对象,socket对象又代表着操作系统的一个socket文件,而socket文件有代表着网卡硬件设备的抽象体现
客户端IP默认是本机IP,端口号是随机分配的
服务器IP默认是主机IP,端口号是自己来指定的;
2.1)构造方法
2.2)客户端和服务器进行通信的核心方法:
1)咱们在这里面的receive()方法是可能会阻塞的,客户端啥时候给服务器来进行发送请求,这是不确定的,客户端发送了,receive()就会立即返回
2)如果客户端不发送数据,那么receive()方法就会一直阻塞
再进行处理业务的过程中:
1)此时接收到的Datagrampacket里面有客户端或者是服务器发送的数据,但是为了更好的处理数据,还是要把Datagrampacket中的内容转化成字符串来处理;
String request=new String(requestpacket.getData(),0,requestpacket.getLength());
2)处理完成服务器有响应之后,还要把字符串类型转化成Datagrampacket类型
这个时候可以直接使用DatagramPacket的构造方法就可以将String类型转化成字节数组再转化成变成DatagramPacket类型;
咱们的具体发送和接受数据,是依靠DatagramPacket来进行发送和接收的
但是具体计算业务数据,还是依靠字符串来进行计算的
3)DatagramSocket只需要包含本身这个客户端或者服务器主机本身的信息,但是DatagramSocket接收信息,但是假设DatagramSocket在进行发送信息的时候,也就是说发送数据包的时候,一部分要进行指定数据包的内容到底是什么?还有就是说一定要在DatagramPacket中指定这个数据包要被DatagramSocket数据包要进行发送给谁,也就是说要再DatagramPacket中进行指定目标主机的IP地址+端口号;
4)当前的场景中,哪一个客户端来进行发送的数据,就把数据返回给哪一个客户端,要进行发送哪一个地址+端口,咱们的DatagramPacket就包含了是哪一个主机发送过来的;
DatagramPacket实例.getSocketAddress(),也可以通过程序员手动指定IP地址和端口号
为什么会有进程构建Socket失败的情况呢?
1)端口号已经被占用了,同一个主机上面的两个应用程序也是不能有相同的端口号,可能就是说这个端口号已经被一个进程标识了,使用了,一个人可以有多个电话号码,但是两个人不可能有相同的电话号码;如果说这个端口号已经被其他的进程所绑定了,此时我们的进程再想绑定这个端口号,就会绑定失败;
2)每一个进程可以打开的文件个数,是有上限的,如果说这个进程之前已经打开了很多很多的文件,那么就有可能导致此处的socket不能正常打开了;
3)咱们的一个进程可以绑定多个端口,这是可以的,一个进程可以创建出多个socket对象,每一个socket对象都可能绑定自己的端口,但是一个端口号不能被绑定多个进程所绑定;
2.4)基于UDP的回显服务器:服务器+客户端
回显服务器设计步骤:
1)创建Socket实例对象,也就是DatagramSocket对象,需要指定服务器的端口号,因为服务器是被动接受和请求的那一方,而客户端时主动发送请求的那一方,客户端必须得知道服务器在那里才可以发送请求,也就是说此时必须知道服务器的端口号,receive内部会对参数对象进行填充数据,填充的数据来源于网卡
2)服务器启动,循环读取客户端请求,服务器将客户端发送的数据填充到提前创建好的DatagramPacket中,这里面得到的请求是包含客户端的地址信息也就是IP地址+端口号,可以通过客户端packet的getSocketAddress返回
3)处理客户端请求,计算响应,这里面实现的是一个回显服务,直接根据客户端的请求返回对应的字符串即可,但是在实际开发中这个过程的处理是最麻烦的
4)将响应返回给客户端,要将响应数据构建成DatagramPacket对象
5)这里response.getBytes().length要注意不能写成response.length(), 因为DatagramPacket不认字符只认字节, response.length()获取的是有效字符数, response.getBytes().length获取的是有效字节数.
package com.example.demo.Controller.Socket;import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.SocketException;public class UdpServer {public DatagramSocket socket=null;public int port;//创建一个服务器public UdpServer(int port) throws SocketException {this.port=port;this.socket=new DatagramSocket(port);}public void StartUdpSever() throws IOException {System.out.println("服务器启动");while(true) {//1.接受请求,并将接收到的请求转化成一个字符串DatagramPacket requestPacket = new DatagramPacket(new byte[4096], 4096);socket.receive(requestPacket);System.out.println("此时客户端的IP地址和端口号是"+requestPacket.getSocketAddress());String request = new String(requestPacket.getData(),0,requestPacket.getLength());System.out.println("服务器接收到了数据" + request);//2.处理请求并解析String response = process(request);//3.构建响应的UDP数据包并且返回响应DatagramPacket responsePacket = new DatagramPacket(response.getBytes(), response.getBytes().length, requestPacket.getSocketAddress());socket.send(responsePacket);System.out.println("服务器返回数据");}}private String process(String request) {return request;}public static void main(String[] args) throws IOException {UdpServer server=new UdpServer(9090);server.StartUdpSever();}}
UDP客户端设计步骤:
1)创建DatagramSocket实例对象,可以指定端口号来进行创建也可以让系统随机分配
2)用户输入请求,并使用DatagramPacket包装用户输入的请求注意给出服务器的IP地址+端口号
3)读取服务器返回的响应并进行处理
package com.example.demo.Controller.Socket;import java.io.IOException; import java.net.*; import java.util.Scanner;public class UdpClient {public DatagramSocket socket=null;public UdpClient() throws SocketException {this.socket=new DatagramSocket();}public void StartUdpClient(String serverIP,int serverPort) throws IOException {System.out.println("客户端开始进行启动");Scanner sc=new Scanner(System.in);while(true){//1.构建请求数据并封装成UDP数据包发送给服务器String request=sc.nextLine();DatagramPacket requestPacket=new DatagramPacket(request.getBytes(),request.getBytes().length, InetAddress.getByName(serverIP),serverPort);socket.send(requestPacket);//2.等待服务器返回数据DatagramPacket responsePacket=new DatagramPacket(new byte[4096],4096);socket.receive(responsePacket);//3.将服务器返回的数据包解析成字符串String response=new String(responsePacket.getData(),0,responsePacket.getLength());System.out.println(response);}}public static void main(String[] args) throws IOException {UdpClient client=new UdpClient();client.StartUdpClient("127.0.0.1",9090);} }
此时DatagramPacket设置的长度,responsePacket.getBytes().length(),必须是字节的长度;
此处设置的response.length()这里求的是字符串的长度,是字符的个数
public class Server {private DatagramSocket socket;int port; 1)构建一个服务器public Server(int port) throws SocketException {this.socket = new DatagramSocket(port);} 2)启动服务器,循环接受请求 public void start() throws IOException { System.out.println("服务器将开始启动"); while (true) { //UDP无连接,读取请求,当前服务器不知道客户端啥时候发来请求,我们直接就进行接收数据即可 receive方法就会进行阻塞,如果真的有请求过来了receive方法就会进行返回 DatagramPacket requestpacket = new DatagramPacket(new byte[4096], 4096); //创建缓冲区,里面存放咱们客户端发送过来的请求的body数据 socket.receive(requestpacket); 3)根据请求来计算响应 把客户端传送过来的数据包进行解析成字符串,执行相关的逻辑 再将执行好的逻辑再次包装成一个数据包,返回给客户端String response=process(request);//这个方法是根据请求来计算响应 4)根据请求计算的结果来进行构建相应的数据包,后续会有DatagramSocket来进行发送 在进行构建数据包的时候,我们要在里面进行指定客户端的IP和端口号,直接使用requestpacket.getSocketAddress();从而让服务器知道客户端是谁,究竟send给谁 DatagramPacket responsePacket=new DatagramPacket(response.getBytes(), response.getBytes().length, requestpacket.getSocketAddress());//获取客户端的IP地址和端口号 socket.send(responsePacket); 5)下面具体的业务流程没啥关系了,只是打印客户端的主机信息 String return1=String.format("[%s:%d] request=%s,response=%s",requestpacket.getAddress(), requestpacket.getPort(),request,response);System.out.println(return1);}}private String process(String request){return request;}public static void main(String[]args)throws SocketException,IOException{Server server=new Server(990);server.start();} }
import java.net.*; import java.util.Scanner; public class Request {private DatagramSocket socket = null;int serverport;String serverIP;public Request(int serverport, String serverIP) throws SocketException { this.socket = new DatagramSocket(); this.serverport = serverport; this.serverIP = serverIP;}public void start() throws UnknownHostException, IOException {1在标准输入键盘中读到数据while (true) {System.out.println("请输入你的请求");Scanner scanner = new Scanner(System.in);System.out.println("->");String quest = scanner.nextLine();//String quest="abc"; 如果读入的字符串等于goodbey客户端退出if (quest.equals("goodbye")) {System.out.println("即将退出客户端");return; } 2把用户输入的字符串构造成一个UDP数据包,发送给我们的服务器 //DtagramgranPacket既要包含具体的数据,又要指明发送给谁 //System.out.println(quest.getBytes().length); DatagramPacket requestPacket = new DatagramPacket(quest.getBytes(), quest.getBytes().lengthInetAddress.getByName(serverIP), serverport); 3调用DatagranSocket中的send方法进行发送数据 socket.send(requestPacket); 4 尝试在服务器中读响应,咱们提前构造好一个缓冲区 DatagramPacket responsePacket = new DatagramPacket(new byte[4096],4096); socket.receive(responsePacket); System.out.println(responsePacket.getData()); String response = new String(responsePacket.getData(),0, responsePacket.getLength()); 5把服务传递过来的数据包解析成字符串并显示这个结果 String str = String.format("request:%s,response %s",quest, response); System.out.println(str); }} public static void main(String[] args) throws SocketException, UnknownHostException, IOException {Request request = new Request(990, "127.0.0.1");//127.0.0是本机 request.start();}}
1)DatagramPacket requestpacket = new DatagramPacket(new byte[4096], 4096); //我们需要提前构建好一个空的DatagramPacket对象,再来进行传递给receive方法 //最后由我们的DatagramSocket借助receive方法向这个缓冲区里面进行填充数据; 2)DatagramPacket responsePacket=newDatagramPacket(response.getBytes(), response.getBytes().length, requestpacket.getSocketAddress());---------约定好给谁发 //里面的参数要放响应字符串转化成字节数组,相应返回响应字符串转化成字节数组的长度,也就是说字节的个数 第三个参数指明这个包要发送给谁;socket之前并没有指定给谁发呀 之前只是绑定了自己的端口号,所以说光从socket这里面看,他只是接受了一个数据包 不知道这个数据包是从哪里来的,所有我们就要从DatagramPacket Response里面进行设置(从DatagramPacket request里面取)//还有在当前创建的时候,还要指定这个包发送给谁,其实发送的目标, 就是发送请求的人,那个第三个方法的返回值是端口号和IP地址,它是一个抽象类 3)DatagramPacket requestPacket = new DatagramPacket( quest.getBytes(), quest.getBytes().lengthInetAddress.getByName(serverIP), serverport);-------约定好给谁发 这是客户端向服务器发送数据的时候,DatagramPacket的构造方法
这里我们还是需要注意一下为什么写这个UDP的客户端和服务器的时候,服务器要自动指定端口呢?而不是让系统自动给咱们分配一个?但是写客户端的时候,咱们是让操作系统自动给咱们分配一个端口,而咱们的程序员不需要指定客户端端口了呢?
客户端在进行创建一个socket对象的时候,就不需要再去进行手动指定端口号了,意思就是说让我们的操作系统分配一个空闲的端口号,其实也是可以手动指定
1)对于服务器来说,必须要进行手动指定,因为我们后续的客户端要根据这个端口来进行访问到服务器,如果服务器的端口让系统随机分配,此时客户端就不知道服务器的端口是啥,就不能访问;
2)对于咱们的客户端来说,如果手动指定端口号也不是不可以,但是我们说如果让系统进行随机分配效果会更好,同一个机器上面的两个进程,是不可以绑定同一个端口的,客户端就是咱们用户普通的电脑,谁也不知道用户电脑上面都装了那些应用程序,天知道用户电脑上面已经被绑定了哪些端口,如果你自己进行手动指定一个端口,万一这个端口被别的程序占用怎么办这个时候咱们的程序是不是就不能正常的进行工作了?但是我们的服务器上面的应用程序是啥,绑定了哪些端口,都是我们指定好的,不会出现端口号占用的问题;
3)况且咱们的客户端时主动发起请求的那一方,客户端在进行发送请求之前,要知道服务器的IP地址加上端口号,但是反过来在请求发送出去之前,服务器是不需要首先知道客户端的地址加上端口号的,直到客户端把数据发送到服务器这边,服务器才知道客户端的IP地址和端口号,这时候处理完业务数据之后也知道发送给谁了;
1)写客户端服务器程序之前,我们是启动服务器之后,才会写客户端代码的,我们在写客户端代码的时候,显然是没有人访问服务器的,服务器其实本质上就卡死在receive里面,直到有客户端发送数据,才会进行返回;因为此时的客户端服务器程序是没有连接的,想进行发送,就发送就可以了;
2)咱们的一个服务器是可以给多个客户端提供请求的,一个服务器要进行处理请求的数量,是有限的,这是完全取决于服务器的能力;
同一时刻服务器能够进行处理的客户端的数目是存在于上限的,服务器来进行处理每一次请求,都需要消耗一定的硬件资源,包括但是不包含与,CPU,内存,磁盘,网络带宽;
同一时刻我们可以处理多少客户端的数目,是存在上限的,完全取决于
2.1)处理一个请求,需要消耗多少资源
2.2)机器一共有多少资源可以用看你的代码和机器配置
3)在JAVA里面是并不容易来进行计算消耗多少资源,因为就是说在咱们的JVM里面有很多辅助性的功能,也是需要消耗额外的资源,咱们的服务器处理每一次请求,都是要消耗额外的资源,虽然咱们不能精准的计算消耗了多少资源,但是我们可以通过性能测试的方式,多多启动客户端进行测试,看看能支持多少客户端;
这样就可以配置多个客户端了
1)典型的是在分布式系统中,两个节点之间的交互,这种情况下,一个服务器只能给一个客户端提供服务,专属的情况‘;
2)况且在进行将客户端进行启动的时候,重复启动客户端,向服务器进行发送数据,每一个客户端都被我们的服务器分配了不同的端口;
2.5)基于UDP的翻译服务器
翻译程序,写一个翻译功能,请求时输入一些简单的英文单词,响应是英文单词对应的翻译
2)在这里面的代码,客户端的逻辑是不会发生改变的,主要来进行处理服务器这方面的逻辑,主要是调整process方法,读取请求并解析,把响应写回到客户端都两步骤一样的,关键的逻辑就是说根据请求来进行计算响应;
public class UDPServer extends Response{HashMap<String,String> hashMap=new HashMap<>();public UDPServer(int serverport) throws SocketException {super(serverport);hashMap.put("dog","小狗");hashMap.put("cat","小猫");hashMap.put("fuck","卧槽");hashMap.put("pig","小猪");}public String process(String request){return hashMap.getOrDefault(request,"您查询的这个词在我们的词典中不存在");}public static void main(String[] args) throws IOException {UDPServer udpServer=new UDPServer(9090);udpServer.start();//调用父类的start()方法} }
在上面的代码当中,主要是使用了多态这样的机制,在前面的服务器里面,我们主要是基于process方法实现的,我们现在自己来写一个服务器来实习字典服务器;
1)当我们最开始写的那一个老版本的服务器,当调用到process方法的时候,会发生向上转型,会执行到我们UDPserver中的process方法;
2)在我们的main方法执行到start方法的时候,会执行到我们父亲类的start()方法,因为我们的子类中没有这个方法,就会自动地执行我们父类中所拥有的这个方法;
3)我们可以启动多个客户端,服务器来进行处理请求;
51877就是客户端给他分配的端口;
import java.io.IOException; import java.net.*; import java.util.Scanner;public class user{DatagramSocket socket=null;int serverport;String serverIP;public user(int serverport,String serverIP)throws SocketException {this.serverIP=serverIP;this.serverport=serverport;this.socket=new DatagramSocket();}public void start()throws UnknownHostException, IOException{System.out.println("客户端即将启动");while(true){System.out.println("请输入你的请求");Scanner scanner = new Scanner(System.in);System.out.println("->");String request=scanner.nextLine();if(request.equals("exit")){System.out.println("goodbye");return;}DatagramPacket requestpacket=new DatagramPacket(request.getBytes(),request.getBytes().length, InetAddress.getByName(serverIP),serverport);socket.send(requestpacket);DatagramPacket responsepacket=new DatagramPacket(new byte[4096],4096);socket.receive(responsepacket);String response=new String(responsepacket.getData(),0,responsepacket.getLength());String str=String.format("request=%s,response=%s",request,response);System.out.println(str);}}public static void main(String[] args)throws SocketException,IOException{user user1=new user(9090,"127.0.0.1");user1.start();} }
import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.SocketException; import java.util.HashMap;public class Server {int port;DatagramSocket socket=null;HashMap<String,String> map=new HashMap<>();public Server(int port)throws SocketException {this.port = port;this.socket =new DatagramSocket(port);map.put("及时雨","宋江");map.put("国民女神","高圆圆");map.put("大聪明","李嘉欣");map.put("王者大神","李佳伟");}public void start()throws IOException{System.out.println("服务器即将启动");while(true){DatagramPacket requestpacket=new DatagramPacket(new byte[4096],4096);socket.receive(requestpacket);String request=new String(requestpacket.getData(),0,requestpacket.getLength());String response=processon(request);DatagramPacket responsepacket=new DatagramPacket(response.getBytes(),response.getBytes().length,requestpacket.getSocketAddress());socket.send(responsepacket);String str=String.format("[%s:%d] request=%s;reponse=%s",requestpacket.getAddress(),requestpacket.getPort(),request,response);System.out.println(str);}}public String processon(String request){return map.getOrDefault(request,"你查询的词不存在");// return request;}public static void main(String[] args)throws SocketException,IOException{Server server=new Server(9090);server.start();} }