概述
TCP通信实战案例
一、即时通信
1、思考
- 即时通信是什么含义,要实现怎么样的设计?
- 即时通信指的是:一个客户端的消息发出去,其他客户端可以接收到。
- 之前我们的消息都是发给服务端的,相当于是弹幕的效果一样。
- 即时通信需要进行端口转发的设计思想。
2、端口转发设计思想
(1)客户端发送消息
- 客户端1发送了一条消息:“约吗?”
- 注意:客户端1发送的 “约吗?”,是先发给服务端的,而不是直接发送给其他客户端的。
(2)群发
- 此时,服务端接收到客户端1发送的 “约吗?”;
- 进行端口转发给其他的客户端,这样其他的客户端就可以看到客户端1发出去的 “约吗?”。
(3)私发
- 此时,服务端接收到客户端1发送的 “约吗?”
- 进行端口转发给要私发的那个客户端,这样被私发的那个客户端就可以看到客户端1发出去的 “约吗?”
3、群聊实现
(1)需求
- 客户端可以发送消息,同时也可以接收到其他客户端和自己发出的消息。
- 服务端接收到多个客户端发送的消息,转发给全部在线的客户端。
(2)分析实现
-
客户端:
-
创建Socket对象,指定服务端的IP和端口,与服务端建立连接。
-
创建客户端读取消息的线程任务类:
a. 定义socket变量,用于存储接收到的客户端的socket管道连接请求
b. 定义有参数构造器,用于接收客户端的socket请求,初始化到socket变量
c. 重写run方法:
- 从socket通信管道中得到字节输入流管道
- 将字节输入流管道转换成字符输入流管道,并包装成缓冲字符输入流管道:负责接收服务端转发其他客户端发送的消息过来
- 使用死循环开始按照行读取服务端转发过来的数据
- 展示读取到的数据
- 如果捕获到异常,说明服务端已经挂了!说明群聊已解散!
-
创建一个独立的线程对象专门负责这个客户端的读取消息任务(服务端随机可能转发其他客户端的消息过来!)
-
从socket通信管道中得到字节输出流管道,并包装成打印流管道
-
客户端开始发送数据给服务端
package com.app.d10_tcp_socket5_sms; import java.io.PrintStream; import java.net.Socket; import java.util.Scanner; /** 需求: 1、客户端可以发送消息 2、客户端可以接收到其他客户端和自己发出的消息 */ public class Client { public static void main(String[] args) throws Exception { System.out.println("-=-=-=-客户端启动成功-=-=-=-"); // 1、创建Socket对象,指定服务端的IP和端口,与服务端建立连接 Socket socket = new Socket("127.0.0.1", 7878); // 3、创建一个独立的线程对象专门负责这个客户端的读取消息任务(服务端随时可能转发其他客户端的消息过来!) new ClientReaderThread(socket).start(); // 4、从socket通信管道中得到字节输出流管道,并包装成打印流管道 PrintStream ps = new PrintStream(socket.getOutputStream()); // 5、客户端开始发送数据给服务端 Scanner sc = new Scanner(System.in); while (true) { String msg = sc.nextLine(); ps.println(msg); ps.flush(); // 刷新数据 } } }
package com.app.d10_tcp_socket5_sms; import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; import java.net.Socket; /** 2、创建客户端读取消息的线程任务类 */ public class ClientReaderThread extends Thread{ // a. 定义socket变量,用于存储接收到的客户端的socket请求 private Socket socket; // b. 定义有参数构造器,用于接收客户端的socket请求,初始化到socket变量 public ClientReaderThread(Socket socket) { this.socket = socket; } // c. 重写run方法: @Override public void run() { try { // 1. 从socket通信管道中得到字节输入流管道 InputStream is = socket.getInputStream(); // 2. 将字节输入流管道转换成字符输入流管道,并包装成缓冲字符输入流管道,负责接收服务端转发其他客户端发送的消息过来 BufferedReader br = new BufferedReader(new InputStreamReader(is)); // 3. 使用死循环开始按照行读取服务端转发过来的数据 String msg; while ( (msg = br.readLine()) != null ) { // 4. 展示读取到的数据 System.out.println("收到了:" + msg); } } catch (Exception e) { // 5. 如果出现异常,说明群主已将你踢出群聊(服务端把你踢出去了~) System.out.println("服务端已将你踢出~~"); } } }
-
-
服务端:
-
创建静态的List集合,用于存储当前全部在线的客户端的socket管道
-
创建ServerSocket对象,注册端口
-
使用死循环接收客户端的socket管道连接请求
-
开始接收每个客户端的socket管道连接请求(注意:在这里等待到客户端的socket管道连接)
-
跟踪客户端上线,将上线的客户端的socket管道添加到List集合中,完成上线
-
创建服务端接收客户端socket管道连接请求的线程任务类:
a. 定义socket变量,用于存储接收到的客户端的socket请求
b. 定义有参数构造器,用于接收客户端的socket请求,初始化到socket变量
c. 重写run方法:
-
从socket通信管道中得到字节输入流管道
-
将字节输入流管道转换成字符输入流管道,并包装成缓冲字符输入流管道:负责接收服务端转发其他客户端发送的消息过来
-
使用死循环开始按照行读取客户端发送过来的数据
-
展示读取到的数据
-
创建独立的方法sendMsgToAllSockets:将该客户端发送的消息进行端口转发给全部在线的客户端的socket管道
a. 遍历全部在线的客户端的socket管道的集合allOnlineSockets:
1. 从socket管道中得到字节输出流管道,并包装成打印流管道:负责转发消息给全部在线的客户端的socket管道
-
package com.app.d10_tcp_socket5_sms; import java.net.ServerSocket; import java.net.Socket; import java.util.ArrayList; import java.util.List; /** 需求: 1、服务端接收多个客户端发送的消息 2、服务端可以将多个客户端发送的消息转发给全部在线的客户端 */ public class Server { // 1、创建静态的List集合,用于存储当前全部在线的客户端的socket管道 public static List<Socket> allOnlineSockets = new ArrayList<>(); public static void main(String[] args) throws Exception { System.out.println("-=-=-=-服务端启动成功-=-=-=-"); // 2、创建ServerSocket对象,注册端口 ServerSocket serverSocket = new ServerSocket(7878); // 3、使用死循环接收客户端的socket管道连接请求 while (true) { // 4、开始接收每个客户端的socket管道连接请求 // 注意:在这里等到客户端的socket管道连接 Socket socket = serverSocket.accept(); // 5、跟踪客户端上线,将上线的客户端的socket管道添加到List集合中,完成上线 System.out.println(socket.getRemoteSocketAddress() + "上线了~~"); allOnlineSockets.add(socket); // 7、每接收到一个客户端的socket管道连接请求,创建一个独立的线程对象专门负责这个客户端的socket管道连接请求的任务 new ServerReaderThread(socket).start(); } } }
package com.app.d10_tcp_socket5_sms; import java.io.*; import java.net.Socket; /** 6、创建服务端接收客户端socket管道连接请求的线程任务类 */ public class ServerReaderThread extends Thread { // a. 定义socket变量,用于存储接收到的客户端的socket请求 private Socket socket; // b. 定义有参数构造器,用于接收客户端的socket请求,初始化到socket变量 public ServerReaderThread(Socket socket) { this.socket = socket; } // c. 重写run方法: @Override public void run() { try { // 1. 从socket通信管道中得到字节输入流管道 InputStream is = socket.getInputStream(); // 2. 将字节输入流管道转换成字符输入流管道,并包装成缓冲字符输入流管道,负责接收服务端转发其他客户端发送的消息过来 BufferedReader br = new BufferedReader(new InputStreamReader(is)); // 3. 使用死循环开始按照行读取客户端发送过来的数据 String msg; while ( (msg = br.readLine()) != null ) { // 4. 展示读取到的数据 System.out.println(socket.getRemoteSocketAddress() + "说了:" + msg); // 5、创建独立的方法sendMsgToAllSockets:将该客户端发送的消息进行端口转发给全部在线的客户端的socket管道 sendMsgToAllSockets(msg); } } catch (Exception e) { // 6. 跟踪客户端下线,将下线的客户端的socket管道从List集合中删除 System.out.println(socket.getRemoteSocketAddress() + "离线了~~"); Server.allOnlineSockets.remove(socket); } } /** 将消息进行端口转发给所有在线的客户端的方法 */ private void sendMsgToAllSockets(String msg) throws Exception { // a.遍历全部在线的客户端的socket管道的集合allOlineSockets for (Socket onlineSocket : Server.allOnlineSockets) { // 1. 从socket管道中得到字节输出流管道,并包装成打印流管道:负责转发消息给全部在线的客户端socket管道 PrintStream ps = new PrintStream(onlineSocket.getOutputStream()); ps.println(msg); ps.flush(); // 刷新数据 } } }
-
(3)测试
总结
1、即时通信是什么含义,要实现怎么样设计?
- 即时通信是指:一个客户端的消息发送出去,其他客户端可以接收到
- 即时通信需要进行端口转发的设计思想
- 服务端需要把在线的Socket管道存储起来
- 一旦收到一条消息要推送给其他管道
二、模拟BS请求[了解]
1、思考
- 之前的客户端是什么样的?
- 其实就是CS架构:客户端是需要我们自己开发实现的。
- BS结构是什么样的,需要开发客户端吗?
- 浏览器访问服务端,不需要开发客户端。
2、实现BS开发
注意:服务器必须给浏览器响应HTTP协议格式的数据,否则浏览器不识别。
HTTP响应数据的协议格式:就是给浏览器显示的网页信息:
package com.app.d11_tcp_socket6_thread_bs;
import java.net.ServerSocket;
import java.net.Socket;
/**
目标:理解BS结构的基本原理
*/
public class BSServerDemo {
public static void main(String[] args) {
System.out.println("---------服务端启动成功----------");
try {
// 1、创建ServerSocket对象,注册端口
ServerSocket serverSocket = new ServerSocket(8080);
// 3、使用死循环接收多个客户端的socket管道连接请求
while (true) {
// a. 开始接收多个客户端的socket管道连接请求
Socket socket = serverSocket.accept();
// b. 将socket管道连接请求交给一个独立的线程来处理
new ServerReaderThread(socket).start();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
package com.app.d11_tcp_socket6_thread_bs;
import java.io.PrintStream;
import java.net.Socket;
/**
2、创建线程任务类处理多个客户端的Socket管道连接请求
*/
public class ServerReaderThread extends Thread{
// a. 定义socket变量,用于存储接收到的客户端的socket管道连接请求
private Socket socket;
// b. 定义有参数构造器,用于接收客户端的socket管道连接请求,并初始化给socket变量
public ServerReaderThread(Socket socket) {
this.socket = socket;
}
/**
c. 重写run方法
*/
@Override
public void run() {
try {
// 浏览器已经与本线程建立了Socket管道
// 1、响应消息给浏览器显示
PrintStream ps = new PrintStream(socket.getOutputStream());
// 2、必须响应HTTP协议格式数据,否则浏览器不认识消息
ps.println("HTTP/1.1 200 OK"); // 协议类型/版本 状态码 响应成功的消息
ps.println("Content-Type:text/html;charset=UTF-8"); // 响应的数据类型:文本/网页;字符编码
ps.println(); // 必须发送一个空行
// 3、响应数据回去给浏览器
ps.println("<span style='color:red;font-size:90px'> 《我是歌手》 </span>");
ps.close(); // 释放资源!
} catch (Exception e) {
e.printStackTrace();
}
}
}
测试
4、线程池优化BS
package com.app.d12_tcp_socket7_threadpool_bs;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.*;
/**
目标:理解BS结构的基本原理,使用线程池优化BS
*/
public class BSServerDemo {
// 定义静态的线程池,用于处理多个客户端的socket管道连接请求
public static final ExecutorService pools = new ThreadPoolExecutor(3,
5, 6, TimeUnit.SECONDS, new ArrayBlockingQueue<>(3),
Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
public static void main(String[] args) {
System.out.println("---------服务端启动成功----------");
try {
// 1、创建ServerSocket对象,注册端口
ServerSocket serverSocket = new ServerSocket(8080);
// 3、使用死循环接收多个客户端的socket管道连接请求
while (true) {
// a. 开始接收多个客户端的socket管道连接请求
Socket socket = serverSocket.accept();
// b. 将socket管道连接请求交给线程池来处理
pools.execute(new ServerReaderRunnable(socket));
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
package com.app.d12_tcp_socket7_threadpool_bs;
import java.io.PrintStream;
import java.net.Socket;
/**
2、创建线程任务类处理多个客户端的Socket管道连接请求
*/
public class ServerReaderRunnable implements Runnable{
// a. 定义socket变量,用于存储接收到的客户端的socket管道连接请求
private Socket socket;
// b. 定义有参数构造器,用于接收客户端的socket管道连接请求,并初始化给socket变量
public ServerReaderRunnable(Socket socket) {
this.socket = socket;
}
/**
c. 重写run方法
*/
@Override
public void run() {
try {
// 浏览器已经与本线程建立了Socket管道
// 1、响应消息给浏览器显示
PrintStream ps = new PrintStream(socket.getOutputStream());
// 2、必须响应HTTP协议格式数据,否则浏览器不认识消息
ps.println("HTTP/1.1 200 OK"); // 协议类型/版本 状态码 响应成功的消息
ps.println("Content-Type:text/html;charset=UTF-8"); // 响应的数据类型:文本/网页;字符编码
ps.println(); // 必须发送一个空行
// 3、响应数据回去给浏览器
ps.println("<span style='color:red;font-size:90px'> 《我是歌手》 </span>");
ps.close(); // 释放资源!
} catch (Exception e) {
e.printStackTrace();
}
}
}
测试
总结
1、TCP通信如何实现BS请求网页信息回来?
- 客户端使用浏览器发起请求(不需要开发客户端)
- 服务端必须按照浏览器的协议规则响应数据
- 浏览器使用什么协议规则呢?
- HTTP协议(简单了解)
最后
以上就是清爽小海豚为你收集整理的107-Java网络编程:TCP通信实战案例:即时通信、BS架构模拟的全部内容,希望文章能够帮你解决107-Java网络编程:TCP通信实战案例:即时通信、BS架构模拟所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复