Java-进阶篇【网络编程】---09
1:网络编程概叙
什么是网络编程?:网络编程可以让程序与网络上的其他设备中的程序进行数据交互。
2:网络通信三要素 之 IP地址
实现网络编程关键的三要素
IP地址:设备在网络中的地址,是唯一的标识。
端口:应用程序在设备中唯一的标识。
协议: 数据在网络中传输的规则,常见的协议有UDP协议和TCP协议
IP地址形式: 公网地址、和私有地址(局域网使用)。 192.168. 开头的就是常见的局域网地址,范围即为192.168.0.0--192.168.255.255,专门为组织机构内部使用。 IP常用命令: ipconfig:查看本机IP地址 ping IP地址:检查网络是否连通 特殊IP地址: 本机IP: 127.0.0.1或者 localhost:称为回送地址也可称本地回环地址,只会寻找当前所在本机。 说说网络通信至少需要几个要素 IP、端口、协议。 IP地址是做什么的,具体有几种 定位网络上的设备的,有IPv4 , IPv6. 如何查看本机IP地址,如何看是否与对方互通 ipcofig ping 192.168.10.23 本机IP是谁? 127.0.0.1或者是localhost
3:IP地址操作类 InetAddress
InetAddress 的使用:此类表示Internet协议(IP)地址。 InetAddress API如下 public static InetAddress getLocalHost() 返回本主机的地址对象 public static InetAddress getByName(String host) 得到指定主机的IP地址对象,参数是域名或者IP地址 public String getHostName() 获取此IP地址的主机名【获取主机名】 public String getHostAddress() 返回IP地址字符串 public boolean isReachable(int timeout) 在指定毫秒内连通该IP地址对应的主机,连通返回true IP地址的代表类是谁? InetAddress类 如何获取本机IP对象 public static InetAddress getLocalHost() 如何判断与该IP地址对象是否互通? public boolean isReachable(int timeout)
package com.itheima.d1_inetAddress; import java.net.InetAddress; /** 目标:InetAddress类概述(了解) 一个该类的对象就代表一个IP地址对象。 InetAddress类成员方法: static InetAddress getLocalHost() * 获得本地主机IP地址对象。 static InetAddress getByName(String host) * 根据IP地址字符串或主机名获得对应的IP地址对象。 String getHostName() * 获得主机名。 String getHostAddress() * 获得IP地址字符串。 */ public class InetAddressDemo01 { 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("112.80.248.76"); System.out.println(ip3.getHostName()); System.out.println(ip3.getHostAddress()); // 4.判断是否能通: ping 5s之内测试是否可通【超时时间】 System.out.println(ip3.isReachable(5000)); } }
4:网络通信三要素 之 端口号
端口号:标识正在计算机设备上运行的进程(程序),被规定为一个 16 位的二进制,范围是 0~65535 端口类型 周知端口:0~1023,被预先定义的知名应用占用(如:HTTP占用 80,FTP占用21) 注册端口:1024~49151,分配给用户进程或某些应用程序。(如:Tomcat占 用8080,MySQL占用3306) 动态端口:49152到65535,之所以称为动态端口,是因为它 一般不固定分配某种进程,而是动态分配。 注意:我们自己开发的程序选择注册端口,且一个设备中不能出现两个程序的端口号一样,否则出错。
5:网络通信三要素 之 协议【通信规则】
通信协议:连接和通信数据的规则被称为网络通信协议
传输层的2个常见协议
TCP(Transmission Control Protocol) :传输控制协议
UDP(User Datagram Protocol):用户数据报协议
TCP协议特点
使用TCP协议,必须双方先建立连接,它是一种面向连接的可靠通信协议。
传输前,采用“三次握手”方式建立连接,所以是可靠的 。
在连接中可进行大数据量的传输 。
连接、发送数据都需要确认,且传输完毕后,还需释放已建立的连接,通信效率较低。
TCP协议通信场景:对信息安全要求较高的场景,例如:文件下载、金融等数据通信
UDP协议
UDP是一种无连接、不可靠传输的协议。
将数据源IP、目的地IP和端口封装成数据包,不需要建立连接
每个数据包的大小限制在64KB内
发送不管对方是否准备好,接收方收到也不确认,故是不可靠的
可以广播发送 ,发送数据结束时无需释放资源,开销小,速度快。
UDP协议通信场景:语音通话,视频会话等。
1、通信协议是什么? 计算机网络中,连接和通信数据的规则被称为网络通信协议。 2、TCP通信协议的特点是什么样的? 它是一种面向连接的可靠通信协议。 传输前,采用“三次握手”方式建立连接,点对点的通信,所以可靠。 在连接中可进行大数据量的传输。 通信效率较低。 3、UDP协议的特点是什么 用户数据报协议(User Datagram Protocol) UDP是面向无连接,不可靠传输的通信协议。 速度快,有大小限制一次最多发送64K,数据不安全,易丢失数据。
6:UDP 通信
UDP协议的特点: UDP是一种无连接、不可靠传输的协议。 将数据源IP、目的地IP和端口以及数据封装成数据包,大小限制在64KB内,直接发送出去即可。 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() 获得实际接收到的字节个数 DatagramSocket:发送端和接收端对象(人) 构造器: public DatagramSocket() 创建发送端的Socket对象,系统会随机分配一个端口号。 public DatagramSocket(int port) 创建接收端的Socket对象并指定端口号 DatagramSocket类成员方法 public void send(DatagramPacket dp) 发送数据包 public void receive(DatagramPacket p) 接收数据包
package com.itheima.d2_udp1; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; import java.net.SocketException; /** 发送端 一发 一收 */ public class ClientDemo1 { public static void main(String[] args) throws Exception { System.out.println("=====客户端启动======"); // 1、创建发送端对象:发送端自带默认的端口号(人)【可以不写端口号】 DatagramSocket socket = new DatagramSocket(6666); // 2、创建一个数据包对象封装数据(韭菜盘子) 字节数组 bytes /** 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、发送数据出去 send socket.send(packet); socket.close(); } }
package com.itheima.d2_udp1; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.SocketException; /** 接收端 */ public class ServerDemo2 { public static void main(String[] args) throws Exception { System.out.println("=====服务端启动======"); // 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(); System.out.println("对方地址:" + ip); int port = packet.getPort(); System.out.println("对方端口:" + port); socket.close(); } }
UDP发送端和接收端的对象是哪个? public DatagramSocket():创建发送端的Socket对象 public DatagramSocket(int port):创建接收端的Socket对象 数据包对象是哪个? DatagramPacket 如何发送、接收数据包 使用DatagramSocket的如下方法: public void send(DatagramPacket dp): 发送数据包 public void receive(DatagramPacket dp) :接收数据包
UPD通信多发多收
package com.itheima.d3_udp2; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; import java.net.MulticastSocket; import java.util.Scanner; /** 发送端 多发 多收 */ public class ClientDemo1 { public static void main(String[] args) throws Exception { System.out.println("=====客户端启动======"); // 1、创建发送端对象:发送端自带默认的端口号(人) DatagramSocket socket = new DatagramSocket(7777); 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); } } }
package com.itheima.d3_udp2; import java.net.DatagramPacket; import java.net.DatagramSocket; /** 接收端 */ public class ServerDemo2 { public static void main(String[] args) throws Exception { System.out.println("=====服务端启动======"); // 1、创建接收端对象:注册端口(人) DatagramSocket socket = new DatagramSocket(8888); // 2、创建一个数据包对象接收数据(韭菜盘子) byte[] buffer = new byte[1024 * 64]; DatagramPacket packet = new DatagramPacket(buffer, buffer.length); while (true) { // 3、等待接收数据。 socket.receive(packet); // 4、取出数据即可 // 读取多少倒出多少 int len = packet.getLength(); String rs = new String(buffer,0, len); System.out.println("收到了来自:" + packet.getAddress() +", 对方端口是" + packet.getPort() +"的消息:" + rs); } } }
8:UDP通信-广播、组播
UDP的三种通信方式
单播:单台主机与单台主机之间的通信。
广播:当前主机与所在网络中的所有主机通信。
组播:当前主机与选定的一组主机的通信
UDP如何实现广播:使用广播地址:255.255.255.255 具体操作: 发送端发送的数据包的目的地写的是 广播地址、且指定端口。 (255.255.255.255 , 9999) 本机所在网段的其他主机的程序只要注册对应端口就可以收到消息了。(9999)
package com.itheima.d4_upd3; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; import java.util.Scanner; /** 广播 */ public class ClientDemo1 { 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(); // 注意:只要目的地IP是 255.255.255.255 这个消息将以广播的形式对外发送 DatagramPacket packet = new DatagramPacket( buffer, buffer.length, InetAddress.getByName("255.255.255.255") , 9999);// 3、发送数据出去 socket.send(packet); } } }
UDP如何实现组播:使用组播地址:224.0.0.0 ~ 239.255.255.255 具体操作: 发送端的数据包的目的地是组播IP (例如:224.0.1.1, 端口:9999) 接收端必须绑定该组播IP(224.0.1.1),端口还要注册发送端的目的端口9999 ,这样即可接收该组播消息。 DatagramSocket的子类MulticastSocket可以在接收端绑定组播IP。
package com.itheima.d4_upd3; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; import java.util.Scanner; /** 组播 */ public class ClientDemo1 { 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.1.1") , 9999); // 3、发送数据出去 socket.send(packet); } } }
package com.itheima.d4_upd3; import java.net.*; /** 接收端 */ public class ServerDemo3 { public static void main(String[] args) throws Exception { System.out.println("=====服务端启动======"); // 1、创建接收端对象:注册端口(人) MulticastSocket socket = new MulticastSocket(9999); // 注意:绑定组播地址(加群)【当前接收端加入到一个组播中去】 socket.joinGroup(new InetSocketAddress(InetAddress.getByName("224.0.1.1") , 9898), NetworkInterface.getByInetAddress(InetAddress.getLocalHost())); // 2、创建一个数据包对象接收数据(韭菜盘子) byte[] buffer = new byte[1024 * 64]; DatagramPacket packet = new DatagramPacket(buffer, buffer.length); while (true) { // 3、等待接收数据。 socket.receive(packet); // 4、取出数据即可 // 读取多少倒出多少 int len = packet.getLength(); String rs = new String(buffer,0, len); System.out.println("收到了来自:" + packet.getAddress() +", 对方端口是" + packet.getPort() +"的消息:" + rs); } } }
如何实现广播,具体怎么操作? 发送端目的IP使用广播IP: 255.255.255.255 9999。 所在网段的其他主机对应了端口(9999)即可接收消息。 如何实现组播,具体怎么操作? 发送端目的IP使用组播IP,且指定端口。 所在网段的其他主机注册了该组播IP和对应端口即可接收消息
9:TCP通信 概叙
TCP协议回顾:
TCP是一种面向连接,安全、可靠的传输数据的协议
传输前,采用“三次握手”方式,点对点通信,是可靠的
在连接中可进行大数据量的传输
Socket 构造器: public Socket(String host , int port) 创建发送端的Socket对象与服务端连接,参数为服务端程序的ip和端口。 Socket类成员方法 OutputStream getOutputStream() 获得字节输出流对象 InputStream getInputStream() 获得字节输入流对象
ServerSocket(服务端) 构造器 public ServerSocket(int port) 注册服务端端口 ServerSocket类成员方法 public Socket accept() 等待接收客户端的Socket通信连接,连接成功返回Socket对象与客户端建立端到端通信
package com.itheima.d5_socket1; import java.io.IOException; import java.io.OutputStream; import java.io.PrintStream; import java.net.Socket; /**
客户端 目标:完成Socket网络编程入门案例的客户端开发,实现1发1收。 */ public class ClientDemo1 { public static void main(String[] args) { try { System.out.println("====客户端启动==="); // 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(); // 关闭资源。 // socket.close(); } catch (Exception e) { e.printStackTrace(); } } }
package com.itheima.d5_socket1; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.ServerSocket; import java.net.Socket; /**
服务端 目标:开发Socket网络编程入门代码的服务端,实现接收消息 */ public class ServerDemo2 { public static void main(String[] args) { try { System.out.println("===服务端启动成功==="); // 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(); } } }
TCP通信的客户端的代表类是谁? Socket类 public Socket(String host , int port) TCP通信如何使用Socket管道发送、接收数据。 OutputStream getOutputStream():获得字节输出流对象(发) InputStream getInputStream():获得字节输入流对象(收)
TCP通信服务端用的代表类?
ServerSocket类,注册端口。
调用accept()方法阻塞等待接收客户端连接。得到Socket对象。
TCP通信的基本原理?
客户端怎么发,服务端就应该怎么收。
客户端如果没有消息,服务端会进入阻塞等待。
Socket一方关闭或者出现异常、对方Socket也会失效或者出错
10:TCP 通信-多发多收消息
本案例实现了多发多收,那么是否可以同时接收多个客户端的消息?
不可以的。
因为服务端现在只有一个线程,只能与一个客户端进行通信。
本次多发多收是如何实现的
客户端使用循环反复地发送消息。
服务端使用循环反复地接收消息。
现在服务端为什么不可以同时接收多个客户端的消息。
目前服务端是单线程的,每次只能处理一个客户端的消息。
package com.itheima.d6_socket2; import java.io.OutputStream; import java.io.PrintStream; import java.net.Socket; import java.util.Scanner; /** 目标:实现多发和多收 */ public class ClientDemo1 { public static void main(String[] args) { try { System.out.println("====客户端启动==="); // 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); Scanner sc = new Scanner(System.in); while (true) { System.out.println("请说:"); String msg = sc.nextLine(); // 4、发送消息 ps.println(msg); ps.flush(); //每次写数据都需要刷新一下,把数据写入内存 } // 关闭资源。 // socket.close(); } catch (Exception e) { e.printStackTrace(); } } }
package com.itheima.d6_socket2; import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; import java.net.ServerSocket; import java.net.Socket; /** 目标:开发Socket网络编程入门代码的服务端,实现接收消息 */ public class ServerDemo2 { public static void main(String[] args) { try { System.out.println("===服务端启动成功==="); // 1、注册端口 ServerSocket serverSocket = new ServerSocket(7777); while (true) { // 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; while ((msg = br.readLine()) != null){ System.out.println(socket.getRemoteSocketAddress() + "说了:: " + msg); } } } catch (Exception e) { e.printStackTrace(); } } }
11:TCP 通信- 同时接受多个客户端消息[重点]
1、之前我们的通信是否可以同时与多个客户端通信,为什么? 不可以的 单线程每次只能处理一个客户端的Socket通信 2、如何才可以让服务端可以处理多个客户端的通信需求? 引入多线程。
本次是如何实现服务端接收多个客户端的消息的。
主线程定义了循环负责接收客户端Socket管道连接
每接收到一个Socket通信管道后分配一个独立的线程负责处理它
package com.itheima.d7_socket3; import java.io.OutputStream; import java.io.PrintStream; import java.net.Socket; import java.util.Scanner; /** 目标:实现服务端可以同时处理多个客户端的消息。 */ public class ClientDemo1 { public static void main(String[] args) { try { System.out.println("====客户端启动==="); // 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); Scanner sc = new Scanner(System.in); while (true) { System.out.println("请说:"); String msg = sc.nextLine(); // 4、发送消息 ps.println(msg); ps.flush(); } // 关闭资源。 // socket.close(); } catch (Exception e) { e.printStackTrace(); } } }
package com.itheima.d7_socket3; import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; import java.net.ServerSocket; import java.net.Socket; /** 目标:实现服务端可以同时处理多个客户端的消息。 */ public class ServerDemo2 { public static void main(String[] args) { try { System.out.println("===服务端启动成功==="); // 1、注册端口 ServerSocket serverSocket = new ServerSocket(7777); // 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(); } } }
package com.itheima.d7_socket3; import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; import java.net.Socket; public class ServerReaderThread extends Thread{ private Socket socket; public ServerReaderThread(Socket socket){ this.socket = socket; } @Override public void run() { try { // 3、从socket通信管道中得到一个字节输入流 InputStream is = socket.getInputStream(); // 4、把字节输入流包装成缓冲字符输入流进行消息的接收 BufferedReader br = new BufferedReader(new InputStreamReader(is)); // 5、按照行读取消息 String msg; while ((msg = br.readLine()) != null){ System.out.println(socket.getRemoteSocketAddress() + "说了:: " + msg); } } catch (Exception e) { System.out.println(socket.getRemoteSocketAddress() + "下线了!!!"); } } }
12:TCP 通信-使用线程池优化
目前的通信架构存在什么问题? 客户端与服务端的线程模型是: N-N的关系。 客户端并发越多,系统瘫痪的越快。
本次使用线程池的优势在哪里?
服务端可以复用线程处理多个客户端,可以避免系统瘫痪。
适合客户端通信时长较短的场景【过长老是占用线程不放,无法释放资源】
package com.itheima.d8_socket4; import java.io.OutputStream; import java.io.PrintStream; import java.net.Socket; import java.util.Scanner; /** 拓展:使用线程池优化:实现通信。 */ public class ClientDemo1 { public static void main(String[] args) { try { System.out.println("====客户端启动==="); // 1、创建Socket通信管道请求有服务端的连接 // public Socket(String host, int port) // 参数一:服务端的IP地址 // 参数二:服务端的端口 Socket socket = new Socket("127.0.0.1", 6666); // 2、从socket通信管道中得到一个字节输出流 负责发送数据 OutputStream os = socket.getOutputStream(); // 3、把低级的字节流包装成打印流 PrintStream ps = new PrintStream(os); Scanner sc = new Scanner(System.in); while (true) { System.out.println("请说:"); String msg = sc.nextLine(); // 4、发送消息 ps.println(msg); ps.flush(); } // 关闭资源。 // socket.close(); } catch (Exception e) { e.printStackTrace(); } } }
package com.itheima.d8_socket4; import com.itheima.d7_socket3.ServerReaderThread; import java.net.ServerSocket; import java.net.Socket; import java.util.concurrent.*; /** 目标:实现服务端可以同时处理多个客户端的消息。 */ public class ServerDemo2 { // 使用静态变量记住一个线程池对象 private static ExecutorService pool = new ThreadPoolExecutor(300, 1500, 6, TimeUnit.SECONDS, new ArrayBlockingQueue<>(2) , Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy()); public static void main(String[] args) { try { System.out.println("===服务端启动成功==="); // 1、注册端口 ServerSocket serverSocket = new ServerSocket(6666); // a.定义一个死循环由主线程负责不断的接收客户端的Socket管道连接。 while (true) { // 2、每接收到一个客户端的Socket管道, Socket socket = serverSocket.accept(); System.out.println(socket.getRemoteSocketAddress()+ "它来了,上线了!"); // 任务对象负责读取消息。 【任务交给线程池】【每次接收socket 对象封装成任务管道对象,交给线程池处理】 Runnable target = new ServerReaderRunnable(socket); pool.execute(target); } } catch (Exception e) { e.printStackTrace(); } } }
package com.itheima.d8_socket4; import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; import java.net.Socket; public class ServerReaderRunnable implements Runnable{ private Socket socket; public ServerReaderRunnable(Socket socket){ this.socket = socket; } @Override public void run() { try { // 3、从socket通信管道中得到一个字节输入流 InputStream is = socket.getInputStream(); // 4、把字节输入流包装成缓冲字符输入流进行消息的接收 BufferedReader br = new BufferedReader(new InputStreamReader(is)); // 5、按照行读取消息 String msg; while ((msg = br.readLine()) != null){ System.out.println(socket.getRemoteSocketAddress() + "说了:: " + msg); } } catch (Exception e) { System.out.println(socket.getRemoteSocketAddress() + "下线了!!!"); } } }
13:TCP 通信实战案例-即时通信
即时通信是什么含义,要实现怎么样的设计?
即时通信,是指一个客户端的消息发出去,其他客户端可以接收到。
之前我们的消息都是发给服务端的。
即时通信需要进行端口转发的设计思想。
即时通信是什么含义,要实现怎么样的设计?
即时通信,是指一个客户端的消息发出去,其他客户端可以接收到
即时通信需要进行端口转发的设计思想。
服务端需要把在线的Socket管道存储起来
一旦收到一个消息要推送给其他管道
package com.itheima.d9_chat; import java.io.OutputStream; import java.io.PrintStream; import java.net.Socket; import java.util.Scanner; /** 拓展:即时通信 客户端:发消息的同时,随时有人发消息过来。 服务端:接收消息后,推送给其他所有的在线socket */ public class ClientDemo1 { public static void main(String[] args) { try { System.out.println("====客户端启动==="); // 1、创建Socket通信管道请求有服务端的连接 // public Socket(String host, int port) // 参数一:服务端的IP地址 // 参数二:服务端的端口 Socket socket = new Socket("127.0.0.1", 6868); // 马上为客户端分配一个独立的线程负责读取它收到的消息 new ClientReaderThread(socket).start(); // 2、从socket通信管道中得到一个字节输出流 负责发送数据 OutputStream os = socket.getOutputStream(); // 3、把低级的字节流包装成打印流 PrintStream ps = new PrintStream(os); Scanner sc = new Scanner(System.in); while (true) { System.out.println("请说:"); String msg = sc.nextLine(); // 4、发送消息 ps.println(msg); ps.flush(); } // 关闭资源。 // socket.close(); } catch (Exception e) { e.printStackTrace(); } } }
package com.itheima.d9_chat; import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; import java.io.PrintStream; import java.net.Socket; public class ClientReaderThread extends Thread{ private Socket socket; public ClientReaderThread(Socket socket){ this.socket = socket; } @Override public void run() { try { // 3、从socket通信管道中得到一个字节输入流 InputStream is = socket.getInputStream(); // 4、把字节输入流包装成缓冲字符输入流进行消息的接收 BufferedReader br = new BufferedReader(new InputStreamReader(is)); // 5、按照行读取消息 String msg; while ((msg = br.readLine()) != null){ System.out.println(socket.getRemoteSocketAddress() + "收到了: " + msg); } } catch (Exception e) { System.out.println("服务端把你踢出去了~~"); } } }
package com.itheima.d9_chat; import java.net.ServerSocket; import java.net.Socket; import java.util.ArrayList; import java.util.List; /** 目标: 即时通信 */ public class ServerDemo2 { public static List<Socket> onLineSockets = new ArrayList<>(); // 存储在线管道【静态的变量】 public static void main(String[] args) { try { System.out.println("===服务端启动成功==="); // 1、注册端口 ServerSocket serverSocket = new ServerSocket(6868); // a.定义一个死循环由主线程负责不断的接收客户端的Socket管道连接。 while (true) { // 2、每接收到一个客户端的Socket管道,交给一个独立的子线程负责读取消息 Socket socket = serverSocket.accept(); System.out.println(socket.getRemoteSocketAddress()+ "它来了,上线了!"); // 把当前客户端管道Socket加入到在线集合中去 onLineSockets.add(socket); // 3、开始创建独立线程处理socket new ServerReaderThread(socket).start(); } } catch (Exception e) { e.printStackTrace(); } } }
package com.itheima.d9_chat; import java.io.*; import java.net.Socket; public class ServerReaderThread extends Thread{ private Socket socket; public ServerReaderThread(Socket socket){ this.socket = socket; } @Override public void run() { try { // 3、从socket通信管道中得到一个字节输入流 InputStream is = socket.getInputStream(); // 4、把字节输入流包装成缓冲字符输入流进行消息的接收 BufferedReader br = new BufferedReader(new InputStreamReader(is)); // 5、按照行读取消息 String msg; while ((msg = br.readLine()) != null){ System.out.println(socket.getRemoteSocketAddress() + "说了:: " + msg); // 把这个消息发给当前所有在线socket sendMsgToAll(msg); } } catch (Exception e) { System.out.println(socket.getRemoteSocketAddress() + "下线了!!!"); ServerDemo2.onLineSockets.remove(socket); // 从在线集合中抹掉本客户端socket } } private void sendMsgToAll(String msg) { try { // 遍历全部的在线 socket给他们发消息 for (Socket onLineSocket : ServerDemo2.onLineSockets) { // 除了自己的socket,其他socket我都发!! if(onLineSocket != socket){ PrintStream ps = new PrintStream(socket.getOutputStream()); ps.println(msg); ps.flush(); } } } catch (Exception e) { e.printStackTrace(); } } }
14:TCP 通信实战案例-即时通信 GUI界面版本
package com.itheima.d9_chat.即时通信; import javax.swing.*; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.DataInputStream; import java.io.DataOutputStream; import java.net.Socket; /** * @Author xlei(徐磊) * 客户端界面 */ public class ClientChat implements ActionListener { /** 1.设计界面 */ private JFrame win = new JFrame(); /** 2.消息内容框架 */ public JTextArea smsContent =new JTextArea(23 , 50); /** 3.发送消息的框 */ private JTextArea smsSend = new JTextArea(4,40); /** 4.在线人数的区域 */ /** 存放人的数据 */ /** 展示在线人数的窗口 */ public JList<String> onLineUsers = new JList<>(); // 是否私聊按钮 private JCheckBox isPrivateBn = new JCheckBox("私聊"); // 消息按钮 private JButton sendBn = new JButton("发送"); // 登录界面 private JFrame loginView; private JTextField ipEt , nameEt , idEt; private Socket socket ; public static void main(String[] args) { new ClientChat().initView(); } private void initView() { /** 初始化聊天窗口的界面 */ win.setSize(650, 600); /** 展示登录界面 */ displayLoginView(); /** 展示聊天界面 */ //displayChatView(); } private void displayChatView() { JPanel bottomPanel = new JPanel(new BorderLayout()); //----------------------------------------------- // 将消息框和按钮 添加到窗口的底端 win.add(bottomPanel, BorderLayout.SOUTH); bottomPanel.add(smsSend); JPanel btns = new JPanel(new FlowLayout(FlowLayout.LEFT)); btns.add(sendBn); btns.add(isPrivateBn); bottomPanel.add(btns, BorderLayout.EAST); //----------------------------------------------- // 给发送消息按钮绑定点击事件监听器 // 将展示消息区centerPanel添加到窗口的中间 smsContent.setBackground(new Color(0xdd,0xdd,0xdd)); // 让展示消息区可以滚动。 win.add(new JScrollPane(smsContent), BorderLayout.CENTER); smsContent.setEditable(false); //----------------------------------------------- // 用户列表和是否私聊放到窗口的最右边 Box rightBox = new Box(BoxLayout.Y_AXIS); onLineUsers.setFixedCellWidth(120); onLineUsers.setVisibleRowCount(13); rightBox.add(new JScrollPane(onLineUsers)); win.add(rightBox, BorderLayout.EAST); //----------------------------------------------- // 关闭窗口退出当前程序 win.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); win.pack(); // swing 加上这句 就可以拥有关闭窗口的功能 /** 设置窗口居中,显示出来 */ setWindowCenter(win,650,600,true); // 发送按钮绑定点击事件 sendBn.addActionListener(this); } private void displayLoginView(){ /** 先让用户进行登录 * 服务端ip * 用户名 * id * */ /** 显示一个qq的登录框 */ loginView = new JFrame("登录"); loginView.setLayout(new GridLayout(3, 1)); loginView.setSize(400, 230); JPanel ip = new JPanel(); JLabel label = new JLabel(" IP:"); ip.add(label); ipEt = new JTextField(20); ip.add(ipEt); loginView.add(ip); JPanel name = new JPanel(); JLabel label1 = new JLabel("姓名:"); name.add(label1); nameEt = new JTextField(20); name.add(nameEt); loginView.add(name); JPanel btnView = new JPanel(); JButton login = new JButton("登陆"); btnView.add(login); JButton cancle = new JButton("取消"); btnView.add(cancle); loginView.add(btnView); // 关闭窗口退出当前程序 loginView.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setWindowCenter(loginView,400,260,true); /** 给登录和取消绑定点击事件 */ login.addActionListener(this); cancle.addActionListener(this); } private static void setWindowCenter(JFrame frame, int width , int height, boolean flag) { /** 得到所在系统所在屏幕的宽高 */ Dimension ds = frame.getToolkit().getScreenSize(); /** 拿到电脑的宽 */ int width1 = ds.width; /** 高 */ int height1 = ds.height ; System.out.println(width1 +"*" + height1); /** 设置窗口的左上角坐标 */ frame.setLocation(width1/2 - width/2, height1/2 -height/2); frame.setVisible(flag); } @Override public void actionPerformed(ActionEvent e) { /** 得到点击的事件源 */ JButton btn = (JButton) e.getSource(); switch(btn.getText()){ case "登陆": String ip = ipEt.getText().toString(); String name = nameEt.getText().toString(); // 校验参数是否为空 // 错误提示 String msg = "" ; // 12.1.2.0 // \d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\ if(ip==null || !ip.matches("\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}")){ msg = "请输入合法的服务端ip地址"; }else if(name==null || !name.matches("\\S{1,}")){ msg = "姓名必须1个字符以上"; } if(!msg.equals("")){ /** msg有内容说明参数有为空 */ // 参数一:弹出放到哪个窗口里面 JOptionPane.showMessageDialog(loginView, msg); }else{ try { // 参数都合法了 // 当前登录的用户,去服务端登陆 /** 先把当前用户的名称展示到界面 */ win.setTitle(name); // 去服务端登陆连接一个socket管道 socket = new Socket(ip, Constants.PORT); //为客户端的socket分配一个线程 专门负责收消息 new ClientReader(this,socket).start(); // 带上用户信息过去 DataOutputStream dos = new DataOutputStream(socket.getOutputStream()); dos.writeInt(1); // 登录消息 dos.writeUTF(name.trim()); dos.flush(); // 关系当前窗口 弹出聊天界面 loginView.dispose(); // 登录窗口销毁 displayChatView(); // 展示了聊天窗口了 } catch (Exception e1) { e1.printStackTrace(); } } break; case "取消": /** 退出系统 */ System.exit(0); break; case "发送": // 得到发送消息的内容 String msgSend = smsSend.getText().toString(); if(!msgSend.trim().equals("")){ /** 发消息给服务端 */ try { // 判断是否对谁发消息 String selectName = onLineUsers.getSelectedValue(); int flag = 2 ;// 群发 @消息 if(selectName!=null&&!selectName.equals("")){ msgSend =("@"+selectName+","+msgSend); /** 判断是否选中了私法 */ if(isPrivateBn.isSelected()){ /** 私法 */ flag = 3 ;//私发消息 } } DataOutputStream dos = new DataOutputStream(socket.getOutputStream()); dos.writeInt(flag); // 群发消息 发送给所有人 dos.writeUTF(msgSend); if(flag == 3){ // 告诉服务端我对谁私发 dos.writeUTF(selectName.trim()); } dos.flush(); } catch (Exception e1) { e1.printStackTrace(); } } smsSend.setText(null); break; } } } class ClientReader extends Thread { private Socket socket; private ClientChat clientChat ; public ClientReader(ClientChat clientChat, Socket socket) { this.clientChat = clientChat; this.socket = socket; } @Override public void run() { try { DataInputStream dis = new DataInputStream(socket.getInputStream()); /** 循环一直等待客户端的消息 */ while(true){ /** 读取当前的消息类型 :登录,群发,私聊 , @消息 */ int flag = dis.readInt(); if(flag == 1){ // 在线人数消息回来了 String nameDatas = dis.readUTF(); // 展示到在线人数的界面 String[] names = nameDatas.split(Constants.SPILIT); clientChat.onLineUsers.setListData(names); }else if(flag == 2){ // 群发消息 String msg = dis.readUTF() ; clientChat.smsContent.append(msg); //滾動到底端 clientChat.smsContent.setCaretPosition(clientChat.smsContent.getText().length()); } } } catch (Exception e) { e.printStackTrace(); } } }
package com.itheima.d9_chat.即时通信; public class Constants { /** 常量 */ public static final int PORT = 7778 ; /** 协议分隔符 */ public static final String SPILIT = "003197♣♣㏘♣④④♣"; }
package com.itheima.d9_chat.即时通信; import java.io.DataInputStream; import java.io.DataOutputStream; import java.net.ServerSocket; import java.net.Socket; import java.text.SimpleDateFormat; import java.util.Collection; import java.util.HashMap; import java.util.Map; import java.util.Set; /** * @Author * @Email dlei0009@163.com */ public class ServerChat { /** 定义一个集合存放所有在线的socket */ public static Map<Socket, String> onLineSockets = new HashMap<>(); public static void main(String[] args) { try { /** 注册端口 */ ServerSocket serverSocket = new ServerSocket(Constants.PORT); /** 循环一直等待所有可能的客户端连接 */ while(true){ Socket socket = serverSocket.accept(); /** 把客户端的socket管道单独配置一个线程来处理 */ new ServerReader(socket).start(); } } catch (Exception e) { e.printStackTrace(); } } } class ServerReader extends Thread { private Socket socket; public ServerReader(Socket socket) { this.socket = socket; } @Override public void run() { DataInputStream dis = null; try { dis = new DataInputStream(socket.getInputStream()); /** 循环一直等待客户端的消息 */ while(true){ /** 读取当前的消息类型 :登录,群发,私聊 , @消息 */ int flag = dis.readInt(); if(flag == 1){ /** 先将当前登录的客户端socket存到在线人数的socket集合中 */ String name = dis.readUTF() ; System.out.println(name+"---->"+socket.getRemoteSocketAddress()); ServerChat.onLineSockets.put(socket, name); } writeMsg(flag,dis); } } catch (Exception e) { System.out.println("--有人下线了--"); // 从在线人数中将当前socket移出去 ServerChat.onLineSockets.remove(socket); try { // 从新更新在线人数并发给所有客户端 writeMsg(1,dis); } catch (Exception e1) { e1.printStackTrace(); } } } private void writeMsg(int flag, DataInputStream dis) throws Exception { // DataOutputStream dos = new DataOutputStream(socket.getOutputStream()); // 定义一个变量存放最终的消息形式 String msg = null ; if(flag == 1){ /** 读取所有在线人数发给所有客户端去更新自己的在线人数列表 */ /** onlineNames = [徐磊,zhangsan,李刚]*/ StringBuilder rs = new StringBuilder(); Collection<String> onlineNames = ServerChat.onLineSockets.values(); // 判断是否存在在线人数 if(onlineNames != null && onlineNames.size() > 0){ for(String name : onlineNames){ rs.append(name+ Constants.SPILIT); } // 徐磊003197♣♣㏘♣④④♣zhangsan003197♣♣㏘♣④④♣李刚003197♣♣㏘♣④④♣ // 去掉最后的一个分隔符 msg = rs.substring(0, rs.lastIndexOf(Constants.SPILIT)); /** 将消息发送给所有的客户端 */ sendMsgToAll(flag,msg); } }else if(flag == 2 || flag == 3){ // 读到消息 群发的 或者 @消息 String newMsg = dis.readUTF() ; // 消息 // 得到发件人 String sendName = ServerChat.onLineSockets.get(socket); // 李刚 时间 // 内容-- StringBuilder msgFinal = new StringBuilder(); // 时间 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss EEE"); if(flag == 2){ msgFinal.append(sendName).append(" ").append(sdf.format(System.currentTimeMillis())).append("\r\n"); msgFinal.append(" ").append(newMsg).append("\r\n"); sendMsgToAll(flag,msgFinal.toString()); }else if(flag == 3){ msgFinal.append(sendName).append(" ").append(sdf.format(System.currentTimeMillis())).append("对您私发\r\n"); msgFinal.append(" ").append(newMsg).append("\r\n"); // 私发 // 得到给谁私发 String destName = dis.readUTF(); sendMsgToOne(destName,msgFinal.toString()); } } } /** * @param destName 对谁私发 * @param msg 发的消息内容 * @throws Exception */ private void sendMsgToOne(String destName, String msg) throws Exception { // 拿到所有的在线socket管道 给这些管道写出消息 Set<Socket> allOnLineSockets = ServerChat.onLineSockets.keySet(); for(Socket sk : allOnLineSockets){ // 得到当前需要私发的socket // 只对这个名字对应的socket私发消息 if(ServerChat.onLineSockets.get(sk).trim().equals(destName)){ DataOutputStream dos = new DataOutputStream(sk.getOutputStream()); dos.writeInt(2); // 消息类型 dos.writeUTF(msg); dos.flush(); } } } private void sendMsgToAll(int flag, String msg) throws Exception { // 拿到所有的在线socket管道 给这些管道写出消息 Set<Socket> allOnLineSockets = ServerChat.onLineSockets.keySet(); for(Socket sk : allOnLineSockets){ DataOutputStream dos = new DataOutputStream(sk.getOutputStream()); dos.writeInt(flag); // 消息类型 dos.writeUTF(msg); dos.flush(); } } }
package com.itheima.d9_chat.即时通信; public class User { private Integer id ; private String name ; public User(Integer id, String name) { this.id = id; this.name = name; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String toString() { return "User [id=" + id + ", name=" + name + "]"; } }
15:TCP通信实战案例-模拟 BS 系统[了解]
1、之前的客户端都是什么样的 其实就是CS架构,客户端实需要我们自己开发实现的。 2、BS结构是什么样的,需要开发客户端吗? 浏览器访问服务端,不需要开发客户端
TCP通信如何实现BS请求网页信息回来呢?
客户端使用浏览器发起请求(不需要开发客户端)
服务端必须按照浏览器的协议规则响应数据。【HTTP】
浏览器使用什么协议规则呢?
HTTP协议(简单了解下)
package com.itheima.d10_bs; import java.io.PrintStream; import java.net.ServerSocket; import java.net.Socket; import java.util.concurrent.*; /** 了解:BS-浏览器-服务器基本了解。 引入: 之前客户端和服务端都需要自己开发。也就是CS架构。 接下来模拟一下BS架构。 客户端:浏览器。(无需开发) 服务端:自己开发。 需求:在浏览器中请求本程序,响应一个网页文字给浏览器显示 */ 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.交给一个独立的线程来处理! 【每次来一个socket 那么就封装成一个任务交给线程池】 pool.execute(new ServerReaderRunnable(socket)); } } catch (Exception e) { e.printStackTrace(); } } }
package com.itheima.d10_bs; import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; import java.io.PrintStream; import java.net.Socket; public class ServerReaderRunnable implements Runnable{ private Socket socket; public ServerReaderRunnable(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'>《最牛的149期》 </span>"); ps.close(); } catch (Exception e) { System.out.println(socket.getRemoteSocketAddress() + "下线了!!!"); } } }
分类:
JAVA篇
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!