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进阶|基于java8的函数式接口做一些数据处理

Java进阶(11) - Java8+新特性

Java高阶进阶之Java函数式编程-Stream流-Lambda表达式

Java高阶进阶之Java函数式编程-Stream流-Lambda表达式

Java开发『函数式编程』——函数式接口

Java8函数式接口编程lambda表达式FunctionalInterface注解SupplierConsumerPredicateFunction函数式接口