Java网络编程 - TCP通信
TCP通信
快速入门(一发一收)
TCP协议回顾:
TCP是一种面向连接,安全、可靠的传输数据的协议
传输前,采用“三次握手”方式,点对点通信,是可靠的
在连接中可进行大数据量的传输
TCP通信模式:
在java中只要是使用java.net.Socket类实现通信,底层即是使用了TCP协议
编写客户端代码
Socket(客户端):
构造器 | 说明 |
---|---|
Socket(String host , int port) | 创建发送端的Socket对象与服务端连接,参数为服务端程序的ip和端口。 |
Socket类成员方法:
方法 | 说明 |
---|---|
OutputStream getOutputStream() | 获得字节输出流对象 |
InputStream getInputStream() | 获得字节输入流对象 |
客户端实现步骤:
- 创建客户端的Socket对象,请求与服务端的连接。
- 使用socket对象调用getOutputStream()方法得到字节输出流。
- 使用字节输出流完成数据的发送。
- 不建议直接关闭socket管道释放资源, 一般用户退出时才会关闭。
public class ClientDemo { public static void main(String[] args) { try { // 1. 创建socket通信管道请求与服务端进行连接 Socket socket = new Socket("127.0.0.1", 7777); // 2. 从socket通信管道中获取到字节输出流 OutputStream os = socket.getOutputStream(); // 包裹低级字节输出流为字节打印流 PrintStream ps = new PrintStream(os); // 3. 打印流发送消息 ps.println("我是TCP的客户端"); ps.flush(); // 刷新 } catch (Exception e) { e.printStackTrace(); } }}
编写服务器代码
ServerSocket(服务端):
构造器 | 说明 |
---|---|
ServerSocket(int port) | 注册服务端端口 |
ServerSocket类成员方法:
方法 | 说明 |
---|---|
Socket accept() | 等待接收客户端的Socket通信连接 连接成功返回Socket对象与客户端建立端到端通信 |
服务端实现步骤:
- 创建ServerSocket对象,注册服务端端口。
- 调用ServerSocket对象的accept()方法,等待客户端的连接,并得到Socket管道对象。
- 通过Socket对象调用getInputStream()方法得到字节输入流、完成数据的接收。
- 不建议直接关闭socket管道释放资源, 一般用户退出时才会关闭。
public class ServerDemo { public static void main(String[] args) { try { // 1. 创建ServerSocket对象注册服务器端口 ServerSocket serverSocket = new ServerSocket(7777); // 2. 调用accept方法, 等待客户端连接, 连接成功返回socket管道对象 Socket socket = serverSocket.accept(); // 3. 从socket管道中获取字节输入流, 完成数据接受 InputStream is = socket.getInputStream(); // 把字节输入流包装为缓冲字符输入流进行消息接收 BufferedReader br = new BufferedReader(new InputStreamReader(is)); // 按照行读取 String message; if ((message = br.readLine()) != null) { System.out.println(message); } } catch (Exception e) { e.printStackTrace(); } }}
多发多收
需求:
- 使用TCP通信方式实现:多发多收消息。
具体要求:
- 可以使用死循环控制服务端收完消息继续等待接收下一个消息。
- 客户端也可以使用死循环等待用户不断输入消息。
- 客户端一旦输入了exit,则关闭客户端程序,并释放资源。
客户端
public class ClientDemo { public static void main(String[] args) { try { Socket socket = new Socket("127.0.0.1", 7777); OutputStream os = socket.getOutputStream(); PrintStream ps = new PrintStream(os); // 客户端使用死循环等待用户不断地输入消息 Scanner scanner = new Scanner(System.in); while (true) { System.out.println("发送消息: "); String inp = scanner.nextLine(); // 一旦输入了exit,则关闭客户端程序,并释放资源 if (inp.equals("exit")) { System.out.println("下线成功"); ps.close(); break; } ps.println(inp); ps.flush(); } } catch (Exception e) { e.printStackTrace(); } }}
服务端
public class ServerDemo { public static void main(String[] args) { try { ServerSocket serverSocket = new ServerSocket(7777); Socket socket = serverSocket.accept(); InputStream is = socket.getInputStream(); BufferedReader br = new BufferedReader(new InputStreamReader(is)); String message; // 死循环控制服务端收完消息继续等待接收下一个消息 while ((message = br.readLine()) != null) { System.out.println("收到消息: " + message); } } catch (Exception e) { e.printStackTrace(); } }}
多发多收(同时接受多个客户端)
思考: 案例实现了多发多收,那么是否可以同时接收多个客户端的消息?
不可以的。
因为服务端现在只有一个线程,只能与一个客户端进行通信; 并且上面代码中, 我们只连接了一个客户端然后就在死循环接受消息。
那么如何才可以让服务端可以处理多个客户端的通信需求?
引入多线程。
同时处理多个客户端消息实现架构如下:
主线程死循环不断地接收socket链接, 每成功链接一个socket, 就交给子线程处理
实现步骤如下:
优化服务器代码即可
创建一个线程类, 用来处理接收消息
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 message; // 死循环控制服务端收完消息继续等待接收下一个消息 while ((message = br.readLine()) != null) { System.out.println("收到消息: " + message); } } catch (Exception e) { e.printStackTrace(); } }}
在服务器主线程中, 每链接到一个socket都要创建一个线程类交给子线程处理
public class ServerDemo { public static void main(String[] args) { try { ServerSocket serverSocket = new ServerSocket(7777); // 1. 主线程中定义一个死循环由主线程不断地接收客户端socket管道连接 while (true) { // 2. 每接收到一个socket管道, 都交给一个独立的子线程负责读取消息 Socket socket = serverSocket.accept(); // 交给子线程处理, 并启动子线程 new ServerReaderThread(socket).start(); } } catch (Exception e) { e.printStackTrace(); } }}
线程池优化
目前的通信架构存在什么问题?
客户端与服务端的线程模型是: 1-1的关系, 有多少客户端就会创建多少线程。
客户端并发越多,系统瘫痪的越快。
引入线程池处理多个客户端消息的架构如下:
线程池优化多发多收, 我们只需要优化服务器的代码即可:
创建一个Runnable任务类
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 message; // 死循环控制服务端收完消息继续等待接收下一个消息 while ((message = br.readLine()) != null) { System.out.println(socket.getRemoteSocketAddress() + "收到消息: " + message); } } catch (Exception e) { e.printStackTrace(); } }}
优化服务器端代码
public class ServerDemo { // 使用静态变量记录一个线程池对象 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 { ServerSocket serverSocket = new ServerSocket(7777); while (true) { Socket socket = serverSocket.accept(); System.out.println(socket.getRemoteSocketAddress() + "上线了"); // 创建Runnable任务交给线程池处理 pool.execute(new ServerReaderRunnable(socket)); } } catch (Exception e) { e.printStackTrace(); } }}
线程池优势是什么?
服务端可以复用线程处理多个客户端,可以避免系统瘫痪。
适合客户端通信时长较短的场景。
计思想, 客户端将消息发送给服务器, 再由服务器进行转发给其他客户端。
来源地址:https://blog.csdn.net/m0_71485750/article/details/127720523
免责声明:
① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。
② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341