目录
二、有链接/无连接(是抽象的概念,是虚拟的/逻辑上的连接),例如要进行网络通信,物理上的连接(网线啥的)
[三、可靠传输 vs 不可靠传输](#三、可靠传输 vs 不可靠传输)
[四、面向字节流 vs 面向数据报](#四、面向字节流 vs 面向数据报)
[五、全双公 vs 半双工](#五、全双公 vs 半双工)
应用层
操作系统提供的一组 API => socket API (传输层给应用层提供);socket 这个词的意思是插槽的意思,对应在主板上可以理解为主板上的一些接口。
一、传输层的两个核心协议
TCP协议 和 UDP协议这两个差别非常大。
- TCP 是有连接,可靠传输,面向字节流,全双工
- UDP 是无连接,不可靠传输,面向数据报,全双工
二、有链接/无连接(是抽象的概念,是虚拟的/逻辑上的连接),例如要进行网络通信,物理上的连接(网线啥的)
- 为什么TCP 是可靠的传输(有链接),而 UDP 是不可靠传输(无连接)。
对于 TCP 来说,TCP 协议中,就会保存对端的信息,例如 A和B通信,A和B先建立连接,让A保存B的信息,B保存A的信息(彼此之间知道,谁是和它连接的哪个)。
对于UDP来说,UDP协议本身,不保存对方的信息。就是无连接 。
三、可靠传输 vs 不可靠传输
网络上数据是非常容易出现丢失的情况(丢包) ,因为光信号/电信号 ,都可能受到外界的干扰。本来是传输0101,但是因为数据的丢包,其中有些bit位就被修改了,这样乱了的数据就会被识别出来,把这样的数据极可能传输不到原来的地方。
而且网络世界是通过路由器/交换机 来进行联络的,路由器/交换机 类似于"十字路口 ",它也会发生我们生活中十字路口堵车的情况,(在某个时间点,实际需要转发的数据超过了设备能转发的上线),就会容易发生数据丢包问题。
- 可靠传输 的意思是,不能保证数据包100% 的到达,而是尽可能的提高传输成功的概率 。如果出现了丢包,能够感知到。
- 不可靠传输,只是把数据发出去,他到底是到了还是没到,他就不管了。
四、面向字节流 vs 面向数据报
面向字节流,读写数据的时候,是以字节为单位的
面向数据报,读写数据的时候,是以一个数据报为单位(不是字符为单位),一次必须读写一个 UDP 数据报,不能是半个。
五、全双公 vs 半双工
全双工:一个通信链路,支持 双向通信 (能读,也能写)
半双工:一个通信链路,只支持单向通信(只能读,或者只能写)
他们俩就像我们的一条公路,公路上的黄色线,就是车道的分割线。全双工就像是双向公路,这条路可以从A到B,也可以从B到A。半双工就像是单向公路,只能从A到B,或者是从B到A。
接下来就可以使用socket api 进行网络编程了
在计算机中"文件 "通常是一个"广义的概念 ",文件还能代指一些硬件设备(操作系统管理硬件设备,也是抽象成文件,统一管理的)。 就像 网卡 抽象成 socket 文件,操作网卡的时候,流程和操作普通文件差不多, 打开 -> 读写 -> 关闭。
操作网卡,直接不好操作,把操作网卡当成操作 socket 文件,就容易操作了。socket文件就相当于"网卡的遥控器"。
六、回升服务器(重点)
下面我们写一个回声服务器。回声服务器是一个客户端输入什么,服务器端什么任务都不做,直接返回客户端输入的内容
先说一下大致流程:
创建一个接口 ,这个接口的端口必须是固定 的,为的就是防止这个服务器今天在这个端口,明天在另外一个端口,端口非常随机,我们很难去访问这个流动的服务器,所以我们把端口给固定之后,端口就不会跑了。这是类的创建和构造函数。Datagram 是数据报,Socket 是接口,所以 DatagramSocket 是数据报的接口,创建一个数据报的接口。
javapublic class UdpEchoServer01 { private DatagramSocket socket = null; public UdpEchoServer01(int port) throws SocketException { // 指定一个固定端口号,给服务器使用 socket = new DatagramSocket(port); } }创建好接口之后,因为这个接口是给服务器服务的,所以我们需要不断的接收客户端的请求。
javapublic void start() throws IOException { System.out.println("服务器启动"); while(true){ } }在不断请求中,我们需要去获取客户端的请求,所以我们需要创建一个数据报,来接收请求的内容
javapublic void start() throws IOException { System.out.println("服务器启动"); while(true){ DatagramPacket requestPacket = new DatagramPacket(new byte[4096], 4096); } }上面我们创建好这个数据报之后,去调用接口来给这个数据报读取客户端发来的请求 。就能知道客户端要做什么了,如果客户端没有请求的发送,这里也会发生阻塞等待 。
我们需要一个 byte 数据来保存请求的数据,所以创建这个请求数据报的时候需要传入一个byte 数组来保存请求。
javapublic void start() throws IOException { System.out.println("服务器启动"); while(true){ DatagramPacket requestPacket = new DatagramPacket(new byte[4096], 4096); //////////// 使用接口给 requestPacket 这个数据报中传入客户端的请求 socket.receive(requestPacket); } }经过 requestPacket 这个请求数据报接收客户端发送的请求之后,这个数据报内就保存好了客户端发送的数据报的内容 ,我们把其中的客户端发送的请求给解析出来放到一个String中。
因为是构造String ,所以我们获取到data 之后,需要从0开始读取 ,然后构造这个数据报的长度的大小。
javapublic void start() throws IOException { System.out.println("服务器启动"); while(true){ DatagramPacket requestPacket = new DatagramPacket(new byte[4096], 4096); socket.receive(requestPacket); //////////////////////// 获取 requestPacket 中的 data String request = new String(requestPacket.getData(), 0, requestPacket.getLength()); ////////////// 这里无法使用,如果传入 getData().length 每次构造的长度都会是4096,会造成请求混乱 // String request = new String(requestPacket.getData(), 0, requestPacket.getData().length); } }我们有了这个请求之后,构造一个处理函数,处理一下这个请求, 模拟一下真实服务器的运行。因为这里我们写的是回声服务器,你也可以直接使用这个请求字符串来构造响应数据报。这里封装成一个函数来处理这个请求。
javapublic 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()); //// 使用 response 接收响应 String response = process(request); } } ///// 服务器处理请求返回响应的函数 public String process(String request){ return request; }拿到响应之后我们就可以构造数据报返回给客户端的接口, 这时候我们需要知道客户端的IP和端口 ,这两个内容都在requestPacket接收客户端的请求的时候就保存起来了, 这时候我们可以构造一个响应数据报 ,直接调用 requestPacket 里面的getAddress() 获取 IP,getPort() 获取端口。或者使用 getSocketAdderss() 直接获取客户端的 IP和端口。
javapublic 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 = process(request); ///////////////// 直接使用 getSocketAdderss() DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),response.getBytes().length, requestPacket.getSocketAddress()); /////////////// 使用 getAddress() 和 gerPort() 分别获取 IP 和 端口号 // DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),response.getBytes().length, // requestPacket.getAddress(), requestPacket.getPort()); } } public String process(String request){ return request; }最后可以把响应数据报返回给客户端,之后就是客户端来接受这个响应了。
还可以打印一下服务器端日志,requestPacket 里面保存了客户端的 IP 和 端口号 。
IP:端口号 req: 请求 resp: 响应
哪个 IP 的 机器,哪个端口的程序 访问了服务器,请求是什么,服务器给的响应是什么
javapublic 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 = process(request); DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),response.getBytes().length, requestPacket.getSocketAddress()); ////////// 把响应数据报返回给客户端 socket.send(responsePacket); ///////////// 打印一下服务器日志 System.out.printf("[%s:%d] req: %s resp: %s\n", requestPacket.getAddress(), requestPacket.getPort(), request, response); } } public String process(String request){ return request; }