Java进阶 - 网络编程Socket函数式接口常用的函数式接口
Posted 五号世界
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java进阶 - 网络编程Socket函数式接口常用的函数式接口相关的知识,希望对你有一定的参考价值。
1.网络通信协议
网络通信协议:通信协议是对计算机必须遵守的规则,只有遵守这些规则,计算机之间才能进行通信。这就好比在道路中行驶的汽车一定要遵守交通规则一样,协议中对数据的传输格式、传输速率、传输步骤等做了统一规定,通信双方必须同时遵守,最终完成数据交换。
TCP/IP协议: 传输控制协议/因特网互联协议( Transmission Control Protocol/Internet Protocol),是Internet最基本、最广泛的协议。它定义了计算机如何连入因特网,以及数据如何在它们之间传输的标准。它的内部包含一系列的用于处理数据通信的协议,并采用了4层的分层模型,每一层都呼叫它的下一层所提供的协议来完成自己的需求。
2.协议分类
包中提供了两种常见的网络协议的支持:
TCP:传输控制协议 (Transmission Control Protocol)。TCP协议是面向连接的通信协议,即传输数据之前,在发送端和接收端建立逻辑连接,然后再传输数据,它提供了两台计算机之间可靠无差错的数据传输。
三次握手:TCP协议中,在发送数据的准备阶段,客户端与服务器之间的三次交互,以保证连接的可靠。
第一次握手,客户端向服务器端发出连接请求,等待服务器确认。
第二次握手,服务器端向客户端回送一个响应,通知客户端收到了连接请求。
第三次握手,客户端再次向服务器端发送确认信息,确认连接。整个交互过程如下图所示。
完成三次握手,连接建立后,客户端和服务器就可以开始进行数据传输了。由于这种面向连接的特性,TCP协议可以保证传输数据的安全,所以应用十分广泛,例如下载文件、浏览网页等。
UDP:用户数据报协议(User Datagram Protocol)。UDP协议是一个面向无连接的协议。传输数据时,不需要建立连接,不管对方端服务是否启动,直接将数据、数据源和目的地都封装在数据包中,直接发送。每个数据包的大小限制在64k以内。它是不可靠协议,因为无连接,所以传输速度快,但是容易丢失数据。日常应用中,例如视频会议、QQ聊天等。
3.网络编程三要素
协议
协议:计算机网络通信必须遵守的规则,已经介绍过了,不再赘述。
IP地址
IP地址:指互联网协议地址(Internet Protocol Address),俗称IP。IP地址用来给一个网络中的计算机设备做唯一的编号。假如我们把“个人电脑”比作“一台电话”的话,那么“IP地址”就相当于“电话号码”
IP地址分类
IPv4:是一个32位的二进制数,通常被分为4个字节,表示成 a.b.c.d 的形式,例如 192.168.65.100 。其中a、b、c、d都是0~255之间的十进制整数,那么最多可以表示42亿个。
IPv6:由于互联网的蓬勃发展,IP地址的需求量愈来愈大,但是网络地址资源有限,使得IP的分配越发紧张。
为了扩大地址空间,拟通过IPv6重新定义地址空间,采用128位地址长度,每16个字节一组,分成8组十六进制数,表示成 ABCD:EF01:2345:6789:ABCD:EF01:2345:6789 ,号称可以为全世界的每一粒沙子编上一个网址,这样就解决了网络地址资源数量不够的问题。
查看本机IP地址,在控制台输入:
ipconfig
检查网络是否连通,在控制台输入:
ping 空格 IP地址
ping 220.181.57.216
特殊的IP地址
本机IP地址: 127.0.0.1 、 localhost 。
端口号
网络的通信,本质上是两个进程(应用程序)的通信。每台计算机都有很多的进程,那么在网络通信时,如何区分这些进程呢?
如果说IP地址可以唯一标识网络中的设备,那么端口号就可以唯一标识设备中的进程(应用程序)了。
端口号:用两个字节表示的整数,它的取值范围是0~65535。其中,0~1023之间的端口号用于一些知名的网络服务和应用,普通的应用程序需要使用1024以上的端口号。如果端口号被另外一个服务或应用所占用,会导致当前程序启动失败。
常用端口号
(1)80端口 网络端口 网址后面默认带有
(2)数据库 mysql:3306 oracle:1521
(3)Tomcat服务器:8080
利用 协议 + IP地址 + 端口号 三元组合,就可以标识网络中的进程了,那么进程间的通信就可以利用这个标识与其它进程进行交互。
4.Socket通信
成员方法
(1)public InputStream getInputStream() : 返回此套接字的输入流。
如果此Scoket具有相关联的通道,则生成的InputStream 的所有操作也关联该通道。关闭生成的InputStream也将关闭相关的Socket。
(2)public OutputStream getOutputStream() : 返回此套接字的输出流。
如果此Scoket具有相关联的通道,则生成的OutputStream 的所有操作也关联该通道。关闭生成的OutputStream也将关闭相关的Socket。
(3)public void close() :关闭此套接字。
一旦一个socket被关闭,它不可再使用。关闭此socket也将关闭相关的InputStream和OutputStream 。
(4)public void shutdownOutput() : 禁用此套接字的输出流。任何先前写出的数据将被发送,随后终止输出流。
基本通信实现:
服务器端: public class TcpServer { public static void main(String[] args) throws IOException { //向系统要一个指定的端口 ServerSocket server = new ServerSocket(8888); System.out.println("开始等待客户连接........"); //获取发送请求的socket对象 Socket s1 = server.accept(); //获取网络字节输入流 InputStream inputStream = s1.getInputStream(); //使用该流读取数据 byte[] bytes = new byte[1024]; int len = inputStream.read(bytes); System.out.println(new String(bytes,0,len)); //获取网络字节输出流 OutputStream outputStream = s1.getOutputStream(); outputStream.write("收到了,谢谢".getBytes()); s1.close(); server.close(); } } 客户端: public class TcpClient { public static void main(String[] args) throws IOException { Socket socket = new Socket("127.0.0.1", 8888); //获取网络字节输出流 OutputStream outputStream = socket.getOutputStream(); //使用该流向服务器发送数据 outputStream.write("你好,服务器".getBytes()); //获取一个网络字节输入流读取服务器发送的数据 InputStream inputStream = socket.getInputStream(); byte[] bytes = new byte[1024]; int len = inputStream.read(bytes); System.out.println(new String(bytes,0,len)); //关闭socket socket.close(); } }
文件上传下载案例:
服务端:
public class TcpServer { public static void main(String[] args) throws IOException { //1.获取一个绑定好指定端口的服务器socket对象 ServerSocket server = new ServerSocket(8888); while (true) { //2.用accept获取到请求的socket客户端 Socket client01 = server.accept(); /* 使用多线程技术,提高程序的效率 有一个客户端上传文件,就开启一个线程,完成文件的上传 */ new Thread(new Runnable() { //完成文件的上传 @Override public void run() { try { //3.获取网络字节输入流对象 InputStream is = client01.getInputStream(); //4.判断文件夹是否存在,不存在就创建一个 File file = new File("D:\\\\CreatNew"); if (!file.exists()) { file.mkdirs(); } /* 自定义一个文件命名规则,防止覆盖 规则:域名+毫秒值+随机数 */ String filename = "itcast" + System.currentTimeMillis() + new Random().nextInt(99999) + ".jpg"; //5.创建一个本地字节输出流 FileOutputStream fos = new FileOutputStream(file + "\\\\" + filename); //6.使用网络字节输入流读取数据 byte[] bytes = new byte[1024]; int len = 0; while ((len = is.read(bytes)) != -1) { //7.通过本地流写入硬盘 fos.write(bytes, 0, len); } System.out.println("2222222222222"); //8.给客户回数据 OutputStream os = client01.getOutputStream(); os.write("上传成功".getBytes()); //9.释放资源 fos.close(); client01.close(); } catch (IOException e) { System.out.println(e); } } }).start(); } } }
客户端:
public class TcpClient { public static void main(String[] args) throws IOException { //1.创建本地字节输入流,获取要读取的数据源 FileInputStream fis = new FileInputStream("D:\\\\Baseball\\\\cai.jpg"); //2.创建一个socket对象 Socket socket = new Socket("127.0.0.1", 8888); //3.获取网络字节输出流对象 OutputStream os = socket.getOutputStream(); //4.通过本地流读取数据 byte[] bytes = new byte[1024]; int len = 0; while ((len = fis.read(bytes)) != -1) { //5.通过网络流写到服务器上去 os.write(bytes, 0, len); } System.out.println("1111111111"); //发送结束标记 socket.shutdownOutput(); //6.读取服务端返回的数据 InputStream is = socket.getInputStream(); while ((len = is.read()) != -1){ System.out.println(new String(bytes,0,len)); } //7.释放资源 fis.close(); socket.close(); } }
BS案例分析:
服务端:
package basicpart.day01.BS.web; import java.io.*; import java.net.ServerSocket; import java.net.Socket; public class BSserver { public static void main(String[] args) throws IOException { //1.创建一个服务器 ServerSocket server = new ServerSocket(8888); /* 浏览器解析服务器回写的html页面,页面中如果有图片,那么浏览器就会单独的开启一个线程,读取服务器的图片 我们就让服务器一直处于监听状态,客户端请求一次,服务器就回写一次 */ while (true) { //2.获取请求的客户端对象 Socket socket = server.accept(); new Thread(new Runnable() { @Override public void run() { try { //3.获取网络字节输入流 InputStream is = socket.getInputStream(); //4.把网络字节输入流对象转换为字符缓存输入流 BufferedReader br = new BufferedReader(new InputStreamReader(is)); //5.把客户端请求信息的第一行读取出来 GET /BS/web/index.html HTTP/1.1 String line = br.readLine(); System.out.println(line); //6.把读取的信息进行切割,只要中间的部分BS/web/index.html String[] arr = line.split(" "); //7.再把路径前面的/去掉,进行截取 从第一个字母截取到最后 String htmlpath = arr[1].substring(1); //8.创建一个本地字节输入流,构造方法中绑定要读取的html路径 FileInputStream fis = new FileInputStream("D:\\\\JA\\\\Part1-basic\\\\src\\\\basicpart\\\\day01\\\\" + htmlpath); //9.使用socket的网络字节输出流对象 OutputStream os = socket.getOutputStream(); //写入HTTP协议响应头,固定写法 os.write("HTTP/1.1 200 OK\\r\\n".getBytes()); os.write("Content-Type:text/html\\r\\n".getBytes()); //必须要写入空行,否则浏览器不解析 os.write("\\r\\n".getBytes()); //10.一读一写复制文件,把服务器读取的html文件回写到客户端 int len = 0; byte[] bytes = new byte[1024]; while ((len = fis.read(bytes)) != -1) { os.write(bytes, 0, len); } //释放资源 fis.close(); socket.close(); } catch (IOException e) { System.out.println(e); } } }).start(); } } }
客户端:
网页输入:http://127.0.0.1:8888/BS/web/index.html
5.函数式接口
概念
函数式接口在Java中是指:有且仅有一个抽象方法的接口。
函数式接口,即适用于函数式编程场景的接口。而Java中的函数式编程体现就是Lambda,所以函数式接口就是可以适用于Lambda使用的接口。只有确保接口中有且仅有一个抽象方法,Java中的Lambda才能顺利地进行推导。
@FunctionalInterface public interface MyFunctionalInterface { void method(); }
作为方法的参数的使用:
public class Demo { public static void show(MyFunctionalInterface myInter){ myInter.method(); } public static void main(String[] args) { //调用show方法,方法的参数是一个接口,所以可以传递接口的实现类 show(new MyFunctionalInterfaceImpl()); //调用show方法,方法的参数是一个接口,所以我们可以传递接口的匿名内部类 show(new MyFunctionalInterface() { @Override public void method() { System.out.println("使用匿名内部类的打印"); } }); //调用show方法,方法的参数是一个函数式接口,所以我们可以传递lambda表达式 //()内为方法的参数 show(()-> System.out.println("lambada表达式打印")); } }
日志性能优化案例:延迟打印字符串
public class DelayLambda { public static void main(String[] args) { String msgA = "hello"; String msgB = "world"; //lambda表达式有延迟执行的功能 //如果等级不为1,则不会执行method方法,不会拼接字符串 showLog(1, () -> msgA + msgB); } public static void showLog(int level, MyFunctionalInterface myInter) { if (level == 1) { myInter.method(); } } }
开启多线程时Runnable也为函数式接口:
public class DelayLambda { public static void main(String[] args) { startThread(()-> System.out.println(Thread.currentThread().getName() + "线程启动了")); } public static void startThread(Runnable task){ //开启多线程 new Thread(task).start(); } }
6.函数式接口作为方法的返回值类型
public class DelayLambda { public static void main(String[] args) { String[] arr = {"aaa","b","ccccc"}; //输出排序前的数组 System.out.println(Arrays.toString(arr)); //调用Arrays中的sort方法,对数组进行排序,第二个参数即为getResult方法返回的实现类对象 Arrays.sort(arr,getResult()); //输出排序后的数组 System.out.println(Arrays.toString(arr)); } public static Comparator<String> getResult(){ //方法的返回值类型为接口,可以使用匿名内部类 /*return new Comparator<String>(){ @Override public int compare(String o1, String o2) { //按照字符串的降序 return o2.length()-o1.length(); } };*/ //方法的返回值类型为函数式接口,可以返回lambda表达式 return (o1,o2)->o2.length()-o1.length(); } }
7.常用的函数式接口
(1)Supplier生产型接口
得到想要的数据类型: public class UsualFunction { public static String getString(Supplier<String> sup){ return sup.get(); } public static void main(String[] args) { String s = getString(()-> "胡歌"); //生产一个字符串并返回 System.out.println(s); } } 求出最大值: public class UsualFunction { public static void main(String[] args) { int[] arr = {2,14,33,66,-35}; int maxValue = getMax(()->{ int max = arr[0]; //遍历数组,获取数组中的其他元素 for (int i : arr) { if(i>max){ max = i; } } return max; }); System.out.println(maxValue); } public static int getMax(Supplier<Integer> sup){ return sup.get(); } }
(2)Consumer消费接口
基本使用: public class UsualFunction { public static void main(String[] args) { method("chris",(String name)->{ //将字符串进行反转 String rename = new StringBuilder(name).reverse().toString(); System.out.println(rename); }); } public static void method(String s, Consumer<String> con){ con.accept(s); } }
默认方法andThen,将两个接口组合到一起,进行消费 先写前面,谁先消费
public class UsualFunction { public static void main(String[] args) { //调用方法,进行两次消费 method("chris",s-> System.out.println(s.toUpperCase()),s-> System.out.println(s.toLowerCase())); } public static void method(String s, Consumer<String> con1, Consumer<String> con2){ con1.andThen(con2).accept(s);//这样就消费了两次 } }
字符串数组格式化输出案例:
public class UsualFunction { public static void main(String[] args) { String[] nameList = {"古力娜扎,女","胡歌,男","宫海成,男"}; method(nameList,(message)->{ //消费方式:对message进行切割,获取姓名,再按指定的格式输出 String name = message.split(",")[0]; System.out.print("姓名:" + name); },(message)->{ //第二个消费方式,获取年龄 String age = message.split(",")[1]; System.out.println(" 性别:" + age); }); } public static void method(String[] list, Consumer<String> con1, Consumer<String> con2){ //首先遍历数组 for (String message : list) { //使用andThen方法连接两个接口,消费每个信息 con1.andThen(con2).accept(message); } } }
(3)Predicate判断接口
public class InterfaceCheck { public static void main(String[] args) { String a = "abcde"; boolean b = stringCheck(a, (String str) -> { //对参数传递的字符串进行判断 return str.length() > 5; }); } public static boolean stringCheck(String s, Predicate<String> pre){ return pre.test(s); } }
默认方法and
public class InterfaceCheck { public static void main(String[] args) { String a = "abcde"; boolean b = stringCheck(a, (str)->str.length()>4,(str)->str.contains("b")); System.out.println(b); } //传递两个predicate接口,判断是否满足两个条件 public static boolean stringCheck(String s, Predicate<String> pre1,Predicate<String> pre2){ // return pre1.test(s) && pre2.test(s); return pre1.and(pre2).test(s); } }
默认方法or 和 negate
return pre1.or(pre2).test(s); return pre1.negate().test(s);
集合信息筛选案例
public class InterfaceCheck { public static void main(String[] args) { String[] array = {"古力娜扎,女", "胡歌,男", "强巴仁增,男", "赵灵儿,女"}; //使用方法,判断数组中名字长度大于四个,且性别为男的 ArrayList<String> list = infoCheck(array,(String info)->{ String name = info.split(",")[0]; return name.length()>2; },(String info)->{ return info.split(",")[1].equals("女"); }); for (String s : list) { System.out.println(s); } } //传递两个predicate接口,判断是否满足两个条件 public static ArrayList<String> infoCheck(String[] arr, Predicate<String> pre1, Predicate<String> pre2) { //定义一个Arraylist集合,存储过滤后的信息 ArrayList<String> list = new ArrayList<>(); for (String info : arr) { //使用predicate中的test方法对获取到的字符串进行判断 boolean b = pre1.and(pre2).test(info); //对得到的布尔值进行判断 if(b){ //两个条件都满足,就加入到集合中 list.add(info); } } return list; } }
(4)Function转换接口 将一个类型的数据转换为另一个数据类型
public class DemoFunction { public static void main(String[] args) { String s = "24"; //调用方法将字符串转换为int类型 change(s,(str)->Integer.parseInt(str)); } public static void change(String str, Function<String,Integer> fun){ int in = fun.apply(str); //也可用int接收 自动拆箱 System.out.println(in); } }
andThen进行两次转换 注意多个连接时,数据类型转换后在lambda表达式中不要写错了
public class DemoFunction { public static void main(String[] args) { String s = "24"; //调用方法将字符串转换为int类型再加10,再转换为字符串 change(s, str -> Integer.parseInt(str) + 10, i -> i + ""); } public static void change(String str, Function<String, Integer> fun1, Function<Integer, String> fun2) { String finalstr = fun1.andThen(fun2).apply(str); System.out.println(finalstr); } }
以上是关于Java进阶 - 网络编程Socket函数式接口常用的函数式接口的主要内容,如果未能解决你的问题,请参考以下文章
Java高阶进阶之Java函数式编程-Stream流-Lambda表达式
Java高阶进阶之Java函数式编程-Stream流-Lambda表达式
Java8函数式接口编程lambda表达式FunctionalInterface注解SupplierConsumerPredicateFunction函数式接口