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);
}
}