Java基础-网络通信
1.1 基本认识
实现网络编程的三要素:
IP地址
:设备在网络中的地址 唯一的标识端口
: 应用程序在设备中的唯一标识协议
: 数据在网络中传输的规则,常见的协议有 UDP协议和TCP协议
1.IP地址
:
-
IPv6:128位(16字节),分为8个整数,每个整数用四个十六进制位表示,数之间用冒号(:)分开
-
IP地址形式:
- 公网地址和私有地址(局域网地址)
- 192.168.开头的就是常见的局域网地址,范围即为192.168.0.0-192.168.255.255,专门为组织机构内部使用
-
IP常用命令:
- ipconfig:查看本机IP地址
- ping IP地址:检查网络是否连通
-
特殊IP地址:
- 本机IP地址:127.0.0.1或者localhost:称为 回送地址也可称为 本地回环地址,只会寻找当前所在本机
-
IP地址基本寻址
- 计算机 依据域名(https://baidu.com)通过 DNS域名服务器,查询IP地址;然后访问对应的服务器IP地址,服务器返回数据展示在浏览器上。
InetAddress 的使用
- 此类表示Internet协议 (IP地址)
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 |
public class InetAddressDemo1 {
public static void main(String[] args) throws Exception{
//1、获取本主机IP对象
InetAddress ip = InetAddress.getLocalHost();
//获取IP地址的主机名
System.out.println(ip.getHostName());
//获取Ip地址
System.out.println(ip.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.74");
System.out.println(ip3.getHostName());
System.out.println(ip3.getHostAddress());
//指定毫秒内连通IP对应主机,连通返回true
System.out.println(ip3.isReachable(3000));
}
}
运行结果:
DESKTOP-6O6MLG4
192.168.1.7
www.baidu.com
39.156.66.14
112.80.248.74
112.80.248.74
false
2.端口号
- 端口号:标识 正在计算机设备上运行的进程(程序),被规定为一个16位的二进制,范围是 0~65535
端口类型
-
周知端口:0~1023 ,被预先定义的知名应用占用(如 HTTP 占用80 , FTP 21)
-
注册端口
:1024~49151,分配给用户进程或某些应用程序(如 Tomcat 8080, MySQL 3306),我们自己开发的程序选择注册端口,且一个设备中不能出现两个一样端口号的程序,否则出错 -
动态端口:49152~65535,之所以称为 动态端口,是因为它一般不固定分配某种进程,而是动态分配
3.通信协议
- 连接和通信数据的规则称为 网络通信协议
网络通信协议有两套参考模型
- OSI参考模型:世界互联协议标准,全球通信规范,由于此模型过于理想化,未能在因特网上进行广泛推广
- TCP/IP参考模型(TCP/IP协议):事实上的国际标准
传输层的2个常见协议:
- TCP (Transmission Control Protocol):传输控制协议
- UDP(User Datagram Protocol):用户数据报协议
TCP协议特点:
- 使用TCP协议,必须双方先建立连接,它是一种面向连接的可靠通信协议
- 传输前,采用”三次握手“方法建立连接,所以是可靠的
- 在连接中可进行大数据量的传输
- 连接、发送数据都需要确认,且传输完毕后,还需释放已建立的连接,通信效率较低
TCP协议通信场景
- 对信息安全要求较高,例如:文件下载、金融等数据通信
UDP协议特点:
- UDP是一种无连接、不可靠连接的协议
- 将数据源IP、目的地IP和端口封装成数据包,不需要建立连接
- 每个数据包的大小限制在64KB内
- 发送不管对方是否准备好,接收方收到也不确认,故是不可靠的
- 可以广播放送,发送数据结束时无需释放资源,开销小,速度快
UDP协议通信场景
- 语音通信,视频会话等
网络通信基本模式:
- 常见的通信模式有两种形式:Client-Server(CS),Browser-Server(BS)
- CS 客户端-服务器
- 客户端(需要程序员开发实现,用户需要安装客户端),服务器(需要程序员开发实现)
- BS 浏览器-服务器
- 浏览器(不需要程序员开发实现,用户需要安装浏览器),服务器(需要程序员开发实现)
1.2 UDP通信
DatagramSocket
:发送端和接收端对象(人)
- 构造器
- public DatagramSocket() 创建发送端 Socket对象,系统会随机分配一个端口号
- public DatagramSocket(int port) 创建接收端 Socket对象,并指定端口号
- 成员方法
- public void send(DatagramPacket dp) 发送数据包
- public void receive(DatagramPacket p) 接收数据包
DatagramPacket
:数据包对象(韭菜盘子)
- 构造器
- public DatagramPacket(byte[] buf ,int length ,InetAddress address ,int port) 创建发送端 数据包对象
- buf :要发送的内容,字节数组
- length :要发送内容的字节长度
- address:接收端的IP地址长度
- port:接收端的端口号
- public DatagramPacket(byte[] buf ,int length) 创建接收端 数据包对象
- buf :用来存储接收的内容
- length:能够接收内容的长度
- public DatagramPacket(byte[] buf ,int length ,InetAddress address ,int port) 创建发送端 数据包对象
- 常用方法
- public int getLength() 获得实际接收到的字节个数
UDP实例 单开单收
/**
* 实现发送端,发送数据
* 客户端对象随机端口号,发送数据包四种参数
*/
public class ClientDataGramSocketDemo {
public static void main(String[] args) throws Exception{
//1.创建发送方对象:随机分配一个端口号,空
DatagramSocket client = new DatagramSocket(); //可以自定义端口888888
//2.创建发送的数据包对象:(韭菜盘子)
/**
* public DatagramPacket(byte buf[], int offset, int length,
* InetAddress address, int port)
*/
byte[] buffer = "我喜欢你~~~~".getBytes();
//本主机
DatagramPacket packet = new DatagramPacket(buffer,buffer.length, InetAddress.getLocalHost(),8888);
//3.发送数据包
client.send(packet);
//4.关闭
client.close();
}
}
/**
* 实现服务端对象 接收数据
* 服务端对象:指定一个端口号
* 服务端接收数据包对象:指定接收数据包与大小
*/
public class ServerDatagramSocketDemo {
public static void main(String[] args) throws Exception{
//1.创建服务端对象:服务端一定要 指定端口号 !!!
DatagramSocket server = new DatagramSocket(8888);
//2.创建服务端的数据包对象:(韭菜盘子)
/**
public DatagramPacket(byte buf[], int length)
*/
byte[] buffer = new byte[1024*64]; // udp最大64B
DatagramPacket packet = new DatagramPacket(buffer,buffer.length);
//3.等待接收数据包
server.receive(packet);
//4.输出
int len = packet.getLength(); //获取接收的数据包大小
System.out.println(new String(buffer,0,len));
//5.获取发送方的ip和端口
SocketAddress ClientIp = packet.getSocketAddress();
System.out.println("发送端ip:"+ClientIp);
int port = packet.getPort();
System.out.println("发送端口:"+port);
server.close();
}
}
运行结果:
我喜欢你~~~~
发送端ip:/192.168.1.7:63536
发送端口:63536
UDP通信实例
- 需要服务器先运营,再客户端
- 允许多开,多收
UDP的接收端为什么可以接收很多发送端的消息?
- 接收端只负责接收数据包,无所谓是哪个发送端的数据包
UDP案例 持续发送与接收消息 多开(一个电脑 只能间断性的运行多次)多发
//发送端
public class ClientDataGramSocketDemo {
public static void main(String[] args) throws Exception{
//1.创建发送方对象:随机分配一个端口号,空
DatagramSocket client = new DatagramSocket(); //可以自定义端口888888
Scanner sc = new Scanner(System.in);
//2.创建发送的数据包对象:(韭菜盘子)
/**
* public DatagramPacket(byte buf[], int offset, int length,
* InetAddress address, int port)
*/
while (true) {
System.out.println("请说:");
String msg = sc.nextLine();
if (Objects.equals("exit",msg)){
//4.关闭
System.out.println("离线成功~~~");
client.close();
break;
}
byte[] buffer = msg.getBytes();
//本主机
DatagramPacket packet = new DatagramPacket(buffer,buffer.length, InetAddress.getLocalHost(),8888);
//3.发送数据包
client.send(packet);
}
}
}
//接收端
public class ServerDatagramSocketDemo {
public static void main(String[] args) throws Exception{
//1.创建服务端对象:服务端一定要 指定端口号 !!!
DatagramSocket server = new DatagramSocket(8888);
//2.创建服务端的数据包对象:(韭菜盘子)
/**
public DatagramPacket(byte buf[], int length)
*/
byte[] buffer = new byte[1024*64]; // udp最大64B
DatagramPacket packet = new DatagramPacket(buffer,buffer.length);
while (true) {
//3.等待接收数据包
server.receive(packet);
//4.输出
int len = packet.getLength(); //获取接收的数据包大小
System.out.println("收到发送方ip:"+packet.getSocketAddress()+",端口:"+packet.getPort()+
",发来消息:"+new String(buffer,0,len));
}
//server.close();
}
}
发送端:
请说:
hello
请说:
exit1
请说:
exit
离线成功~~~
接收端:
收到发送方ip:/192.168.1.7:63581,端口:63581,发来消息:hello
收到发送方ip:/192.168.1.7:63581,端口:63581,发来消息:exit1
UDP的三种通信方式
- 单播:单台主机与单台主机之间的通信
- 广播:当前主机与所在网络中的所有主机通信
- 组播:当前主机与选定的一组主机的通信
UDP如何实现广播?
- 使用广播地址:255.255.255.255
- 具体操作:
- 发送端 发送的数据包的目的地,写的是广播地址 并且 指定端口 (255.255.255.255 ,9999)
- 本机所在网段的 其他主机 的程序只要匹配端口成功 , 即可收到消息 (9999)
实现 (上一个实例,仅修改数据包对象)
//本主机
//广播
//255.255.255.255,发送同一个局域网内的其他主机
DatagramPacket packet = new DatagramPacket(buffer,buffer.length,
InetAddress.getByName("255.255.255.255"),9999);
UDP如何实现组播?
- 使用组播地址:224.0.0.0~239.255.255.255
- 具体操作:
- 发送端的数据包 的 目的地址 是组播IP(例如:224.0.1.1 , 端口号 :9999)
- 接收端 必须 绑定该组播IP (224.0.1.1),端口还要对应发送端的目的端口 9999,这样即可接收该组播消息
- DatagramSocket的子类
MulticastSocket
可以在接收端绑定组播 IP
//224.0.0.0~239.255.255.255 之间
DatagramPacket packet = new DatagramPacket(buffer,buffer.length,
InetAddress.getByName("224.0.1.1"),9999);
public class ServerDatagramSocketDemo {
public static void main(String[] args) throws Exception{
//1.创建服务端对象:服务端一定要 指定端口号 !!!
//DatagramSocket 子类 Multicast
MulticastSocket server = new MulticastSocket(9999);
//将当前接收端加入到一个组播组中,绑定对应的组播消息的组播ip !!!!!!
server.joinGroup(InetAddress.getByName("224.0.1.1"));
// public void joinGroup(SocketAddress mcastaddr, NetworkInterface netIf
// server.joinGroup(new InetSocketAddress(InetAddress.getByName("224.0.1.1"),9999),
// NetworkInterface.getByInetAddress(InetAddress.getLocalHost()));
//2.创建服务端的数据包对象:(韭菜盘子)
/**
public DatagramPacket(byte buf[], int length)
*/
byte[] buffer = new byte[1024*64]; // udp最大64B
DatagramPacket packet = new DatagramPacket(buffer,buffer.length);
while (true) {
//3.等待接收数据包
server.receive(packet);
//4.输出
int len = packet.getLength(); //获取接收的数据包大小
System.out.println("收到发送方ip:"+packet.getSocketAddress()+",端口:"+packet.getPort()+
",发来消息:"+new String(buffer,0,len));
}
//server.close();
}
}
1.3 TCP通信
客户端 - 服务端
注意:
- 在
Java
中 只要使用java.net.Socket类实现通信,底层即是使用了TCP协议 - 服务端 通过 SocketServer注册服务端(端口)server,server 通过
accept()
获取 Socket对象
1.3.1 单发 单收
发送端(客户端)
客户端发送消息
- 创建客户端的
Socket
对象,请求与服务器的连接 - 使用 socket 对象调用
getOutputStream()
方法得到字节输出流 - 使用字节输出流完成数据的发送 (使用打印流 printStream)
- 释放资源:关闭 socket 管道(输出流)
Socket 类
- 构造器
- public Socket(String host ,int port) 创建发送端的 Socket 对象与服务端连接,参数为服务端程序的 ip 和端口
- 成员方法
- OutputStream getOutputStream() 获得字节输出流对象
- InputStream getInputStream() 获得字节输入流对象
服务器实现接收消息
- 创建
ServerSocket
对象,注册服务器端口 - 调用 ServerSocket 对象的 accept()方法,等待客户端的连接,并得到Socket管道对象
- 通过 获取的 Socket 对象调用 getInputStream() 方法得到字节流输入、完成数据的接收 (使用 转换流 —> 字符流 —> 缓冲流 )
- 释放资源:关闭 socket 管道(输入流)
ServerSocket 类
-
构造器
- public ServerSocket (int port) 注册服务端端口
-
成员方法
- public Socket accept() 等待接收客户端的Socket通信连接,连接成功返回Socket对象与客户端建立端到端通信
//客户端
public class ClientSocketTcpDemo {
public static void main(String[] args) {
try {
//1.创建 socket 管道!!!!
/** public Socket(String host, int port)
*/
Socket socket = new Socket(InetAddress.getLocalHost().getHostAddress(),9999);
//2.socket管道中,创建字节输出流
OutputStream os = socket.getOutputStream();
//3.低级字节输出流 转为 打印流
//public PrintStream(OutputStream out)
PrintStream ps = new PrintStream(os);
//4.发送消息
// ps.print("Tcp连接成功,你好,今晚约吗?");
// Connection reset , 这里没有换行符而服务端是按行接收
// Tcp 当一方挂了,另一方也会挂
ps.println("Tcp连接成功,你好,今晚约吗?");
ps.flush();
//关闭资源
//socket.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
//服务端
public class ServerSocketDemo {
public static void main(String[] args) {
try {
//1.注册端口!!! 使用ServerSocket !!!!
//指定一致的服务端端口号
ServerSocket serverSocket = new ServerSocket(9999);
//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){
//获取远端ip地址
System.out.println(socket.getRemoteSocketAddress()+"发送:"+msg);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
服务端
/192.168.1.7:55243发送:Tcp连接成功,你好,今晚约吗?
小结:
-
TCP通信 服务端 用的代表类?
- ServerSocket类,注册端口
- 调用
accept()
方法 阻塞等待接收客户端连接,得到Socket对象
-
TCP通信的基本原理?
- 客户端怎么发,服务端就应该怎么收
- 客户端如果没有消息,服务端会进入阻塞等待
- Socket一方关闭或者出现异常、对方Socket也会失效或者出错 java.net.SocketException: Connection reset
1.3.2 多发 多收
与之前UDP 持续发送与接收消息 类似
- 多发多收
- 客户端使用循环反复地发送消息
- 服务端使用循环反复地接收消息
//客户端
public class ClientSocketTcpDemo {
public static void main(String[] args) {
try {
//1.创建 socket 管道!!!!
/** public Socket(String host, int port)
*/
Socket socket = new Socket(InetAddress.getLocalHost().getHostAddress(),9999);
//2.socket管道中,创建字节输出流
OutputStream os = socket.getOutputStream();
//3.低级字节输出流 转为 打印流
//public PrintStream(OutputStream out)
PrintStream ps = new PrintStream(os);
Scanner sc = new Scanner(System.in);
while (true) {
//4.发送消息
// ps.print("Tcp连接成功,你好,今晚约吗?");
// Connection reset , 这里没有换行符而服务端是按行接收 ,
// Tcp 当一方挂了,另一方也会挂
System.out.println("请说:");
String msg = sc.nextLine();
ps.println(msg);
ps.flush();
}
//socket.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
//服务端
public class ServerSocketDemo {
public static void main(String[] args) {
try {
//1.注册端口!!! 使用ServerSocket
//指定一致的服务端端口号
ServerSocket serverSocket = new ServerSocket(9999);
//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){
//获取远端ip地址
System.out.println(socket.getRemoteSocketAddress()+"发送:"+msg);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
- 现在服务端 为什么 不可以同时接收多个客户端地消息?
- 目前服务端是单线程的 , 每次只能处理一个客户端的消息
解决接收多个发送端消息问题? (真正的多发
)
- 方式一:
- 1 主线程定义 循环负责接收客户端Socket管道 连接
- 2 每接收到一个Socket通信管道后 分配一个独立的线程负责 处理它
//客户端
public class ClientSocketTcpDemo {
public static void main(String[] args) {
try {
//1.创建 socket 管道!!!!
Socket socket = new Socket(InetAddress.getLocalHost().getHostAddress(),9999);
//2.socket管道中,创建字节输出流
OutputStream os = socket.getOutputStream();
//3.低级字节输出流 转为 打印流
//public PrintStream(OutputStream out)
PrintStream ps = new PrintStream(os);
Scanner sc = new Scanner(System.in);
while (true) {
//4.发送消息
System.out.println("请说:");
String msg = sc.nextLine();
//输入exit,关闭socket管道
if ("exit".equals(msg)){
break;
}else {
ps.println(msg);
ps.flush();
}
}
//socket.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
//服务端主线程
public class ServerSocketDemo {
public static void main(String[] args) {
try {
//1.注册端口!!! 使用ServerSocket
//指定一致的服务端端口号
ServerSocket serverSocket = new ServerSocket(9999);
while (true) {
//2.调用accept() , 等待接收客户端Socket连接请求 ,建立Socket通信管道
Socket socket = serverSocket.accept();
new ServerReaderThread(socket).start();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
//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));
System.out.println(socket.getRemoteSocketAddress()+"已上线!!!欢迎!!!");
//5.按行读取消息
String msg;
while ((msg = br.readLine()) != null){
//获取远端ip地址
System.out.println(socket.getRemoteSocketAddress()+"发送:"+msg);
}
} catch (Exception e) {
//发送端异常!!!!
//当发送方socket管道断开,结束接收
System.out.println(socket.getRemoteSocketAddress() + "已下线~~~~");
}
}
}
同时运行 三个不同的客户端 发送消息
服务端的 运行结果
- 方式二
- 引入线程池 处理多个客户端消息
线程池使用:
客户端不用修改
//服务端 主线程
public class ServerSocketDemo {
//创建线程池
public static Executor executor = new ThreadPoolExecutor(3,5,3, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(2), Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
public static void main(String[] args) {
try {
//1.注册端口!!! 使用ServerSocket
//指定一致的服务端端口号
ServerSocket serverSocket = new ServerSocket(9999);
while (true) {
//2.调用accept() , 等待接收客户端Socket连接请求 ,建立Socket通信管道
Socket socket = serverSocket.accept();
//3.创建 任务类对象
Runnable target = new ServerSocketRunnable(socket);
executor.execute(target);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
//服务端接收消息 的 独立线程
public class ServerSocketRunnable implements Runnable{
private Socket socket;
public ServerSocketRunnable(Socket socket){
this.socket = socket;
}
@Override
public void run() {
try {
//3.通过Socket通信管道 得到 一个字节输入流
InputStream is = socket.getInputStream();
//4.低级输入流 转为 缓冲字节输入流
//通过转换流 , 将字节流变为字符流
BufferedReader br = new BufferedReader(new InputStreamReader(is));
System.out.println(socket.getRemoteSocketAddress()+"已上线!!!欢迎!!!");
//5.按行读取消息
String msg;
while ((msg = br.readLine()) != null){
//获取远端ip地址
System.out.println(socket.getRemoteSocketAddress()+"发送:"+msg);
}
} catch (IOException e) {
//发送端异常!!!!
//当发送方socket管道断开,结束接收
System.out.println(socket.getRemoteSocketAddress() + "已下线~~~~");
}
}
}
使用线程池的优势在哪里?
- 服务端可以服用线程处理多个客户端,可以避免系统瘫痪
- 适合客户端通信时长较短的场景
1.4 即时通信(重点)
即时通信是什么含义,要实现怎样的设计?
-
即时通信:一个客户端的消息发出去,其他客户端可以接收到
-
之前 消息都是发给服务端
-
即时通信 需要进行 端口转发 的设计思想
/**
* 重点 !!!!!
* 目标 : 学会使用 socket类 ,实现即时通信 多发多收
* 客户端发送消息(输出) ,服务器转交(输入和输出) , 所有客户端接收消息(输入)
*/
public class ClientSocketTcpDemo {
public static void main(String[] args) {
try {
//1.创建 socket 管道!!!!
Socket socket = new Socket(InetAddress.getLocalHost().getHostAddress(),9999);
//创建 一个独立的线程 专门负责客户端的 接收 消息(服务器随时可能转发过来)
new ClientReaderThread(socket).start();
//2.socket管道中,创建字节输出流,发送消息
OutputStream os = socket.getOutputStream();
//3.低级字节输出流 转为 打印流
PrintStream ps = new PrintStream(os);
Scanner sc = new Scanner(System.in);
while (true) {
//4.发送消息
System.out.println("请说:");
String msg = sc.nextLine();
//输入exit,关闭socket管道
if ("exit".equals(msg)){
break;
}else {
ps.println(msg);
ps.flush();
}
}
//socket.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* 客户端 接收消息的线程 , 打印消息
*/
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){
//获取远端ip地址
System.out.println("收到"+socket.getRemoteSocketAddress()+"发送:"+msg);
}
} catch (Exception e) {
//服务端异常!!!!
//当服务端socket管道断开,结束接收
System.out.println("服务端把你退出群聊~~~~");
}
}
}
/**
* 重点 !!!
* 目标:实现服务端 网络通信 多个发送端的多发多收 !!!
* 注意!!! 主线程 根据socket管道创建子线程
* 子线程启动后,接收消息
* 服务端,接收发送端消息,转发给所有的发送端
*/
public class ServerSocketDemo {
//创建 一个静态的Socket集合
public static List<Socket> allSocketList = new ArrayList<>();
public static void main(String[] args) {
try {
//1.注册端口!!! 使用ServerSocket
//指定一致的服务端端口号
ServerSocket serverSocket = new ServerSocket(9999);
while (true) {
//2.调用accept() , 等待接收客户端Socket连接请求 ,建立Socket通信管道
Socket socket = serverSocket.accept();
System.out.println(socket.getRemoteSocketAddress()+"已上线!!!欢迎!!!"); //上线
//添加到 Socket集合
ServerSocketDemo.allSocketList.add(socket);
//子线程 启动(服务端 接收和发送消息)
new ServerReaderThread(socket).start();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 服务端,接收发送端的Socket管道
* 再向所有的Socket对象 发送消息
*/
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){
//获取远端ip地址
//服务器 接收(输入)
System.out.println(socket.getRemoteSocketAddress()+"发送:"+msg);
//每一行的消息,服务器再(输出)到所有管道
sendMsgToAll(msg);
}
} catch (Exception e) {
//发送端异常!!!!
//当发送方socket管道断开,结束接收
System.out.println(socket.getRemoteSocketAddress() + "已下线~~~~");
ServerSocketDemo.allSocketList.remove(socket); //移除
}
}
/**
* 向所有Socket管道发送(输出)消息
* @param msg 消息
* @throws Exception
*/
private void sendMsgToAll(String msg) throws Exception{
for (Socket socket : ServerSocketDemo.allSocketList) {
PrintStream ps = new PrintStream(socket.getOutputStream());
ps.println(msg);
ps.flush();
}
}
}
之前 都是 CS通信
1.5 BS开发
/**
* 目标 : 模拟 BS模式
* 不需要考虑开发浏览器(发送端), 只考虑服务器输出
*/
public class ServerDemo{
public static Executor pool = new ThreadPoolExecutor(3,5,3, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(2), Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
public static void main(String[] args) {
try {
//1.注册端口
ServerSocket serverSocket = new ServerSocket(9999);
//2.线程池处理 接收多个客户端请求
while(true){
Socket socket = serverSocket.accept();
//3.交给一个独立线程来处理
pool.execute(new ServerSocketRunnable(socket));
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* 注意: 响应HTTP协议格式!!!!
*/
public class ServerSocketRunnable implements Runnable{
private Socket socket;
public ServerSocketRunnable(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) {
//发送端异常!!!!
//当发送方socket管道断开,结束接收
System.out.println(socket.getRemoteSocketAddress() + "已下线~~~~");
}
}
}
Http协议格式