https://www.cnblogs.com/swordfall/p/10781281.html
1. InetAddress 类概述
InetAddress 类对象代表了一个 IP 地址信息。其提供了一系列获取 IP 地址等相关信息的方法。
static InetAddress getLocalHost()
:获得本地主机IP地址对象。static InetAddress getByName(String host)
:根据IP地址字符串或主机名获得对应的IP地址对象。String getHostName()
:获得主机名。String getHostAddress()
:获得IP地址字符串。
2. Java UDP 通信
UDP协议相关的两个类
- DatagramPacket,数据包对象。作用:用来封装要发送或要接收的数据,比如:集装箱
- DatagramSocket,发送对象。作用:用来发送或接收数据包,比如:码头
2.1 DatagramPacket
类构造器:
public DatagramPacket(byte buf[], int length, InetAddress address, int port)
:创建发送端数据包对象,发送端用。- buf:要发送的内容,字节数组
- length:要发送内容的长度,单位是字节
- address:接收端的IP地址对象
- port:接收端的端口号
public DatagramPacket(byte buf[], int length)
:创建接收端的数据包对象,接收端用。- buf:用来存储接收到内容
- length:能够接收内容的长度
常用方法:
int getLength()
:获得实际接收到的字节个数。
2.2 DatagramSocket
构造方法:
public DatagramSocket()
: 创建发送端的 Socket 对象,系统会随机分配一个端口号。public DatagramSocket(int port)
: 创建接收端的 Socket 对象并指定端口号
常用方法:
public void send(DatagramPacket p)
:发送数据包public synchronized void receive(DatagramPacket p)
:接收数据包
2.3 实现客户端发消息,服务端接收消息
客户端: ```java public class UDPClient { public static void main(String[] args) throws Exception {
System.out.println("===启动客户端===");
// 1.创建一个集装箱对象,用于封装需要发送的数据包!
/**
new DatagramPacket(byte[] buf, int length, InetAddress address, int port)
参数一:封装数据的字节数组。
参数二:发送数据的长度!
参数三:服务端的IP地址
参数四:服务端程序的端口号码。
*/
byte[] buffer = "今晚,约吗?".getBytes();
DatagramPacket packet = new DatagramPacket(buffer, buffer.length
, InetAddress.getLocalHost(), 6666);
// 2.创建一个码头对象
// 参数可以申明客户端端口,可以有可以没有,默认会给一个端口。
DatagramSocket socket = new DatagramSocket();
// 3.开始发送数据包对象
socket.send(packet);
socket.close();
} }
2. 服务端:
```java
public class UDPServer {
public static void main(String[] args) throws Exception {
System.out.println("==启动服务端程序==");
// 1.创建一个接收客户都端的数据包对象(集装箱)
/**
* new DatagramPacket(byte[] buffer ,int lenght):
* 参数一:接收数据的数组。
* 参数二:接收数据的数组的长度!
*/
byte[] buffer = new byte[1024 * 64];
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
// 2.创建一个接收端的码头对象
DatagramSocket socket = new DatagramSocket(6666);
// 3.开始接收
socket.receive(packet);
// 4.从集装箱中获取本次读取的数据量
int len = packet.getLength();
// 5.输出数据
String rs = new String(buffer, 0, len);
System.out.println(rs);
// 6.服务端还可以获取发来信息的客户端的IP和端口。
String ip = packet.getAddress().getHostAddress();
int port = packet.getPort();
System.out.println("对方:" + ip + ":" + port);
socket.close();
}
}
3. Java TCP 通信
TCP 通信也叫 Socket 网络编程,只要代码基于 Socket 开发,底层就是基于了可靠传输的 TCP 通信。
TCP协议相关的类
- Socket:一个该类的对象就代表一个客户端程序。
ServerSocket:一个该类的对象就代表一个服务器端程序。
3.1 Socket
构造方法:
public Socket(String host, int port)
:根据 ip 地址字符串和端口号创建客户端 Socket 对象
注意事项:只要执行该方法,就会立即连接指定的服务器程序,如果连接不成功,则会抛出异常。如果连接成功,则表示三次握手通过。
常用方法:
public OutputStream getOutputStream()
:获得字节输出流对象public InputStream getInputStream()
:获得字节输入流对象
3.2 ServerSocket
构造方法:
public ServerSocket(int port)
:根据 port 端口创建服务端 ServerSocket 对象;
常用方法:
public Socket accept()
:等待接收一个客户端的 Socket 管道连接请求,连接成功返回一个 Socket 对象;3.3 实现客户端服务端收发消息
客户端发一行消息,服务端接收一行。
可以拓展多个客户端向服务端发送消息,服务端创建线程池,没收到一个请求分配一个线程接收消息。客户端:
public class Client { public static void main(String[] args) throws Exception { // 1.客户端要请求于服务端的socket管道连接。 // Socket(String host, int port) Socket socket = new Socket("127.0.0.1", 9999); // 2.从socket通信管道中得到一个字节输出流 OutputStream os = socket.getOutputStream(); // 3.把低级的字节输出流包装成高级的打印流。 PrintStream ps = new PrintStream(os); // 4.开始发消息出去 ps.println("我是客户端,喜欢你很久了,第一次给你发消息,只想说:约吗?"); ps.flush(); System.out.println("客户端发送完毕~~~~"); } }
服务端:
public class ServerDemo02 { public static void main(String[] args) throws Exception { System.out.println("----服务端启动----"); // 1.注册端口: public ServerSocket(int port) ServerSocket serverSocket = new ServerSocket(9999); // 2.开始等待接收客户端的Socket管道连接。 Socket socket = serverSocket.accept(); // 3.从socket通信管道中得到一个字节输入流。 InputStream is = socket.getInputStream(); // 4.把字节输入流转换成字符输入流 Reader isr = new InputStreamReader(is); // 5.把字符输入流包装成缓冲字符输入流。 BufferedReader br = new BufferedReader(isr); // 6.按照行读取消息 。 String line; if ((line = br.readLine()) != null) { System.out.println(line); } } }
4. 基本通信模型的概念介绍
BIO通信模式:同步阻塞式通信。(Socket网络编程也就是上面的通信架构)
- 同步:当前线程要自己进行数据的读写操作。(自己去银行取钱)
- 异步: 当前线程可以去做其他事情,(委托一小弟拿银行卡到银行取钱,然后给你)
- 阻塞: 在数据没有的情况下,还是要继续等待着读。(排队等待)
- 非阻塞:在数据没有的情况下,会去做其他事情,一旦有了数据再来获取。(柜台取款,取个号,然后坐在椅子上做其它事,等号广播会通知你办理
BIO表示同步阻塞式IO,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。
同步阻塞式性能极差:大量线程,大量阻塞。
- 伪异步通信:引入了线程池。
- 不需要一个客户端一个线程,可以实现1个线程复用来处理很多个客户端!
- 这种架构,可以避免系统的死机,因为不会出现很多线程,线程可控。
- 但是高并发下性能还是很差:a.线程数量少,数据依然是阻塞的。数据没有来线程还是要等待!
- NIO表示同步非阻塞IO,服务器实现模式为请求对应一个线程,
- 同步:线程还是要不断的接收客户端连接,以及处理数据。
- 非阻塞:如果一个管道没有数据,不需要等待,可以轮询下一个管道是否有数据!
即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。
- 1个主线程专门负责接收客户端:
- 1个线程[c1 ,s2 ,c3,c4, ,s2 ,c3,c4,,c3,c4, ,s2 ,c3,c4]轮询所有的客户端,发来了数据才会开启线程处理
- 这种架构性能还可以
- AIO表示异步非阻塞IO,服务器实现模式为一个有效请求一个线程,
- 客户端的I/O请求都是由操作系统先完成IO操作后再通知服务器应用来启动线程进行处理。
- 异步:服务端线程接收到了客户端管道以后就交给底层处理它的io通信。自己可以做其他事情。
- 非阻塞:底层也是客户端有数据才会处理,有了数据以后处理好通知服务器应用来启动线程进行处理。
小结:
各种模型应用场景:
- BIO适用于连接数目比较小且固定的架构,该方式对服务器资源要求比较高,JDK 1.4以前的唯一选择。
- NIO适用于连接数目多且连接比较短(轻操作)的架构,如聊天服务器,编程复杂,JDK 1.4开始支持。
- AIO适用于连接数目多且连接比较长(重操作)的架构,如相册服务器,充分调用操作系统参与并发操作,编程复杂,JDK 1.7开始支持。