一、网络编程
(一)软件结构
- C/S结构:全称为Client/Server结构,是指客户端和服务器结构。常见的有QQ、迅雷等。
- B/S结构:全称为Browser/Server结构,是指浏览器和服务器结构。常见的有谷歌、火狐等等。
- 网络编程:就是在一定的协议下实现两台计算机的通信的程序。
(二)网络通信协议 - 网络通信协议:通过计算机网络使多台计算机实现连接位于同一个网络中的计算机在进行连接和通信时需要遵守的一定规则。在计算机网络中,这些连接和通信的规则被称为网络通信协议,它对数据传输的格式、传输的速率和步骤做了统一规定,通信双方必须同时遵守才能完成数据交换。
- TCP/IP协议:传输控制协议/因特网互联协议,是Internet最基本最广泛的协议。分为应用层、传输层、网络层(核心层)、链路层。
(三)协议分类 java.net
包中提供两种常见的网路协议的支持。
UDP:用户数据报协议。UDP是无线通信协议,在数据传输时,数据的发送端和接收端不建立逻辑连接。
特点:数据被限制在64KB内,超出该范围不能发送。
TCP:传输控制协议。TCP协议是面向连接的通信协议,在传输数据之前,在发送端和接收端建立逻辑连接,然后在传输数据,它提供了两台计算机之间可靠无差错的数据传输。
在TCP协议中,在发送数据的准备阶段,客户端与服务器之间三次交互以保证连接的可靠性。
三次握手:
- 第一次:客户端向服务器端发出连接请求,等待服务器确认。
- 第二次:服务器端向客户端回送一个响应,通知客户端收到了连接请求。
- 第三次:客户端再次向服务器端发送确认信息,确认连接。
(四)网络编程三要素
协议:计算机网络通信遵守的规则。
IP地址
- IP地址:互联网协议地址,俗称IP。IP地址用来给一个网路中的计算机设备做唯一的编号。
- IP地址分类:
- IPv4:一个32位的二进制数,通常被分为4个字节。表示为
a.b.c.d
形式,例如192.168.65.100
。 - IPv6:采用128位地址长度,每16个字节分成一组,分成8组16进制数,表示为
ABCD:EF01:2345:6789:ABCD:EF01:2345:6789
。 - 常用命令
- 查看本机IP地址,在控制台输入`ipconfig
- 检查网络是否连通在控制台输入
ping 空格 IP地址
。 - 本机IP地址:
localhost
端口号 - IP地址可以唯一标识网络中的设备,端口号可以唯一标识设备中的进程(应用程序)了。
- 端口号:用两个字节表示的整数,取值范围是065535。其中,01023之间的端口号用于一些知名网络服务应用,普通的应程序需要使用1024以上的端口号。网络软件的端口号不能重复。
- 常用的端口号
- 80端口 网络端口
- 数据库 MySQL:3306 Oracle:1521
- Tomca服务器:8080
二、TCP通信程序
(一)概述
TCP通信能否实现两台计算机之间的数据交互,通信的两端,要严格区分客户端(Client)和服务端(Server)
两端通信时步骤
-
服务端程序,需要事先启动,等待客户端连接
-
客户端主动连接服务器端,连接成功才能通信。服务端不可以主动连接客户端。
Java中提供两个类用于实现TCP通信程序 -
客户端:
java.io.Socket
类表示。创建Socket
对象,向服务端发出连接请求,服务端响应请求,两者建立连接开始通信。 -
服务端:
java.io.SeverSocket
类表示。创建SeverSocket
对象,相当于开启一个服务,并等待客户端的连接。
(二)代码实现
表示客户端的类:java.net.Socket
:此类实现客户端套接字。套接字是两台机器间通信的端点。- 套接字:包含了IP地址和端口的网络单位。
构造方法
-Socke(String host, int port)
:创建一个流套接字并将其连接到指定主机上的指定端口号。
参数:Sting host
:服务器主机的名称/IP地址int port
:服务器的端口号
成员方法OutputStream getOutputStream( )
:返回此套接字的输出流。InputStream getInputStream( )
:返回此套接字的输入流。void close( )
:关闭此套接字。
实现步骤
- 创建一个客户端对象Socket,构造方法绑定服务器的IP地址和端口号
- 使用Socket对象中的方法getOutputStream( )获取网络字节输出流OutputStream对象。
- 使用网络字节输出流OutputStream对象中的方法write,给服务器发送数据
- 使用Socket对象中的方法getInputStream( )获取网络字节输入流InputStream对象
- 使用网络字节输入流InputStream对象中的方法read,读取服务器回写的数据,
- 释放资源(Socket)
注意:
- 客户端和服务器端进行交互,必须使用Socket中提供的网络流,不能使用自己创建的流对象
- 当我们创建客户端对象Socket时,就会请求服务器和服务器经过3次握手建立连接通路。这时如果服务器没有启动,就会抛出异常。如果启动,就可以进行交互。
表示服务器的类
java.net.ServerSocket
:此类实现服务器端套接字
构造方法ServerSocket( int port)
:创建绑定到特定端口的服务器套接字
成员方法Socket accep( )
:倾听并接受到此套接字的连接。
服务器的实现步骤
- 创建服务器ServerSocket对象和系统要指定的端口号
- 使用ServerSocket对象中的方法accept,获取到请求的客户端对象Socket
- 使用Socket对象中的方法getInputStream( )获取网络字节输入流InputStream对象
- 使用网络字节输入流InputStream( )对象中方法read,读取客户端发送的数据
- 使用Socket对象中的方法getOutputStream( )获取网络字节输出流OutputStream对象
- 使用网络字节输出流OutputStream对象中方法write,给客户端回写数据
- 释放资源
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;public class TCPL {public static void main(String[] args) throws IOException {Socket soc = new Socket("127.0.0.1",8888);OutputStream os = soc.getOutputStream();os.write("你好服务器".getBytes());InputStream is = soc.getInputStream();byte [] bytes = new byte[1024];int len=is.read(bytes);System.out.println(new String(bytes,0,len));soc.close();}
}
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;public class TCPS {public static void main(String[] args) throws IOException {ServerSocket sso = new ServerSocket(8888);Socket soc = sso.accept();InputStream is = soc.getInputStream();byte [] bytes = new byte[1024];int len =is.read(bytes);System.out.println(new String(bytes,0,len));OutputStream os =soc.getOutputStream();os.write("收到谢谢".getBytes());soc.close();sso.close();}
}
综合案例:文件上传
实现步骤
客户端
- 创建一个本地字节输入流FileInputStream对象,构造方法中绑定要读取的数据源
- 创建一个客户端Socke对象,构造方法中绑定服务器的IP地址和端口号
- 使用Socket中方法getOutputStream,获取网络字节输出流OutputStream对象
- 使用本地字节输入流FileInputStream对象中方法read,读取本地文件
- 使用网络字节输出流OutputStream对象中方法write,把读取到的文件上传到服务器
- 使用Socket方法getInputStream,获取网络字节输入流InputStream对象
- 使用网络字节输入流InputStream对象中方法read读取服务回写的shuju
- 释放资源(FileInputStream, Socket)
服务端 - 创建一个服务器ServerSocket对象,和系统指定的端口号
- 使用ServerSocket对象中方法accept,获取到请求客户端Socket对象
- 使用Socket对象中的方法getInputStream,获取到网络字节输入流InputStream对象
- 判断文件是否存在,不存在则创建
- 创建一个本地字节输出流FileOutputStream对象,构造方法中绑定要输出的目的地
- 使用网络字节输入流InputStream对象中方法read,读取客户端上传的文件
- 使用本地字节输出流FileOutputStream对象中的方法write,把读取到的文件保存到服务器的硬盘上
- 使用Socket对象中的方法getOutputStream,获取到网路输出流OutputStream对象
- 使用网络字节输出流OutputStream对象中的方法write,给客户端回写上传成功
- 释放资源
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;public class TCPS1 {public static void main(String[] args) throws IOException {ServerSocket sst = new ServerSocket(8888);Socket soc = sst.accept();InputStream is = soc.getInputStream();File file = new File("C:\\Users\\阚开豪\\Desktop\\javaFile\\a");if(!file.exists()){file.mkdirs();}FileOutputStream fos = new FileOutputStream(file+"\\a.txt");int len =0;byte [] bytes = new byte[1024];while ((len=is.read(bytes))!=-1){fos.write(bytes,0,len);}soc.getOutputStream().write("上传成功".getBytes());fos.close();soc.close();sst.close();}
}
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;public class TCPL1 {public static void main(String[] args) throws IOException {FileInputStream fis = new FileInputStream("C:\\Users\\阚开豪\\Desktop\\javaFile\\e.txt");Socket soc = new Socket("127.0.0.1",8888);OutputStream os = soc.getOutputStream();int len = 0;byte [] bytes = new byte[1024];while((len=fis.read(bytes))!=-1){os.write(bytes,0,len);}while((len = fis.read(bytes))!=-1){System.out.println(new String(bytes,0,len));}fis.close();soc.close();}
}
文件上传案例的阻塞问题
解决方案:
上传完文件给服务器一个结束标记
void shutdowOutput( )
:禁用此套接字的输出流。对于TCP套接字,任何以前写入的数据都将发送,并且后跟TCP的正常连接序列。
while ((len=is.read(bytes))!=-1){fos.write(bytes,0,len);}soc.shutdowOutput( );
文件上传优化分析
- 自定义一个文件的命名规则:防止同名文件被覆盖。
规则:域名+毫秒值+随机数 - 让服务器一直处于监听状态(死循环accept方法)
有一个客户端上传就保存一个文件,服务器不用被关闭 - 提高效率;使用多线程技术
有一个客户端上传文件,就开启一个线程,完成文件的上传
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;public class TCPL1 {public static void main(String[] args) throws IOException {FileInputStream fis = new FileInputStream("C:\\Users\\阚开豪\\Desktop\\javaFile\\e.txt");Socket soc = new Socket("127.0.0.1",8888);OutputStream os = soc.getOutputStream();int len = 0;byte [] bytes = new byte[1024];while((len=fis.read(bytes))!=-1){os.write(bytes,0,len);}soc.shutdownOutput();while((len = fis.read(bytes))!=-1){System.out.println(new String(bytes,0,len));}fis.close();soc.close();}
}
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Random;public class TCPS1 {public static void main(String[] args) throws IOException {ServerSocket sst = new ServerSocket(8888);while(true){Socket soc = sst.accept();new Thread(new Runnable() {@Overridepublic void run() {try{ InputStream is = soc.getInputStream();File file = new File("C:\\Users\\阚开豪\\Desktop\\javaFile\\a");if(!file.exists()){file.mkdirs();}String fileName ="itcast"+System.currentTimeMillis()+new Random().nextInt(300000)+".txt";FileOutputStream fos = new FileOutputStream(file+"\\"+fileName);int len =0;byte [] bytes = new byte[1024];while ((len=is.read(bytes))!=-1){fos.write(bytes,0,len);}soc.getOutputStream().write("上传成功".getBytes());fos.close();soc.close();}catch (IOException e){System.out.println(e);}}}).start();}}
}
三、函数式接口
(一)概念
- 函数式接口:有且仅有一个抽象方法的接口。
- Java的函数式接口就是可以使用Lambda使用的接口。
- 语法糖:使用更加方便,但是原理不变的代码语法。
二(格式)
@FuncationInterface
修饰符 interface 接口名称{public abstract 返回值类型 方法名称(可选参数信息)//其他非抽象方法内容}
@FuncationInterface
public interface Myfunction{void myMethod( );
}
@FuncationInterface注解
- 作用:检测接口是否为一个函数式接口。
- 是:编译成功 否:编译失败(接口中没有抽象方法或者抽象方法的个数多于一个)
(三)函数式接口的使用
一般可以作为方法的参数和返回值类型
@FunctionalInterface
public interface MyFun {public abstract void method();
}
public class MyFunImp implements MyFun{@Overridepublic void method() {}
}
public class Myfunmain {public static void show(MyFun mf){mf.method();}public static void main(String[] args) {show(new MyFun() {@Overridepublic void method() {System.out.println("匿名内部类实现。");}});show(()->{System.out.println("Lambda表达式实现。");});show(()->System.out.println("简化Lambda表达式实现。"));}
}
函数式编程
(一)Lambda延迟执行
性能浪费的日志案例
public class log {private static void show(int level,String msg ){if(level==1){System.out.println(msg);}}public static void main(String[] args) {String msg1="Hello";String msg2="World";String msg3="Java";show(1,msg1+msg2+msg3);}
}
使用Lambda优化日志案例
Lambda特点:延迟加载
使用前提:必须存在函数式接口
@FunctionalInterface
public interface Msg {public abstract String build();
}
public class logs {private static void show(int level,Msg msg ){if(level==1){System.out.println(msg.build());}}public static void main(String[] args) {String msg1="Hello";String msg2="World";String msg3="Java";show(1,()->{return msg1+msg2+msg3;});}
}
public class logs {private static void show(int level,Msg msg ){if(level==1){System.out.println(msg.build());}}public static void main(String[] args) {String msg1="Hello";String msg2="World";String msg3="Java";show(1,()->{return msg1+msg2+msg3;});}
}
(二)使用Lambda作为参数和返回值
使用Lambda表达式作为方法参数,其实就是使用函数式接口作为方法参数。
Runnable
接口就是一个函数式接口,假设有一个startThread
方法使用该接口作为参数,那么就可以使用Lambda进行传参,这种情况和Thread
类的构造方法参数为Runnable
没有本质区别。
public class Lam1 {private static void startThread(Runnable task){new Thread(task).start();}public static void main(String[] args) {startThread(()-> System.out.println("线程任务执行"));}
}
**如果一个方法的返回值类型是一个函数式接口,就可以直接返回一个Lambda表达式。**当需要通过一个方法来获取Comparator
接口类型对象作为排序器时,就可以调用该方法获取。
直接return一个Lambda表达式即可
import java.util.Arrays;
import java.util.Comparator;public class Lam2 {private static Comparator<String> newcom(){return (a,b)->b.length()-a.length();}public static void main(String[] args) {String[] array ={"abc","ab","abcd"};System.out.println(Arrays.toString(array));//[abc, ab, abcd]Arrays.sort(array,newcom());System.out.println(Arrays.toString(array));//[abcd, abc, ab]}
}
(三)常用的函数式接口
Supplier接口
java.util.function.Supplier<T>
:接口仅包含一个无参的方法:T get( )。用来获取一个泛型指定类型的对象数据。Supplier<T>
接口被称之为生产型接口,指定接口的泛型是什么类型,那么接口中的get方法就会产生什么类型的数据。
import java.util.function.Supplier;public class Sup {public static String getString(Supplier<String> sup){return sup.get();}public static void main(String[] args) {String s =getString(()->{return "zhangsan";});//简化Lambda表达式String s2=getString(()->"lisi");System.out.println(s);}
}
Consumer接口
java.tuil.function.Consumer<T>
接口与Supplier
接口相反,它不生产数据,而是消费数据,其数据类型由泛型决定。Consumer
接口中包含抽象方法void accept(T t)
,是为消费一个指定泛型的数据。Consumer
接口是一个消费型接口,泛型执行什么类型就可以使用accept方法消费什么类型的数据。具体怎么使用,需要自定义。
import java.util.function.Consumer;public class Cor {public static void method(String name, Consumer<String> con){con.accept(name);}public static void main(String[] args) {method("zhangsan",(String name)->{String rename = new StringBuffer(name).reverse().toString();System.out.println(rename);});}
}
- 默认方法:andthen
作用:需要两个Consumer接口,可以把两个接口组合到一起对数据进行消费。
import java.util.function.Consumer;public class Corps {public static void method(String s, Consumer<String> con1,Consumer<String> con2){con1.andThen(con2).accept(s);}public static void main(String[] args) {method("Hello", (t)->{System.out.println(t.toUpperCase());},(t)->{System.out.println(t.toLowerCase());});}
}
Predicate接口
java.util.function.Predicate<T>
接口
作用:对某种数据类型进行判断,结果返回一个boolean值
Predicate接口中包含一个抽象方法:
- boolean test(T t):用来对指定数据类型进行判断的方法
结果:
符合条件返回true
不符合条件返回false
import java.util.function.Predicate;public class pred {public static boolean check(String s, Predicate<String> pre){return pre.test(s);}public static void main(String[] args) {String s = "abcdef";boolean b = check(s,str->str.length()>5);System.out.println(b);}}
-默认方法and
将两个Predicate
条件使用”与“逻辑连接起来实现”并且“的效果时,可以使用default方法and
- 使用and方法,一个用于判断字符串长度。一个用于判断字符串中是否包含’ a '。
import java.util.function.Predicate;public class pred1{public static boolean check(String s, Predicate<String> pre1,Predicate<String> pre2){return pre1.and(pre2).test(s);}public static void main(String[] args) {String s = "HelloJava";boolean b = check(s,(String str)->{return str.length()>5;},(String str)->{return str.contains("a");});System.out.println(b);}
}
- 默认方法or
与and
类似,or
方法实现逻辑关系中的“或。
-默认方法:negate
表示非即取反
import java.util.function.Predicate;public class pred2 {public static boolean check(String s, Predicate<String> pre1,Predicate<String> pre2){return pre1.or(pre2).test(s);}public static boolean check2(String s,Predicate<String> pre){return pre.negate().test(s);}public static void main(String[] args) {String s="HelloJava";boolean b = check(s,(String str)->{return str.length()>5;},(String str)->{return str.contains("a");});System.out.println(b);boolean b2 =check2(s,(String str)->{return str.length()<10;});System.out.println(b2);}
}
Function接口
-java.util.function.Function<T,R>
接口用来根据一个类型的数据得到另一个类型的数据,前者称为前置条件,后者称为后置条件。
-抽象方法apply
R apply(T t)
:根据类型T的参数获取类型R的结果。例如将String类型转换为Integer类型。
import java.util.function.Function;public class Fun1 {public static void change(String s, Function<String,Integer> fun){int in = fun.apply(s);System.out.println(in);}public static void main(String[] args) {String s = "1234";change(s,(String str)->{return Integer.parseInt(str);});//优化Lambdachange(s,str->Integer.parseInt(str));}
}
默认方法andthen
import java.util.function.Function;public class Fun2 {public static void change(String s, Function<String,Integer> fun1,Function<Integer,String> fun2){String str = fun1.andThen(fun2).apply(s);System.out.println(str);}public static void main(String[] args) {String s = "123";change(s,(String str)->{return Integer.parseInt(str)+10;},(Integer i)->{return i+" ";});change(s,str->Integer.parseInt(str)+10,i->+i+" ");}
}