一、网络通信三件套
1、IP地址: 设备在网络中的地址,唯一标识
-
概念: Internet Protocal,简称为IP,全称“互联网协议地址”。
-
常见分类: IPv4(32位) 和 IPv6(128位)
-
IP地址形式: 公网地址和私网地址(局域网),以192.168为开头的地址为常见的局域网地址。
-
查看IP地址命令 ipconfig(windows):查看本机IP;ifconfig(linux/mac)
-
查看IP是否通信: ping IP或是 ping 域名
-
特殊IP地址: 127.0.0.1 或者 localhost,称为回送地址也称为本地回环地址,只会寻找当前本机
-
IP操作类-InetAddress
-
API方法
方法 | 说明 |
---|---|
static InetAddress getLocalHost() | 返回本主机的地址对象 |
static InetAddress getByName(String host) | 获取指定主机的IP地址对象,参数是域名或者IP地址 |
static String getHostName() | 返回IP地址的主机名 |
static String getHostAddress() | 返回IP地址字符串 |
static boolean isReachable(int timeout) | 在指定毫秒内连接IP地址对应的主机,连通返回true |
- 使用示例
public class InetAddressDemo {
public static void main(String[] args) {
try {
/*1、获取本机地址对象*/
InetAddress inetAddress = InetAddress.getLocalHost();
System.out.println(inetAddress.toString());
/*2、获取域名IP对象*/
InetAddress inetAddress1 = InetAddress.getByName("www.baidu.com");
System.out.println(inetAddress1.toString());
/*3、获取主机名*/
String hostname = InetAddress.getLocalHost().getHostName();
System.out.println(hostname);
/*4、获取公网IP地址*/
String ip = InetAddress.getLocalHost().getHostAddress();
System.out.println(ip);
/*5、判断IP是否能通信*/
boolean isPing = InetAddress.getByName("192.168.137.1").isReachable(5000);
System.out.println(isPing);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
/*打印输出*/
LAPTOP-76LAVL3O/192.168.xxx.xxx
www.baidu.com/180.101.50.242
LAPTOP-76LAVL3O
192.168.xxx.xxx
true
2、端口: 应用程序在设备中的唯一标识。
- 概念: 标识正在计算机设备上运行的进程,被规定为一个16位的二进制,范围是0~65535
- 端口类型:
- 常用端口: 0~1023,例如HTTP:80,FTP:21
- 注册端口(主要端口对象): 1024~49151,分配给用户进程或某些应用程序。如Tomcat:8080,MySQL:3306
- 动态端口: 49152~65535,动态分配进程。
3、协议: 数据在网络中传输的规则
- 概念: 连接和通信数据的规则称为网络通信规则。
- 通信协议的两套参考模型:对照计算机网络对两种模型的介绍
- OSI参考模型: 理想化的世界互联协议标准
- TCP/IP参考模型: 事实上的国际标准
- 常见协议
-
TCP协议(Transmission Control Protocol):传输控制协议
- TCP协议特点:
(1) 使用TCP,双方必须先建立连接,它是一种面向连接的可靠通信协议。
(2) 传输前,采用 “三次握手” 方式建立连接,所以是可靠的。
(3) 在连接中可以进行大量传输。
(4) 连接、发送数据都需要确认,且传输完毕后,还需要释放连接,通信效率较低。- TCP协议通信场景: 对信息安全要求较高的场景,比如文件下载、金融等数据通信。
- 三次握手:
(1)第一次握手: 客户端向服务器发送连接请求,等待确认。
(2)第二次握手: 服务器向客户端返回一个响应,向客户端确认收到连接请求。
(3)第三次握手: 客户端向服务器发送确认信息,连接正式建立。 - 为什么需要三次握手? 因为客户端和服务器要相互验证通信是否通畅,若有一次握手没有通过,则连接不建立。
- 四次挥手断开连接
(1)第一次挥手: 客户端向服务器发送取消连接请求
(2)服务器向客户端返回一个响应表示收到请求
(3)服务器向客户端发送确认取消信息
(4)客户端向服务器发送确认信息,连接正式取消
- TCP协议特点:
-
UDP(User Datagram Protocol):用户数据报协议
- UDP的特点
(1)UDP是一种无连接、不可靠的传输协议
(2)将数据源IP、目标IP和端口封装成数据包,不需要建立连接
(3)每个数据包大小限制在64KB内
(4)发送不管对方是否准备好,接收方收到也不确认,故是不可靠的
(5)可以广播发生,发送数据后无需释放资源,开销小,速度快 - UDP使用场景: 语音通话,视频会话,直播等等…
- UDP的特点
-
二、UDP通信
1、数据包对象DatagramPacket创建
构造器 | 说明 |
---|---|
DatagramPacket(byte[] buf, int length, InetAddress address, int port ) | 创建发送端数据包对象:buf——发送的内容,length——发送内容的字节长度,address——接收端的IP地址,port——接收端的端口号 |
DatagramPacket(byte[] buf, int length ) | 创建接收端数据包对象:buf——接收内容,length——接收最大字节长度 |
2、发送端和接收端对象DatagramSocket创建
构造器 | 说明 |
---|---|
DatagramSocket() | 创建发送端的Socket对象,系统随机分配端口号 |
DatagramSocket(int port) | 创建接收端对象并指定端口号 |
3、发送/接收端对象操作数据包
方法 | 说明 |
---|---|
void send(DatagramPacket dp) | 发送数据包 |
void receive(DatagramPacket p) | 接收数据包 |
(1)单收单发数据示例
/*发送端*/
public class ClientDemo {
public static void main(String[] args) {
try {
//1、创建发送对象
DatagramSocket sender = new DatagramSocket();
//2、数据包对象
byte[] buf = "UDP数据报".getBytes();
DatagramPacket data = new DatagramPacket(buf,buf.length, InetAddress.getLocalHost(),8888);
//3、发送数据
sender.send(data);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
/*接收端*/
class ServerDemo{
public static void main(String[] args) {
try {
//1、创建接收对象
DatagramSocket accepter = new DatagramSocket(8888);
//2、接收数据包对象
byte[] buf = new byte[1024];
DatagramPacket datagramPacket = new DatagramPacket(buf,buf.length);
accepter.receive(datagramPacket);
//3、打印数据
System.out.println(new String(buf,0,datagramPacket.getLength()));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
/*打印输出*/
UDP数据报
(2)多发多收示例
public class ClientDemo {
public static void main(String[] args) {
try {
//1、创建发送对象
DatagramSocket sender = new DatagramSocket();
byte[] buf;
Scanner in = new Scanner(System.in);
String message;
DatagramPacket data;
while(!(message = in.next()).equals("exit")) {
//2、数据包对象
buf = message.getBytes();
data = new DatagramPacket(buf, buf.length, InetAddress.getLocalHost(), 8888);
//3、发送数据
sender.send(data);
}
message = "exit";
buf = message.getBytes();
data = new DatagramPacket(buf, buf.length, InetAddress.getLocalHost(), 8888);
sender.send(data);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
class ServerDemo{
public static void main(String[] args) {
try {
//1、创建接收对象
DatagramSocket accepter = new DatagramSocket(8888);
//2、接收数据包对象
byte[] buf = new byte[1024];
DatagramPacket datagramPacket;
while(true){
datagramPacket = new DatagramPacket(buf,buf.length);
accepter.receive(datagramPacket);
if (new String(buf,0,datagramPacket.getLength()).equals("exit")) {
System.out.println("会话已结束");
break;
}
//3、打印数据
System.out.println(new String(buf,0,datagramPacket.getLength()));
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
结果显示
问:实验中如何实现多端口通讯
答:事实上,我们可以直接使用不同的端口直接向接收端发送数据。
结果如图所示
4、UDP通信——广播、组播
(1)三种UDP通信方式
- 单播: 单台主机与单台主机之间通信。
- 广播: 当前主机与所在网络中的所有主机通信。
- 组播: 当前主机与选定主机组通信
(2)UDP实现广播
- 广播地址:255.255.255.255
- 操作过程
- 发送端发送的数据包目的地址是广播地址,且指定端口9999。
- 本机所在网段的其它主机的程序只要匹配端口成功就可以收到消息
public class BroadCastDemo {
public static void main(String[] args) {
try {
//1、创建发送对象
DatagramSocket sender = new DatagramSocket();
byte[] buf;
Scanner in = new Scanner(System.in);
String message;
DatagramPacket data;
while(!(message = in.next()).equals("exit")) {
//2、数据包对象
buf = message.getBytes();
data = new DatagramPacket(buf, buf.length, InetAddress.getByName("255.255.255.255"), 9999);
//3、发送数据
sender.send(data);
}
message = "exit";
buf = message.getBytes();
data = new DatagramPacket(buf, buf.length, InetAddress.getByName("255.255.255.255"), 9999);
sender.send(data);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
/*实验组*/
class ServerDemo1{
public static void main(String[] args) {
try {
//1、创建接收对象
DatagramSocket accepter = new DatagramSocket(9999);
//2、接收数据包对象
byte[] buf = new byte[1024];
DatagramPacket datagramPacket;
while(true){
datagramPacket = new DatagramPacket(buf,buf.length);
accepter.receive(datagramPacket);
if (new String(buf,0,datagramPacket.getLength()).equals("exit")) {
System.out.println("会话已结束");
break;
}
//3、打印数据
System.out.println("IP地址:" + datagramPacket.getAddress() + ":" + datagramPacket.getPort() + "发送数据:" + new String(buf,0,datagramPacket.getLength()));
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
/*对照组*/
class ServerDemo2{
public static void main(String[] args) {
try {
//1、创建接收对象
DatagramSocket accepter = new DatagramSocket(8888);
//2、接收数据包对象
byte[] buf = new byte[1024];
DatagramPacket datagramPacket;
while(true){
datagramPacket = new DatagramPacket(buf,buf.length);
accepter.receive(datagramPacket);
if (new String(buf,0,datagramPacket.getLength()).equals("exit")) {
System.out.println("会话已结束");
break;
}
//3、打印数据
System.out.println("IP地址:" + datagramPacket.getAddress() + ":" + datagramPacket.getPort() + "发送数据:" + new String(buf,0,datagramPacket.getLength()));
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
效果图
(3)UDP实现组播
- 组播地址: 224.0.0.0 ~ 239.255.255.255
- 具体操作
- 发送端数据包目的地址是组播IP(例如:224.0.1.1:9999)
- 接收端必须绑定组播IP
- DatagramSocket的子类MulticastSocket绑定组播IP
- 示例
public class MutiCastDemo {
public static void main(String[] args) {
/*1、创建发送对象*/
try {
DatagramSocket sender = new DatagramSocket();
DatagramPacket data;
String message;
Scanner scanner = new Scanner(System.in);
byte[] buf;
while(!(message = scanner.next()).equals("exit")){
buf = message.getBytes();
data = new DatagramPacket(buf, buf.length, InetAddress.getByName("224.0.1.1"),9999);
sender.send(data);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
class ServerDemo3{
public static void main(String[] args) {
try {
//1、创建接收对象
MulticastSocket accepter = new MulticastSocket(9999);
//绑定组播
accepter.joinGroup(InetAddress.getByName("224.0.1.1"));
//2、接收数据包对象
byte[] buf = new byte[1024];
DatagramPacket datagramPacket;
while(true){
datagramPacket = new DatagramPacket(buf,buf.length);
accepter.receive(datagramPacket);
if (new String(buf,0,datagramPacket.getLength()).equals("exit")) {
System.out.println("会话已结束");
break;
}
//3、打印数据
System.out.println("IP地址:" + datagramPacket.getSocketAddress() + "发送数据:" + new String(buf,0,datagramPacket.getLength()));
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
class ServerDemo4{
public static void main(String[] args) {
try {
//1、创建接收对象
MulticastSocket accepter = new MulticastSocket(9999);
//绑定组播
accepter.joinGroup(InetAddress.getByName("224.0.1.1"));
//2、接收数据包对象
byte[] buf = new byte[1024];
DatagramPacket datagramPacket;
while(true){
datagramPacket = new DatagramPacket(buf,buf.length);
accepter.receive(datagramPacket);
if (new String(buf,0,datagramPacket.getLength()).equals("exit")) {
System.out.println("会话已结束");
break;
}
//3、打印数据
System.out.println("IP地址:" + datagramPacket.getSocketAddress() + "发送数据:" + new String(buf,0,datagramPacket.getLength(),StandardCharsets.UTF_8));
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
效果图
三、TCP通信
(1)实现方法:
TCP通信是通过C/S双方建立Socekt通道
- 客户端
创建构造器
构造器 | 说明 |
---|---|
Socket(String host, int port) | 常见发送端的Socket对象与服务端连接,参数为服务端程序的IP和端口 |
方法
方法 | 说明 |
---|---|
OutputStream getOutputStream() | 获取字节输出流对象 |
InputStream getInputStream () | 获取字节输入流对象 |
- 服务端
服务端构造器
构造器 | 说明 |
---|---|
ServerSocket(int port) | 注册服务端口 |
方法
方法 | 说明 |
---|---|
Socket accept() | 等待Socket连接,连接成功后返回Socket对象 |
服务器端
/*服务端*/
class Server {
public static void main(String[] args) {
try {
//1、建立通道
ServerSocket socket = new ServerSocket(7777);
//2、连接通道
Socket connection = socket.accept();
//3、读数据
BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
String message;
System.out.println(socket.getLocalSocketAddress() + " 连接成功,时间:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(System.currentTimeMillis()));
while ((message = reader.readLine()) != null) {
System.out.println(socket.getInetAddress().getHostName() + "," + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(System.currentTimeMillis()) + " : " + message);
}
} catch (SocketException socketException) {
System.out.println("连接已中断");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
客户端
public class SocketDemo {
public static void main(String[] args) {
try {
//1、建立通道
Socket socket = new Socket(InetAddress.getLocalHost(), 7777);
//2、发送数据
PrintStream stream = new PrintStream(socket.getOutputStream());
//3、写入数据
String message;
Scanner in = new Scanner(System.in);
while (!(message = in.next()).equals("exit")) {
stream.println(message);
stream.flush();
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
效果图
(2)多客户端连接服务端
代码实现
public class SocketDemo {
public static void main(String[] args) {
try {
//1、建立通道
Socket socket = new Socket(InetAddress.getLocalHost().getHostAddress(),7777);
if (socket.isConnected()) System.out.println(socket.getRemoteSocketAddress() + " 已连接!");
//2、发送数据
PrintStream stream = new PrintStream(socket.getOutputStream());
//3、写入数据
String message;
Scanner in = new Scanner(System.in);
while (!(message = in.next()).equals("exit")) {
stream.println(message);
stream.flush();
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
class client{
public static void main(String[] args) {
try {
//1、建立通道
Socket socket = new Socket(InetAddress.getLoopbackAddress().getHostAddress(),7777);
if (socket.isConnected()) System.out.println(socket.getRemoteSocketAddress() + " 已连接!");
//2、发送数据
PrintStream stream = new PrintStream(socket.getOutputStream());
//3、写入数据
String message;
Scanner in = new Scanner(System.in);
while (!(message = in.next()).equals("exit")) {
stream.println(message);
stream.flush();
}
} catch (IOException e) {
System.out.println("连接已中断");
}
}
}
class Server {
public static void main(String[] args) {
try {
//1、建立通道
ServerSocket socket = new ServerSocket(7777);
//2、连接通道
while(true){
Socket connection = socket.accept();
new ServerServiceThread(connection,"服务器").start();
}
} catch (IOException e) {
System.out.println("连接已中断");
}
}
}
class ServerServiceThread extends Thread{
private Socket socket;
public ServerServiceThread() {
}
public ServerServiceThread(Socket socket,String name) {
super(name);
this.socket = socket;
}
@Override
public void run() {
BufferedReader reader = null;
try {
reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String message;
System.out.println(socket.getLocalSocketAddress() + " 连接成功,时间:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(System.currentTimeMillis()));
while ((message = reader.readLine()) != null) {
System.out.println(socket.getInetAddress().getHostName() + "," + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(System.currentTimeMillis()) + " : " + message);
}
} catch (IOException e) {
System.out.println(socket.getRemoteSocketAddress() + "已下线!");
}
}
}
效果图
(3)线程池优化实现多客户端连接服务器
以下代码仅展示修改部分
class Server {
public static void main(String[] args) {
try {
//1、建立通道
ServerSocket socket = new ServerSocket(7777);
ExecutorService service = new ThreadPoolExecutor(3,10,5, TimeUnit.SECONDS, new LinkedBlockingDeque<>(),new ThreadPoolExecutor.AbortPolicy());
//2、连接通道
while(true){
Socket connection = socket.accept();
service.execute(new ServerServiceThread(connection));
}
} catch (IOException e) {
System.out.println("连接已中断");
}
}
}
class ServerServiceThread implements Runnable{
private Socket socket;
public ServerServiceThread() {
}
public ServerServiceThread(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
BufferedReader reader = null;
try {
reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String message;
System.out.println(socket.getLocalSocketAddress() + " 连接成功,时间:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(System.currentTimeMillis()));
while ((message = reader.readLine()) != null) {
System.out.println(socket.getInetAddress().getHostName() + "," + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(System.currentTimeMillis()) + " : " + message);
}
} catch (IOException e) {
System.out.println(socket.getRemoteSocketAddress() + "已下线!");
}
}
}
效果图
注意:我们可以从效果图里看出,外网IP与内网IP连接内网服务器的速度是不一样的。
外网: 本机——运营商——本机
内网: 本机——本机