Java网络编程
一、网络分层
1、OSI七层
应用层: HTTP FTP POP3 SMTP TELNET ...
表示层:
会话层:
传输层: TCP UDP
网络层: IP
数据链路层
物理层
2、TCP/IP协议
应用层 --- 应用层,表示层,会话层
传输层 --- 传输层
网际层 --- 网络层
网络层 --- 数据链路层,物理层
二、计算机网络的基本要素
1、IP地址(门牌号)
计算在网络中的唯一标识
ipv4 - 32位
127.0.0.1 -- 指向当前机器
255.255.255.255 -- 广播地址
ipv6 - 128位
2、主机名
因为ip地址难于记忆,可以为不同的主机起不同的名字 来唯一标识 这个唯一标识主机的名字 称为主机名 和 ip地址具有映射关系
127.0.0.1 localhost 代表本机
3、域名
全网认可的主机名 域名不能随意用 需要注册 先到先得 收取一定费用
4、域名解析
DNS服务器:域名解析服务器 接受域名 解析为 ip 返回
5、Hosts文件:windows自带的机制 可以在本地做主机名 和ip 的映射
在windows中使用主机名时 会先查找hosts文件 得到主机名和ip的映射 得到就用 得不到再求访问公网上的dns服务器翻译
C:\Windows\System32\drivers\etc\hosts
6、端口
每个计算机 都有 若干个 可以供外界连接的窗口 每个窗口都有独一无二的编号 程序可以监听指定的端口号
外界通过ip连接到当前机器后 将数据发送 给指定端口号 从而 让正在监听该端口的程序收到数据。
范围:0~65535 其中0~1024是被系统占用了的,我们不能使用。
7、协议:网络中主机之间通信的规范
HTTP 80
HTTPS 443
SMTP 25
SSH 22
三、InetAddress
java.net.InetAddress - 此类表示互联网协议 (IP) 地址
主要方法:
static InetAddress getLocalHost() //获取本机ip地址对象
static InetAddress getByName(String host) //根据给定ip或主机名 获取ip地址对象
String getHostName() //获取当前对象代表的主机的主机名
String getHostAddress() //获取当前对象代表的主机的ip地址
四、传输协议
1、UDP
不需要建立连接
将数据及源和目的地封装为数据包
每个数据报的大小限制在64k内
因为无连接,是不可靠协议
优点:快
缺点:会丢数据
应用场景:数据及时传输要求高,但是数据稳定性不需保证的场景(例如:QQ,微信聊天)
2、TCP
建立连接,形成传输数据的通道
在连接中进行大数据量传输,靠流来进行操作
三次握手,是可靠协议
必须先建立连接,效率会稍低
优点:不丢数据
缺点:效率相对较低
应用场景: 要求可靠性比较高的场景(web服务)
五、java中的socket编程
1、利用socket实现UDP通信
java.net.DatagramSocket - UDP协议的编程接口类,负责收发数据包
构造方法:
DatagramSocket()//通常用来发送
DatagramSocket(int port)//通常用来接收
相关方法:
void send(DatagramPacket p) //发送数据包
void receive(DatagramPacket p) //接收数据包
void close() //关闭套接字
数据包:
java.net.DatagramPacket - UDP的数据包,内含 数据 数据来源 数据目的地
构造:
DatagramPacket(byte[] buf, int length)//一般用做接收
DatagramPacket(byte[] buf, int length, InetAddress address, int port) //一般用作发送
相关方法:
InetAddress getAddress()
int getLength()
byte[] getData()
int getPort()
案例代码:利用UDP实现客户端和服务器通信
UDPServer:
/**
* java利用socket实现基于UDP协议的服务端
*/
public class UDPServer {
public static void main(String[] args) throws Exception {
//构建UDP服务端并监听服务端口--8000
DatagramSocket udpSocket = new DatagramSocket(8000);
//构建接收数据包的对象
byte[] bytes = new byte[1024];
DatagramPacket datagramPacket = new DatagramPacket(bytes, bytes.length);
//接收数据包
System.err.println("数据等待中,请稍后...");
udpSocket.receive(datagramPacket);
//获取接收的数据
byte[] reciveData = datagramPacket.getData();
//打印获取的内容
String result = new String(reciveData, 0, reciveData.length, "utf-8");
System.out.println("客户端发送的信息:"+result);
//关闭资源
udpSocket.close();
}
}
UDPClient:
/**
* java利用socket实现基于UDP协议的客户端
*/
public class UDPClient {
public static void main(String[] args) throws Exception {
//构建UDP客户端
DatagramSocket datagramSocket = new DatagramSocket();
//扫描器接收用户输入
Scanner scanner = new Scanner(System.in);
System.out.println("客户端准备发送数据:");
byte[] content = scanner.next().getBytes("utf-8");
//构建服务器的地址对象
InetAddress localhost = InetAddress.getByName("192.168.176.136");
//封装数据包
DatagramPacket datagramPacket = new DatagramPacket(content, content.length, localhost, 8000);
//发送数据
datagramSocket.send(datagramPacket);
System.out.println("数据发送完成...");
//关闭资源
datagramSocket.close();
}
}
2、socket实现TCP
1)主要流程
需要经历三次握手建立连接
用流来传输大量数据
有数据包重发机制 实现可靠的数据传输
可以双向传输数据
TCP基于连接工作 连接具有两端 称为服务器端 和 客户端
把主动发起请求的一端 称为客户端 被动等待请求 接收连接的一方称之为 服务器端
2)java中的主要api
--代表soket地址的类:
SocketAddress - InetSocketAddress
构造方法:
InetSocketAddress(InetAddress addr, int port)
InetSocketAddress(String hostname, int port)
其他方法:
InetAddress getAddress()
String getHostName()
int getPort()
--表示TCP socket客户端的类:
java.net.Socket
构造方法:
Socket()
Socket(InetAddress address, int port)
Socket(String host, int port)
相关方法:
void connect(SocketAddress endpoint) //开始连接服务器
void connect(SocketAddress endpoint, int timeout) //开始连接服务器,额外指定超时时间
InetAddress getInetAddress() //获取连接的地址
int getPort() //获取连接的端口
InputStream getInputStream() //获取输入流
OutputStream getOutputStream() //获取输出流
void shutdownInput() //结束对输入流的读取,不会关闭流,只是无法再从流中读取数据
void shutdownOutput() //结束对输出流的输入,不会关闭流,只是无法再向流中写出数据
boolean isInputShutdown() //判断输入流是否已经被置于结束状态
boolean isOutputShutdown() //判断输出流是否已经被置于结束状态
void close() //关闭套接字 自动关闭底层输入输出流
--表示TCP socket服务端的类:
java.net.ServerSocket
构造方法:
ServerSocket()
ServerSocket(int port)
相关方法:
Socket accept() //开始接受连接 一旦调用 就进入阻塞状态 直到有一个客户端连接过来 才会解除阻塞继续运行
void close() //关闭套接字 自动关闭底层输入输出流
InetAddress getInetAddress() //获取地址对象
int getLocalPort() //获取本地监听端口
java基于socket通信的TCP案例:
服务端:
/**
* Server socket Tcp
*/
public class ServerSocketForTCP {
public static void main(String[] args) throws Exception {
//1、构建server socket对象(监听端口)
ServerSocket serverSocket = new ServerSocket(9999);
//2、等待客户端链接Socket(套接字)
Socket accept = serverSocket.accept();
//3、基于socket获取输入流接收客户端的数据
InputStream inputStream = accept.getInputStream();
//4、流处理
InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
String msg = bufferedReader.readLine();
System.out.println("Server端接收到的消息:"+msg);
//5、关闭资源
serverSocket.close();
}
}
客户端:
/**
* Client socket Tcp
*/
public class ClientSocketForTCP {
public static void main(String[] args) throws Exception {
//1、构建客户端的socket
Socket socket = new Socket();
//2、连接到服务端
//构建socker地址对象
InetSocketAddress socketAddress = new InetSocketAddress("localhost", 9999);
socket.connect(socketAddress);
//3、基于socker完成数据传递
OutputStream outputStream = socket.getOutputStream();
PrintStream printStream = new PrintStream(outputStream);
printStream.print("你好,我是客户端信息");
//4、关闭资源
socket.close();
}
}
java8中的常用新特性
一、Lambda表达式
lambda表达式就是匿名函数,匿名函数可以作为参数传递,表示数据。目的是让代码更加简洁,开发更高效!
(参数1,参数2...)->{函数体}
特点:
* 参数的类型可以不写,lambda表达式可以根据上下文进行推断
* 函数体如果只有一行代码,大括号可以不写。
* 如果只有一行代码并且是作为返回值使用,则return也可以不写
* lambda表达式与函数式接口绑定出现
案例1:利用lambda表达式改写Thread线程对象构建
//lambda表达式
Thread thread = new Thread(() -> {
System.out.println("倒数:3");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("倒数:2");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("倒数:1");
});
thread.start();
案例2:参数根据上下文自动推算
//参数x和y的类型进行了自动推算
MyLambdaTest myLambdaTest = (x, y)->x+y; //()->{return x+y;}
interface MyLambdaTest{
default void printName(){
System.out.println(this.getClass().getName());
}
int sum(int a, int b);
}
二、函数式接口
1、特点:
只包含一个抽象方法的接口就是函数式接口。
如果需要将接口设置为函数式接口,可以在接口上添加注解:
@FunctionalInterface
2、函数式接口分类
jdk内置的函数式接口:
java.util.function:(Function、Consumer、Supplier、Predicate)
函数型接口:有输入参数,也有返回值。Function
案例:
Function<String, Integer> fumc = (x)->x.length();
Integer nihao = fumc.apply("nihao");
System.out.println(nihao);
消费型接口:有参数,没有返回值。Consumer
供给型接口:没有参数,有返回值。Supplier
断言型接口:有输入参数,也有返回值,返回boolean类型。 Predicate
补充案例:java支持不定长参数
public static void nums(int ... n){
//System.out.println(n instanceof int[]);
int sum = 0;
for (int num:n) {
sum+=num;
}
System.out.println("所有数值的总和是:"+sum);
}
三、方法引用和构造引用
1、方法引用
当要传递给Lambda体的操作已经有实现方法,可以直接使用方法引用
写法:
对象/类::方法名 //仅仅是引用但不执行
案例:
lambda:
Consumer<String> consumer = (x)-> System.out.println(x);
consumer.accept("hello world");
lambda+函数引用:
Consumer<String> consumer = System.out::println; //此处只是引用out对象的println方法,而不是调用
consumer.accept("hello world");
2、构造引用
//普通构造引用
Supplier<Students> students = Students::new;
students.get().printClassInfo();
class Students{
public void printClassInfo(){
System.out.println(this.getClass().getName());
}
}
//数组构造引用
//可以传递一个参数(表示数组长度),返回数组对象,通过相应类型的构造引用创建对象。
Integer[] arr = new Integer[5];
Function<Integer, Integer[]> integersFunc = (len)->new Integer[len];
//Integer数组的构造引用
Function<Integer, Integer[]> integersFunc1 = Integer[]::new;
Integer[] apply = integersFunc1.apply(6);
System.out.println(Arrays.toString(apply));