一、
传输层提供协议主要有两个:(两套有不同的API)
1.UDP:无连接,不可靠传输,面向数据报,全双工
2.TCP:有连接,可靠传输,面向字节流,全双工
有/无连接,如同JDBC(看我之前写的里面),先创建一个DataSource,再通过DataSource创建Connection->(TCP编程,也存在这种类似建立连接的过程。
客户端和服务器之间,使用“内存”保存对端信息~~,双方都保存这个信息,此时连接就存在,客户端可以连多个服务器,服务器也可以连接多个客户端。
链接(和连接是不一样的)——其实就是快捷方式,也叫符号链接
TCP可靠传输(效果也会因为可靠而降低):A尽可能的传递给B,并且假如传输失败的时候,A能感知到,成功的时候哦,自己也能知道,自己传递成功了。
UDP不可靠传输(效率偏高一点)
注意⚠️:可靠和安全是两回事,谈到的网络安全,你是否容易被黑客捕获,以及如果捕获后,会不会泄露一些重要信息。
面向字节流,面向数据报:这两种特性很影响代码编写。
TCP和文件操作类似(都是流式,这里传输单位是字节,成为字节流)通过TCP读写100字节数据,可一次读写100字节,当然也可以拆分,一次读10,读10次等等。
UDP的数据报,读写的基本单位,是一个UDP数据报(包含一系列数据属性)
全双工:一个通道,可以双向通信
半双工:一个通道,可单向通信
二、
UDP的socket. API
1.DatagramSocket:是一个Socket对象,操作系统,使用文件这样的概念,来管理一些软硬件资源,网卡,操作系统,也是用文件这样的方式管理网卡,表示网卡这类文件成为Socket文件。JAVA中的socket对象,就对应系统里的socket文件(最终管理到网卡),要进行网络通信,要先有socket对象
DatagramSocket():在客户端这边使用,客户端使用哪个端口,系统是自动分配的
DatagramSocket(int port):在服务器这边使用,服务器使用哪个端口是我们手动指定的。
2.DatagramPacket:
表示一个UDP数据报,代表系统中设定的UDP数据报的二进制结构
这就如同:
大学六厅,9号窗口西安里凉皮<->相当于服务器端口(固定好找),大学六厅就相当于IP地址,同学们坐的位置就相当于客户端端口号,你下次来是找空位,而不是上次的座位,但是9号窗口:客户端的端口号是要求固定的
一个客户端主机,上面运行的程序很多,不知道,你手动指定的端口是不是被别的程序占用 ,让系统自动分配一个端口是更明智的选择,服务器是完全在程序员手里控制的,程序员可以把服务器上多个程序安排好,让他们使用不同的端口。
DatagramSocket提供了三个方法。
void receive(DatagramPacket p)
send(DatagramPacket p):发送
close()关闭
DatagramPacket :表示一个UDP数据报,代表系统中设定的UDP数据报的二进制结构
DatagramPacket(byte[]buf,int length)->接受数组
DatagramPacket(byte[]buf,int offset,int length,SocketAddress address(目的端口和目的IP) )
DatagramPacket作为UDP数组报,必然要承载一些数据,通过手动指定的byte[]作为存储数据的空间。
三个方法(知道就好):
InetAddress getAdress():从接收的数据报中,获取发送端主机IP地址,或从发送的数据报中,获取接收端主机IP地址
int getPort():从接收数据报中,获取发送端主机的端口号,或从发送的数据报中,获取接受端的主机端口号。
byte []getData:获取数据报中的数据
开始写UDP客户端服务器——新的流程
最简单的UDP服务器,回显服务器(客户端发啥,服务器就返回什么东西),编写网络程序,经常会见到SocketException这个异常
最典型的情况,端口号被占用,端口号用来区分主机上的应用程序,一个应用程序可以占据主机上多个端口,一个端口只能被一个进程占用(这话其实也不太严谨,但此处不做讨论)端口已经被别的进程占用了,此时你再创建这个socket对象,占用该端口,就会报错。
一个服务器要给很多用户的客户端提供服务,服务器也不知道客户端什么时候来,服务器只能时刻准备着,随时客户端来了,随时提供服务。
写服务器三个步骤
1.读取请求,并解析
2.根据请求,计算出响应:
回显服务器,并不关注这个,但是正经的服务器,主要代码都是在完成这个模块,(处理一个请求,可能会经历几万行的代码,几十万行代码构建出来的逻辑)
3.把响应写回客户端
介绍方法
1.socket.receive():这方法,参数DatagramPacket是一个“输出型参数”,传入receive是一个空的对象,receive内部就会把这个内容填充上,当receive执行结束,于是就得到了一个装满内容的DatagramPacket.(幻想,你把餐盘(DatagramPacket)给食堂大姨,食堂大姨给你添饭(receive),小伙盘子给你,你拿回去
2.DatagramPacket requestPacket=new DatagramPacket(new byte[4096],4096)
这个4096,你随便写,无所谓,但最好别太大,也别太小,这个对象用来保存数据的内存空间,是需要手动指定的,不像我们之前的集合类,能自动申请,释放,扩容内存
3.utf8中,一个汉字,三个字节,我们的String s ——s.length——s.getBytes().length,一个是按照字节存储的,一个是按照字符存储,英文还好,但是假如说是中文,中文的话两个字,是两个字符还是6个字节就有区别了。
4.
DatagramPacket responsePacket=new Datagrampacket(response.getBytes(),response.getBytes().length,requestPacket.getSocketAddress());
response.getBytes():接收的字符串数组
response.getBytes().length:字节长度
requestPacket.getSocketAddress():发给谁,目的端口,IP,要构造一个DatagramPacket对象,把响应发给客户端
三、
回显代码的服务器具体实现
import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.SocketException; public class UdpEchServer { private DatagramSocket socket = null; //服务器需要绑定的窗口 public UdpEchServer(int port) throws SocketException { socket = new DatagramSocket(port); } public void start() throws IOException { System.out.println("服务器启动"); while (true) { //申请一个4096的字符串数组取装你的语句回显。 DatagramPacket requestPacker = new DatagramPacket(new byte[4096], 4096); //接收这个字符串(在客户端来临之前,他会一直停止在这个地方等待) socket.receive(requestPacker); //这句话应该是相当于把你哪个语句内容,放到一个字符串里面。 String request = new String(requestPacker.getData(), 0, requestPacker.getLength()); //2.根据请求,计算出响应(由于这个是回显,所以直接返回你的输入值就行,也就是上面的字符串) String response = process(request); //3.把响应写回客户端,此时需要告知网卡,要发的内容是什么,发给谁。 DatagramPacket responsePacket = new DatagramPacket(response.getBytes(), response.getBytes().length, requestPacker.getSocketAddress()); //4.把这个发的内容,重新返回给客户端 socket.send(requestPacker); //返回之后给他打印一样,C语言的printf System.out.printf("[%s:%d] rep:%s,resp:%s\n", requestPacker.getAddress().toString(), requestPacker.getPort(), request, response); } } public String process(String request) { return request; } public static void main(String[] args) throws IOException { UdpEchServer server = new UdpEchServer(9090); server.start(); } }
四、❤️
回显服务器客户端的编写
import java.io.IOException; import java.net.*; import java.util.Scanner; public class UdpEchoClient { //第一个是用来装客户端发的语句并且传输给服务器的。 private DatagramSocket socket=null; //下面两个是本机的IP和连接服务器的端口 private String serverIP; private int serverPort; public UdpEchoClient(String ip,int port) throws SocketException { serverIP = ip; serverPort = port; socket=new DatagramSocket(); } //让这个客户端反复从控制台读取用户输入的内容,把这个过程构造成UDP的请求,发给服务器,再读取服务器返回的UDP响应 //最终显示在客户端屏幕上 public void start() throws IOException { Scanner scanner=new Scanner(System.in); System.out.println("客户端启动"); while(true){ //1.从控制台读取用户制定的内容 System.out.println("->"); //输入你要输入的语句。 String request= scanner.next(); //2.构造请求对象,并且把你的语句打包,并且发给服务器 //request.getBytes():字符串的字节数组 //request.getBytes().length:字节数组长度 //InetAddress.getByName(serverIP):本机的IP,此时需要的是InetAddress对象,使用inetAddress的静态方法,getByName来进行构造(工厂模式,工厂方法) //serverPort:服务器的端口号 DatagramPacket requestPacket=new DatagramPacket(request.getBytes(),request.getBytes().length, InetAddress.getByName(serverIP),serverPort); //把这个打包好的发送给服务器 socket.send(requestPacket); //3.读取服务器的响应,并解析出响应内容 DatagramPacket responsePacket=new DatagramPacket(new byte[4096],4096); //接收到服务器返回的语句,,但是他返回的值是返回到上面那个我们打包的那个东西里面了 socket.receive(responsePacket); String response=new String(responsePacket.getData(),0,responsePacket.getLength()); //4.显示到屏幕上 System.out.println(response); } } public static void main(String[] args) throws IOException { //127.0.0.1是用来访问本机地址的 UdpEchoClient client=new UdpEchoClient("127.0.0.1",9090); client.start(); } }
五、
我们如何开启多个客户端呢?
1.点击我点击的地方
2.点这个more的这个东西
3.勾选第一个选项(第二个原来就有),然后退出点击OK保存就可以。
最终的呈现结果
总结:这个勾八内容挺复杂,自己去写一写,感受一下。