第十一天网络编程网络通信协议 网络通信协议: 通信协议是计算机必须遵守的规则, 只有遵守这些规则, 计算机之间才能进行通信. 协议中对数据的传输格式, 传输速率, 传输步骤等做了统一规定, 通信双方必须同时遵守, 最终完成数据交换 (类似于一种语言, 语法语速做了规定) TCP/IP协议: Transmission Control Protocol/Internet Protocol, 传输控制协议/因特网互联协议. 它定义了计算机如何连入因特网, 以及数据如何在它们之间传输的标准. 它的内部包含一系列的用于处理数据通 信的协议, 并采用了4层的分层模型, 每一层都呼叫它的下一层所提供的协议来完成自己的需求 网络通信协议分类UDP: User Datagram Protocol, 用户数据报协议 特点: 无连接的不可靠协议 数据按包发送, 64K一个包 速度快效率高, 容易丢包 用于视频直播, 网络电话
TCP: Transmission Control Protocol, 传输控制协议 特点: 1.需要建立连接的可靠协议 电话 2.数据传输无大小限制 3.速度慢效率低 重发机制 用于文件下载, 浏览网页 TCP通信的三次握手: TCP协议中, 在发送数据的准备阶段, 客户端与服务器之间的三次交互, 以保证连接的可 靠 1.客户端向服务端发送验证信息, 等待服务器确认 2.服务端收到验证信息后, 回复客户端验证信息, 同时发送自己的一条验证信息 3.客户端收到服务端回复的信息, 确认自己之前发的信息无误, 并再次向服务器发回服务端的验证信息 网络编程三要素:IP地址网络编程三要素: 1.通信协议 TCP 2.IP地址 3.端口号 IP地址: 互联网协议地址(Internet Protocol Address). 是网络中计算机的唯一标识 版本: IPv4: 192.168.1.2 IPv6: ABCD:EF01:2345:6789:ABCD:EF01:2345:6789 特殊的IP地址: "127.0.0.1", "localhost", 都代表自己的电脑 常用DOS命令: // 查看自己电脑的IP地址 ipconfig // 查看是否能连接到指定IP地址 ping IP地址 ping 192.168.31.2 网络三要素:端口号 端口号: 计算机中进程的唯一标识 端口号的取值范围: 0~65535的整数, 其中0~1024不建议使用 注意: 通信的两端是2个计算机中的2个程序在相互通信, 所以2个程序都要有端口号. 端口号可以相同, 也可以不同, 相互之间能识别就行 Tcp通信Tcp通信程序概述(上) java.net.ServerSocket类: TCP服务端 // 构造方法 ServerSocket(int port): 创建一个TCP服务端, 并监听指定端口 // 成员方法 Socket accept(): 监听数据, 会阻塞. 收到数据后返回Socket对象 void close(): 关闭服务端ServerSocket java.net.Socket类: TCP客户端 // 构造方法 Socket(String ip, int port): 创建TCP客户端对象 // 成员方法 OutputStream getOutputStream(): 获取输出流对象, 用于发送数据 InputStream getInputStream(): 获取输入流, 用于接收数void shutdownOutput(): 关闭输出流, 发送结束标记 void close(): 关闭客户端Socket TCP严格区分为 客户端(Client) 与 服务端(Server) 两端通信时步骤: 两端之间以 "IO字节流" 进行通信 一个服务端可以和多个客户端同时通信 TCP通信方式概述(下) 服务端(ServerSocket)可以通过 accept() 方法等待一个客户端(Socket)主动连接, 从而得到一个客户端对象 (Socket), 来识别不同的客户端 服务端(ServerSocket)没有IO流, 是通过获取到"每个客户端对象(Socket)的IO流对象"来进行通信的. 使用"客户端的InputStream"读取客户端发来的数据 使用"客户端的OutputStream"向客户端回写数据 文件上传文件上传堵塞问题 java.net.Socket类: TCP客户端 void shutdownOutput(): 关闭输出流, 告知服务端数据发送完毕 文件上传优化 目前服务端有以下问题: 上传的图片因为写入文件名相同, 每次都会被覆盖, 如何解决? 上传一个文件服务端就结束了, 如何让服务端不停止, 一直接收文件上传 在同一个线程读大文件可能会比较慢, 能否利用多线程提高程序效率
解决方案: 上传文件的命名规则: 域名+毫秒值+随机数 将serverSocket.accept()直到最后的操作放入死循环中, 服务器就可以一直接收文件上传 当serverSocket.accept()得到Socket客户端对象后的操作, 全都放在子线程中执行
11天APIjava.net.ServerSocket类: TCP服务端 // 构造方法 ServerSocket(int port): 创建一个TCP服务端, 并监听指定端口 // 成员方法 Socket accept(): 监听数据, 会阻塞. 收到数据后返回Socket对象 void close(): 关闭服务端ServerSocket java.net.Socket类: TCP客户端 // 构造方法 Socket(String ip, int port): 创建TCP客户端对象 // 成员方法 OutputStream getOutputStream(): 获取输出流对象, 用于发送数据 InputStream getInputStream(): 获取输入流, 用于接收数据 void shutdownOutput(): 关闭输出流, 告知服务端数据发送完毕 void close(): 关闭客户端Socket TCP协议下的常用类名称客户端: Socket 服务端: ServerSocket 编写TCP协议下字符串数据传输程序你好服务器 收到谢谢 客户端发送字符串的byte 服务端接收客户端的byte 服务端回写客户端内容 客户端再接收服务端的响应 Tcp协议下文件上传客户端 读取本地图片文件 发送到服务端 读取服务端的响应结果 服务端 接收客户端的文件数据 写入到服务端磁盘 发送响应给客户端 软件结构C/S: Client Server 客户端 服务端 B/S: Browser Server 浏览器 服务端 网络通信三要素通信协议: 怎么传输 IP地址: 传给哪个主机 端口号: 传给主机的哪个进
第十二天函数式接口函数式接口格式是要确保接口中有且仅有一个抽象方法即可: 修饰符 interface 接口名称 { public abstract 返回值类型 方法名称(可选参数信息); // 其他非抽象方法内容 } 由于接口当中抽象方法的public abstract是可以省略的,所以定义一个函数式接口很简单: public interface MyFunctionalInterface { void myMethod(); } @FunctionalInterface注解与@Override注解的作用类似,Java 8中专门为函数式接口引入了一个新的注解:@FunctionalInterface`。该注解可用于一个接口的定义上: @FunctionalInterface public interface MyFunctionalInterface { void myMethod(); } 一旦使用该注解来定义接口,编译器将会强制检查该接口是否确实有且仅有一个抽象方法,否则将会报错。需要注意的是,即使不使用该注解,只要满足函数式接口的定义,这仍然是一个函数式接口,使用起来都一样。 自定义函数式接口对于刚刚定义好的MyFunctionalInterface函数式接口,典型使用场景就是作为方法的参数: public class Demo09FunctionalInterface { // 使用自定义的函数式接口作为方法参数 private static void doSomething(MyFunctionalInterface inter) { inter.myMethod(); // 调用自定义的函数式接口方法 } public static void main(String[] args) { // 调用使用函数式接口的方法 doSomething(() -> System.out.println("Lambda执行啦!")); }} 函数式编程Lambda写法:使用Lambda必然需要一个函数式接口 @FunctionalInterfacepublic interface MessageBuilder { String buildMessage();} 使用Lambda作为参数和返回值如果抛开实现原理不说,Java中的Lambda表达式可以被当作是匿名内部类的替代品。如果方法的参数是一个函数式接口类型,那么就可以使用Lambda表达式进行替代。使用Lambda表达式作为方法参数,其实就是使用函数式接口作为方法参数。 例如java.lang.Runnable接口就是一个函数式接口,假设有一个startThread方法使用该接口作为参数,那么就可以使用Lambda进行传参。这种情况其实和Thread类的构造方法参数为Runnable`没有本质区别。 public class Demo04Runnable { private static void startThread(Runnable task) { new Thread(task).start(); } public static void main(String[] args) { startThread(() -> System.out.println("线程任务执行!")); } } 类似地,如果一个方法的返回值类型是一个函数式接口,那么就可以直接返回一个Lambda表达式。当需要通过一个方法来获取一个java.util.Comparator`接口类型的对象作为排序器时,就可以调该方法获取。 常用函数式接口Supplier接口java.util.function.Supplier<T>接口仅包含一个无参的方法:T get()。用来获取一个泛型参数指定类型的对象数据。由于这是一个函数式接口,这也就意味着对应的Lambda表达式需要“对外提供”一个符合泛型类型的对象数据 Consumerjava.util.function.Consumer<T>`接口则正好与Supplier接口相反,它不是生产一个数据,而是消费一个数据,其数据类型由泛型决定。 抽象方法:acceptConsumer接口中包含抽象方法void accept(T t)`,意为消费一个指定泛型的数据。 默认方法:andThen如果一个方法的参数和返回值全都是Consumer类型,那么就可以实现效果:消费数据的时候,首先做一个操作,然后再做一个操作,实现组合。而这个方法就是Consumer接口中的default方法andThen. Predicate接口有时候我们需要对某种类型的数据进行判断,从而得到一个boolean值结果。这时可以使用java.util.function.Predicate<T>接口 抽象方法:testPredicate接口中包含一个抽象方法:boolean test(T t) 默认方法:and既然是条件判断,就会存在与、或、非三种常见的逻辑关系。其中将两个Predicate条件使用“与”逻辑连接起来实现“并且”的效果时,可以使用default方法and` 默认方法:or与and的“与”类似,默认方法or实现逻辑关系中的“或”. 默认方法:negate“与”、“或”已经了解了,剩下的“非”(取反)也会简单。默认方法negate的JDK源代码为: default Predicate<T> negate() { return (t) -> !test(t);} Function接口java.util.function.Function<T,R>接口用来根据一个类型的数据得到另一个类型的数据,前者称为前置条件,后者称为后置条件。 默认方法:andThenFunction接口中有一个默认的andThen方法,用来进行组合操作。JDK源代码如: default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) { Objects.requireNonNull(after); return (T t) -> after.apply(apply(t));} 第十三天Stream流Stream流式思想便利集合进行过滤Stream流式思想: JDK 8 出现, 是函数式编程中的一大特性 关注做什么, 而不是怎么做 public class Demo02Stream { public static void main(String[] args) { //创建一个List集合,存储姓名 List<String> list = new ArrayList<>(); list.add("张无忌"); list.add("周芷若"); list.add("赵敏"); list.add("张强"); list.add("张三丰"); // 使用Stream流式操作 list.stream() .filter(name -> name.startsWith("张")) // 第一次过滤: 只要以张开头的元素 .filter(name -> name.length()==3) // 第二次过滤: 只要姓名长度为3的人 .forEach(name -> System.out.println(name)); // 遍历查看结果 } } 流式思想概述Stream流式思想处理数据的方式: 让代码的执行像流水线一样, 先设计好处理方案, 然后一按开关开始执行 流相比于集合的2个优点: 1.Pipelining(管道特性): "可以链式调用" Stream流对象的 延迟方法 调用后, 会返回新的Stream流对象, 可以链式调用 每个方法类似于一条一条的管道, 衔接了不同的处理方案 2.内部迭代特性: "不用写for循环" 集合遍历通过 Iterator 或者 增强for, 显式的在集合外部进行迭代, 这叫做外部迭代 Stream提供了内部迭代的方法 forEach(Consumer c), 可以直接调用遍历方法 使用Stream流的3个步骤: 两种获取Stream流的方式获取Stream流对象的2种方式: 1.利用"Collection接口"中的默认方法 default Stream<E> stream() 方法: 集合转Stream对象 2.利用"Stream接口"中的静态方法 static <T> Stream<T> of(T... values): 数组转Stream对象 java.util.Collection<E>接口: // 默认方法 default Stream<E> stream(): 将"集合"转换为Stream对象 java.util.stream.Stream<T>接口: 管道接口, 泛型为流中元素的类型 // 静态方法 static<T> Stream<T> of(T... values): 将"数组"转换为Stream对象 Stream API: 方法分类, forEach()遍历延迟方法: (具有延迟执行的特性) 返回值类型"是Stream"类型的方法, 支持链式调用 Stream filter(): 过滤 startsWith 判断当前字符串是否以参数字符串开头 filter 过滤符合条件的结果. 返回过滤后的流 Stream<String> stream2 = stream1.filter((String name) -> { return name.startsWith("张"); // 如果以张开头, 结果为true, 返回true正好会保留到 集合中 }); Stream map(): 映射/转换 map 将当前流中的T类型的元素, 转换R类型元素, 放入新流 并返回 Stream<Integer> stream2 = stream1.map((String num) -> { // 元素从String 转换为 Integer. 返回流的泛型也要变化 return Integer.parseInt(num); }); limit 从前面选输入几就有几个元素 Stream.of("美羊羊", "喜羊羊", "懒羊羊", "灰太狼", "红太狼") .limit(3) // 只要前3个 Stream limit(): 截取 skip 从前面选输入几个就跳过几个元素 Stream skip(): 跳过 Stream.of("美羊羊", "喜羊羊", "懒羊羊", "灰太狼", "红太狼") // .skip(3) // 不要前3个 终结方法: 返回值类型"不是Stream"类型的方法, 不支持链式调用 void forEach(): 遍历 long count(): 统计 注意: 除了终结方法外, 其余方法均为延迟方法 方法引用 方法引用: Method Reference 如果Lambda表达式仅仅是调用一个已经存在的方法, 那就可以通过方法引用来替代Lambda表达式 作用: 简化Lambda表达式 :: 方法引用运算符, 它所在的表达式被称为方法引用 Lambda表达式写法: (String s) -> System.out.println(s) 参数传递给System.out.println()方法去打印 方法引用写法: System.out::println 引用System.out.println()方法中代码, 来作为Lambda中重写方法的实现方式 注意: Lambda中, 重写方法的"参数", 必须是方法引用的方法"要接收的类型", 否则会抛出异常 (String s) -> System.out.println(s) 方法有个参数String s System.out::println 引用的println方法必须能接收String类型的s 方法引用能简化以下场景: (方法名后不要写小括号) 场景 格式 简化之前的Lambda 方法引用简化后 通过对象名引用成员方法 对象名::成员方法名 ()->person.eat() person::eat 通过类名引用静态方法 类名::静态方法名 i -> Math.abs(i) Math::abs 通过super引用父类成员方法 super::父类方法名 ()->super.eat(); super::eat 通过this引用本类成员方法 this::本类方法名 ()->this.eat(); this::eat 引用某个类的构造方法 类名::new name->new Person(name) Person::new 引用创建数组的方法 数据类型[]::new length->new int[length]; int[]::new
|