IP地址:设备在网络中的地址,是唯一的标识
端口:应用程序在设备中的唯一标识
协议:数据在网络传输中的规则,常见的协议有UDP协议和TCP协议
IP地址形式:
IP常用命令:
特殊IP地址:
InetAddress的使用
InetAddress API如下
名称 | 说明 |
---|---|
public static InteAddress getLocalHost() | 返回本机的地址对象 |
public static InetAddress getByName(String host) | 得到指定主机的IP地址对象,参数是域名或者IP地址 |
public String getHostName() | 获取此IP地址的主机名 |
public String getHostAddress() | 返回IP地址字符串 |
public boolean isReachable(int timeout) | 在指定毫秒内连通该IP地址对应的主机,连通返回true |
public class InetAddressDemo1 { public static void main(String[] args) throws Exception { //1.获取本机地址对象 InetAddress ip1 = InetAddress.getLocalHost(); System.out.println(ip1.getHostName()); System.out.println(ip1.getHostAddress()); //2.获取域名IP对象 InetAddress ip2 = InetAddress.getByName("www.baidu.com"); System.out.println(ip2.getHostName()); System.out.println(ip2.getHostAddress()); //3.获取公网IP对象 InetAddress ip3 = InetAddress.getByName("36.152.44.95"); System.out.println(ip3.getHostName()); System.out.println(ip3.getHostAddress()); //4.判断是否能通:ping 5s之前测试是否可通 System.out.println(ip3.isReachable(5000)); } } output: WIN-OVMGN94BT74 192.168.91.101 www.baidu.com 36.152.44.95 36.152.44.95 36.152.44.95 true
小结
IP地址的代表类是谁?
如何获取本机对象?
何如判断与该IP地址对象是否互通?
端口号
端口类型
通信协议
网络通信协议有两套参考模型
传输层常见的2个协议
TCP协议特点
TCP协议通信场景
UDP协议:
UDP协议通信场景
DatagramPacket:数据包对象(韭菜盘子)
构造器 | 说明 |
---|---|
public DatagramPacket(byte[] buf, int length, InetAddress address, int port) | 创建发送端数据包对象 buf:要发送的内容,字节数组 length:要发送内容的字节长度 address:接收端的IP地址对象 port:接收端的端口号 |
public DatagramPacket(byte[ ] buf, int length) | 创建接收端的数据端对象 buf:用来存储接收的内容 length:能够接收内容的长度 |
DatagramPacket常用方法
方法 | 说明 |
---|---|
public int getLength( ) | 获得实际接收到的字节个数 |
DatagramPacket:发送端和接收端对象(人)
构造器 | 说明 |
---|---|
public DatagramSocket( ) | 创建发送端的Socket对象,系统会随机分配一个端口号。 |
public DatagramSocket(int port) | 创建接收端的Socket对象并指定端口号 |
DatagramSocket类成员方法
方法 | 说明 |
---|---|
public void send(DatagramPacket dp) | 发送数据包 |
public void receive(DatagramPacket p) | 接收数据包 |
/** * 接收端 */ public class ServerTest1 { public static void main(String[] args) { System.out.println("==========服务端启动=========="); try { //1.创建接收端对象,注册端口 DatagramSocket socket = new DatagramSocket(8888); //2.创建一个数据包对象接收数据(韭菜盘子) byte[] buffer = new byte[1024 * 64]; DatagramPacket packet = new DatagramPacket(buffer, buffer.length); //3.接受数据 socket.receive(packet); //4.取出数据 //读取多少倒出多少 int len = packet.getLength(); String rs = new String(buffer, 0, len); System.out.println("收到了:" + rs); //获取发送端的IP和端口 String ip = packet.getSocketAddress().toString(); int port = packet.getPort(); System.out.println("对方IP为:" + ip); System.out.println("对方端口为:" + port); } catch (Exception e) { e.printStackTrace(); } } } output: ==========服务端启动========== 收到了:我是一颗韭菜 对方IP为:/192.168.131.101:52513 对方端口为:52513
/** 发送端:一发一收 */ public class ClientTest1 { public static void main(String[] args) { System.out.println("==========客户端启动=========="); try { //1.创建发送端对象,发送端自带默认端口号(人) DatagramSocket socket = new DatagramSocket(); //2.创建一个数据包对象封装数据(韭菜盘子) /** public DatagramPacket(byte buf[], int length, InetAddress address, int port) 参数一:封装发送的数据(韭菜) 参数二:发送数据的大小 参数三:服务端的IP地址 参数四:服务端的端口 */ byte[] buffer = "我是一颗韭菜".getBytes(); DatagramPacket packet = new DatagramPacket(buffer,buffer.length, InetAddress.getLocalHost(),8888); //3.发送数据 socket.send(packet); socket.close(); } catch (Exception e) { e.printStackTrace(); } } } output: ==========客户端启动==========
案例:使用UDP通信实现:多发多收消息
需求:
分析:
/** 客户端:多发多收 */ public class CilentDemo1 { public static void main(String[] args) throws Exception { System.out.println("客户端启动,开始发送数据:~~~~"); //1.创建发送端对象 DatagramSocket socket = new DatagramSocket(); Scanner sc = new Scanner(System.in); while (true) { System.out.println("请输入:"); String msg = sc.nextLine(); if ("exit".equals(msg)){ System.out.println("离线成功!"); socket.close(); break; } //2.创建发送端数据对象 byte[] buffer = msg.getBytes(); DatagramPacket packet = new DatagramPacket(buffer,buffer.length, InetAddress.getLocalHost(),8888); //3.发送数据 socket.send(packet); } } } output: 客户端启动,开始发送数据:~~~~ 请输入: 我是第二个UDP客户端 请输入: exit 离线成功!
public class ServerDemo1 { public static void main(String[] args) throws Exception { System.out.println("服务端启动,开始接收数据:~~~~"); //1.创建客户端对象 DatagramSocket socket = new DatagramSocket(8888); while (true) { //2.创建接收数据包对象 byte[] buffer = new byte[1024 * 6]; DatagramPacket packet = new DatagramPacket(buffer, buffer.length); //3.接收数据 socket.receive(packet); int len = packet.getLength(); String rs = new String(buffer, 0, len); System.out.println("接收到来自 ip:" + packet.getSocketAddress() + ",端口号:" + packet.getPort() + "的消息:" + rs); } } } output: 服务端启动,开始接收数据:~~~~ 接收到来自 ip:/192.168.31.101:57151,端口号:57151的消息:halo啊 接收到来自 ip:/192.168.31.101:59056,端口号:59056的消息:你好 接收到来自 ip:/192.168.31.101:63080,端口号:63080的消息:我是第二个UDP客户端 接收到来自 ip:/192.168.31.101:64055,端口号:64055的消息:我是第三个
UDP的三种通信方式
UDP如何实现广播
public class Client { public static void main(String[] args) throws Exception { System.out.println("=======客户端启动了====="); while (true) { DatagramSocket socket = new DatagramSocket(); Scanner sc = new Scanner(System.in); System.out.println("请输入:"); String msg = sc.nextLine(); byte[] buffer = msg.getBytes(); if ("exit".equals(msg)) { System.out.println("离线成功!"); socket.close(); break; } DatagramPacket packet = new DatagramPacket(buffer, buffer.length, InetAddress.getByName("255.255.255.255"), 9999); socket.send(packet); } } } output: =======客户端启动了===== 请输入: 我是一个广播消息 请输入:
public class Server { public static void main(String[] args) throws Exception { System.out.println("====服务端启动了======"); DatagramSocket socket = new DatagramSocket(9999); while (true) { byte[] buffer = new byte[1024 * 64]; DatagramPacket packet = new DatagramPacket(buffer, buffer.length); socket.receive(packet); int len = packet.getLength(); String rs = new String(buffer, 0, len); System.out.println("接收到来自 ip:" + packet.getSocketAddress() + ",端口号:" + packet.getPort() + "的消息:" + rs); } } } output: ====服务端启动了====== 接收到来自 ip:/192.168.31.101:55798,端口号:55798的消息:我是一个广播消息
UDP如何实现组播
public class ClientDemo { public static void main(String[] args)throws Exception { System.out.println("客户端启动,开始发送数据:~~~~"); //1.创建发送端对象 DatagramSocket socket = new DatagramSocket(); Scanner sc = new Scanner(System.in); while (true) { System.out.println("请输入:"); String msg = sc.nextLine(); if ("exit".equals(msg)){ System.out.println("离线成功!"); socket.close(); break; } //2.创建发送端数据对象 byte[] buffer = msg.getBytes(); DatagramPacket packet = new DatagramPacket(buffer,buffer.length, InetAddress.getByName("224.0.0.1"),9999); //3.发送数据 socket.send(packet); } } } output: 客户端启动,开始发送数据:~~~~ 请输入: 我是一个新的组播消息 请输入: exit 离线成功!
public static void main(String[] args)throws Exception { System.out.println("服务端启动,开始接收数据:~~~~"); //1.创建接收端对象,注册端口 MulticastSocket socket = new MulticastSocket(9999); //2.把当前接收端加入到一个组播中去,绑定对于的组播消息的组播IP // socket.joinGroup(InetAddress.getByName("224.0.0.1")); socket.joinGroup(new InetSocketAddress(InetAddress.getByName("224.0.0.1"),9999), NetworkInterface.getByInetAddress(InetAddress.getLocalHost())); while (true) { //2.创建接收数据包对象 byte[] buffer = new byte[1024 * 6]; DatagramPacket packet = new DatagramPacket(buffer, buffer.length); //3.接收数据 socket.receive(packet); int len = packet.getLength(); String rs = new String(buffer, 0, len); System.out.println("接收到来自 ip:" + packet.getSocketAddress() + ",端口号:" + packet.getPort() + "的消息:" + rs); } } } output: 服务端启动,开始接收数据:~~~~ 接收到来自 ip:/192.168.31.101:63641,端口号:63641的消息:我是一个新的组播消息
Socket
构造器 | 说明 |
---|---|
public Socket(String host, int port ) | 创建发送端的Socket对象与服务端连接,参数为服务端程序的IP和端口 |
Socket类成员方法、
方法 | 说明 |
---|---|
OutputStream getOutputStream( ) | 获得字节输出流对象 |
InputStream getInputStream( ) | 获得字节输入流对象 |
客户端发送消息
需求:客户端实现步骤
/** Socket 的客户端开发 */ public class ClientDemo1 { public static void main(String[] args) { System.out.println("客户端启动,开始发送数据:~~~~"); try { //1.创建Socket通信管道请求与服务器进行连接 //public Socket(String host ,int port) //参数一:服务器的IP地址 //参数二:服务器的端口号: Socket socket = new Socket("127.0.0.1",7777); //2.从socket通信管道中得到一个字节输出流,负责发送数据 OutputStream os = socket.getOutputStream(); //3.包装字节输出流为打印流 PrintStream ps = new PrintStream(os); //4.发送消息 ps.println("我是TCP的客户端,我已经与你建立连接!"); ps.flush(); } catch (Exception e) { e.printStackTrace(); } } } output: 客户端启动,开始发送数据:~~~~
ServerSocket(服务端)
构造器 | 说明 |
---|---|
public ServerSocket(int port) | 注册服务端端口 |
ServerSocket类成员方法
方法 | 说明 |
---|---|
public Socket accpet() | 等待接收客户端的Socket通信连接 连接成功返回Socket对象与客户端建立端到端通信 |
public class ServerDemo1 { public static void main(String[] args) { System.out.println("服务端启动,开始接收数据:~~~~"); try { //1.注册端口 ServerSocket serverSocket = new ServerSocket(7777); //2.必须调用accept方法,等待接收客户端socket的连接请求,建立Socket通信管道 Socket socket = serverSocket.accept(); //3.从Socket管道中得到一个字节输入流 InputStream is = socket.getInputStream(); //4.把字节输入流包装成一个字符缓冲输入流进行消息的接收 BufferedReader br = new BufferedReader(new InputStreamReader(is)); //5.按照行读取信息 String msg; if ((msg = br.readLine()) != null) { System.out.println(socket.getRemoteSocketAddress() + "说了:" + msg); } } catch (Exception e) { e.printStackTrace(); } } } output: 服务端启动,开始接收数据:~~~~ /127.0.0.1:50871说了:我是TCP的客户端,我已经与你建立连接!
小结
1、TCP通信服务端用的代表雷?
2、 TCP通信的基本原理?
需求:使用TCP通信方式实现:多发多收消息。
具体要求:
/** 客户端,重复发 */ public class ClientDemo2 { public static void main(String[] args) { System.out.println("===========客户端启动了==========="); try { //1.创建客户端socket管道 Socket socket = new Socket("127.0.0.1",12345); //2.创建一个字节输入流 OutputStream os = socket.getOutputStream(); //3.包装成打印流 PrintStream ps = new PrintStream(os); Scanner sc = new Scanner(System.in); String msg; while (true){ System.out.println("请输入:"); msg = sc.nextLine(); //4.发送消息 ps.println(msg); ps.flush(); } } catch (Exception e) { e.printStackTrace(); } } } output: ===========客户端启动了=========== 请输入: 现在是十二点 请输入: 我来研究TCP客户端重复发了 请输入: exit 请输入:
public class ServerDemo2 { public static void main(String[] args) { System.out.println("===========服务端启动了==========="); try { //1.注册端口 ServerSocket serverSocket = new ServerSocket(12345); //2.调用accept方法,等到客户端的socket请求,建立一个socket通信管道 Socket socket = serverSocket.accept(); //3.从socket管道中得到一个字节输入流 InputStream is = socket.getInputStream(); //包装成字符输入流,进行消息的读取 BufferedReader br = new BufferedReader(new InputStreamReader(is)); String msg; while (true) { if ((msg = br.readLine())!=null){ System.out.println(socket.getRemoteSocketAddress()+"说了:"+msg); } } } catch (Exception e) { e.printStackTrace(); } } } output: ===========服务端启动了=========== /127.0.0.1:51561说了:现在是十二点 /127.0.0.1:51561说了:我来研究TCP客户端重复发了 /127.0.0.1:51561说了:exit
上面代码实现了多发多收,但是不可以同时接收多个客户端的消息,因为服务端现在只有一个线程,只能与一个客户端进行通信。
引入多线程处理服务端可以与多个客户端之间进行通信
public class ServerReaderThread extends Thread { private Socket socket; public ServerReaderThread(Socket socket) { this.socket = socket; } @Override public void run() { try { InputStream is = socket.getInputStream(); BufferedReader br = new BufferedReader(new InputStreamReader(is)); String msg; while ((msg = br.readLine()) != null) { System.out.println(socket.getRemoteSocketAddress() + "说了:" + msg); } } catch (Exception e) { System.out.println(socket.getRemoteSocketAddress()+"下线了!"); } } }
public class ServerDemo4 { public static void main(String[] args) { System.out.println("============服务端启动了============="); try { //1.注册端口 ServerSocket serverSocket = new ServerSocket(6667); //a.定义一个死循环来不断接收客户端的socket请求 while (true) { //2.每接收到一个客户端的socket管道,交给一个子线程去负责 Socket socket = serverSocket.accept(); System.out.println(socket.getRemoteSocketAddress() + "上线了!"); //3.开始创建独立线程处理socket new ServerReaderThread(socket).start(); } } catch (Exception e) { e.printStackTrace(); } } }
/** 多发多收 */ public class ClientDemo3 { public static void main(String[] args) { System.out.println("=========客户端启动了========="); try { //1.创建Socket管道 Socket socket = new Socket("127.0.0.1",6667); OutputStream os = socket.getOutputStream(); PrintStream ps = new PrintStream(os); Scanner sc = new Scanner(System.in); String msg; while (true){ System.out.println("请输入:"); msg = sc.nextLine(); ps.println(msg); ps.flush(); } } catch (Exception e) { e.printStackTrace(); } } }
public class ServerReaderRunnable implements Runnable { private Socket socket; public ServerReaderRunnable(Socket socket) { this.socket = socket; } @Override public void run() { try { InputStream is = socket.getInputStream(); BufferedReader br = new BufferedReader(new InputStreamReader(is)); String msg; while ((msg = br.readLine()) != null) { System.out.println(socket.getRemoteSocketAddress() + " 说了:" + msg); } } catch (IOException e) { System.out.println(socket.getRemoteSocketAddress() + " 下线了!"); } } }
public class ClientDemo4 { public static void main(String[] args) { System.out.println("=======客户端启动了========="); try { Socket socket = new Socket("127.0.0.1",5555); OutputStream os = socket.getOutputStream(); PrintStream ps = new PrintStream(os); Scanner sc = new Scanner(System.in); String msg; while (true){ System.out.println("请输入:"); msg = sc.nextLine(); ps.println(msg); ps.flush(); } } catch (Exception e) { e.printStackTrace(); } } } output: =======客户端启动了========= 请输入: 我是第二个客户端,我也拽住了一个核心线程 请输入: =======客户端启动了========= 请输入: 我是第三个客户端,我也拽住了一条核心线程 请输入:
public class ServerDemo4 { private static final ExecutorService pool = new ThreadPoolExecutor(3,5,6 , TimeUnit.SECONDS,new ArrayBlockingQueue<>(2) ,Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy()); public static void main(String[] args) { System.out.println("========服务端启动了========"); try { //注册端口 ServerSocket serverSocket = new ServerSocket(5555); //定义一个死循环由主线程负责不断地接收客户端的Socket管道连接 while (true) { //每接收到一个客户端的Socket管道 Socket socket = serverSocket.accept(); System.out.println(socket.getRemoteSocketAddress()+" 上线了!"); //任务对象负责读取消息 Runnable target = new ServerReaderRunnable(socket); pool.execute(target); } } catch (Exception e) { e.printStackTrace(); } } } output: ========服务端启动了======== /127.0.0.1:51798 上线了! /127.0.0.1:51798 说了:我是第一个客户端,我拽住了一条核心线程 /127.0.0.1:51810 上线了! /127.0.0.1:51810 说了:我是第二个客户端,我也拽住了一个核心线程 /127.0.0.1:51834 上线了! /127.0.0.1:51834 说了:我是第三个客户端,我也拽住了一条核心线程 /127.0.0.1:51840 上线了! /127.0.0.1:51844 上线了! /127.0.0.1:51850 上线了! /127.0.0.1:51850 说了:我是第六个客户端 /127.0.0.1:51853 上线了! /127.0.0.1:51853 说了:我是第七个客户端 /127.0.0.1:51858 上线了! java.util.concurrent.RejectedExecutionException: Task com.csl.d8_socket4.ServerReaderRunnable@5b6f7412 rejected from java.util.concurrent.ThreadPoolExecutor@27973e9b[Running, pool size = 5, active threads = 5, queued tasks = 2, completed tasks = 0] /127.0.0.1:51853 下线了! /127.0.0.1:51840 说了:我是第四个客户端 /127.0.0.1:51850 下线了! /127.0.0.1:51844 说了:我是第五个客户端 /127.0.0.1:51844 下线了!
public class ClientDemo5 { public static void main(String[] args) { System.out.println("==========客户端启动了========="); try { Socket socket = new Socket("127.0.0.1",5555); //创建一个独立的线程负责这个客户端的读消息(服务器随时可能发信息过来) new ClientReaderThread(socket).start(); OutputStream os = socket.getOutputStream(); PrintStream ps = new PrintStream(os); Scanner sc = new Scanner(System.in); while (true){ String msg = sc.nextLine(); ps.println(msg); ps.flush(); } } catch (Exception e) { e.printStackTrace(); } } } public class ClientReaderThread extends Thread { private Socket socket; public ClientReaderThread(Socket socket) { this.socket = socket; } @Override public void run() { try { InputStream is = socket.getInputStream(); BufferedReader br = new BufferedReader(new InputStreamReader(is)); String line; while ((line = br.readLine()) != null) { System.out.println("收到消息:" + line); } } catch (Exception e) { System.out.println("你已被服务器强制下线"); } } } output: ==========客户端启动了========= 我发送了一个即时通信消息 收到消息:我发送了一个即时通信消息
public class ServerDemo5 { private static final ExecutorService pool = new ThreadPoolExecutor(3, 5, 6, TimeUnit.SECONDS , new ArrayBlockingQueue<>(2), Executors.defaultThreadFactory() , new ThreadPoolExecutor.AbortPolicy()); public static List<Socket> allOnlineSockets = new ArrayList<>(); public static void main(String[] args) { System.out.println("==========服务端启动了========"); try { ServerSocket serverSocket = new ServerSocket(5555); while (true) { Socket socket = serverSocket.accept(); System.out.println(socket.getRemoteSocketAddress() + "上线了!"); allOnlineSockets.add(socket);//上线完成 pool.execute(new ServerReaderRunnable2(socket)); } } catch (Exception e) { e.printStackTrace(); } } } public class ServerReaderRunnable2 implements Runnable{ private Socket socket; public ServerReaderRunnable2(Socket socket){ this.socket = socket; } @Override public void run() { try { InputStream is = socket.getInputStream(); BufferedReader br = new BufferedReader(new InputStreamReader(is)); String line; while ((line=br.readLine())!=null){ System.out.println(socket.getRemoteSocketAddress()+"说了:"+line); //端口转发给全部客户端Socket管道 sedMsgToAll(line); } } catch (Exception e) { System.out.println(socket.getRemoteSocketAddress()+"下线了!"); ServerDemo5.allOnlineSockets.remove(socket); } } private void sedMsgToAll(String msg) throws Exception { for (Socket socket : ServerDemo5.allOnlineSockets) { PrintStream ps = new PrintStream(socket.getOutputStream()); ps.println(msg); ps.flush(); } } } output: ==========服务端启动了======== /127.0.0.1:52109上线了! /127.0.0.1:52109说了:我发送了一个即时通信消息 /127.0.0.1:52109下线了!
注意:服务器必须给浏览器相应HTTP协议格式的数据,否则浏览器不识别。
HTTP响应数据的协议格式:就是给浏览器显示的网页信息
public class BSserverDemo { public static void main(String[] args) { try { //1.注册端口 ServerSocket ss = new ServerSocket(8080); //2.创建一个循环接收多个客户端的请求 while (true){ Socket socket = ss.accept(); //3.交给一个独立的线程来处理 new ServerReaderThread(socket).start(); } } catch (Exception e) { e.printStackTrace(); } } } class ServerReaderThread extends Thread{ private Socket socket; public ServerReaderThread(Socket socket){ this.socket = socket; } @Override public void run() { try { //浏览器已经与本线程建立了Socket管道 //响应消息给浏览器显示 PrintStream ps = new PrintStream(socket.getOutputStream()); //必须响应HTTP协议格式数据,否则浏览器不认识消息 ps.println("HTTP/1.1 200 OK");//协议类型和版本 响应成功的消息! ps.println("Content-Type:text/html;charset-UTF-8");//响应的类型: 文本/网页 ps.println();//必须发送一个空行 ps.println("<span style='color:red;font-size:90px'>《独孤患者》</span>"); ps.close(); } catch (Exception e) { e.printStackTrace(); } } }
使用线程池优化
public class BSserverDemo { //使用静态变量记住一个线程池对象 private static ExecutorService pool = new ThreadPoolExecutor(3,5,6, TimeUnit.SECONDS, new ArrayBlockingQueue<>(2),Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy()); public static void main(String[] args) { try { //1.注册端口 ServerSocket ss = new ServerSocket(8080); //2.创建一个循环接收多个客户端的请求 while (true){ Socket socket = ss.accept(); //3.交给一个独立的线程来处理 pool.execute(new ServerRunnableThread(socket)); } } catch (Exception e) { e.printStackTrace(); } } } class ServerRunnableThread implements Runnable{ private Socket socket; public ServerRunnableThread(Socket socket){ this.socket = socket; } @Override public void run() { try { //浏览器已经与本线程建立了Socket管道 //响应消息给浏览器显示 PrintStream ps = new PrintStream(socket.getOutputStream()); //必须响应HTTP协议格式数据,否则浏览器不认识消息 ps.println("HTTP/1.1 200 OK");//协议类型和版本 响应成功的消息! ps.println("Content-Type:text/html;charset-UTF-8");//响应的类型: 文本/网页 ps.println();//必须发送一个空行 ps.println("<span style='color:red;font-size:90px'>《独孤患者》</span>"); ps.close(); } catch (Exception e) { e.printStackTrace(); } } }