TCP协议
TCP协议特点
使用TCP协议,必须双方先建立连接,它是一种面向连接的可靠通信协议。
传输前,采用“三次握手”方式建立连接,所以是可靠的 。
在连接中可进行大数据量的传输 。
连接、发送数据都需要确认,且传输完毕后,还需释放已建立的连接,通信效率较低。
TCP协议通信场景
对信息安全要求较高的场景,例如:文件下载、金融等数据通信
TCP是一种面向连接,安全、可靠的传输数据的协议
传输前,采用“三次握手”方式,点对点通信,是可靠的
在连接中可进行大数据量的传输
注意:在java中只要是使用java.net.Socket类实现通信,底层即是使用了TCP协议
TCP通信-一发一收消息
客户端发送消息 [一发 一收]
需求:客户端实现步骤
创建客户端的Socket对象,请求与服务端的连接。
使用socket对象调用getOutputStream()方法得到字节输出流。
使用字节输出流完成数据的发送。
释放资源:关闭socket管道。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | 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(); } } } |
TCP通信的客户端的代表类是谁?
Socket类
public Socket(String host , int port)
TCP通信如何使用Socket管道发送、接收数据。
OutputStream getOutputStream():获得字节输出流对象(发)
InputStream getInputStream():获得字节输入流对象(收)
服务端
服务端实现接收消息
需求:服务端实现步骤
创建ServerSocket对象,注册服务端端口。
调用ServerSocket对象的accept()方法,等待客户端的连接,并得到Socket管道对象。
通过Socket对象调用getInputStream()方法得到字节输入流、完成数据的接收。
释放资源:关闭socket管道
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | 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(); } } } |
===服务端启动成功===
/127.0.0.1:49454说了:: 我是TCP的客户端,我已经与你对接,并发出邀请:约吗?
====客户端启动===
TCP通信服务端用的代表类?
ServerSocket类,注册端口。
调用accept()方法阻塞等待接收客户端连接。得到Socket对象。
TCP通信的基本原理?
客户端怎么发,服务端就应该怎么收。
客户端如果没有消息,服务端会进入阻塞等待。
Socket一方关闭或者出现异常、对方Socket也会失效或者出错。
TCP通信-多发多收消息
需求:使用TCP通信方式实现:多发多收消息。
具体要求:
可以使用死循环控制服务端收完消息继续等待接收下一个消息。
客户端也可以使用死循环等待用户不断输入消息。
客户端一旦输入了exit,则关闭客户端程序,并释放资源。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | 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(); } } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | 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(); } } } |
===服务端启动成功===
/127.0.0.1:50221说了:: 你好
/127.0.0.1:50221说了:: 在哪呢
/127.0.0.1:50221说了:: 退出
/127.0.0.1:50221说了:: exit
====客户端启动===
请说:
你好
请说:
在哪呢
请说:
退出
请说:
exit
请说
本案例实现了多发多收,那么是否可以同时接收多个客户端的消息?
不可以的。
因为服务端现在只有一个线程,只能与一个客户端进行通信。
本次多发多收是如何实现的
客户端使用循环反复地发送消息。
服务端使用循环反复地接收消息。
现在服务端为什么不可以同时接收多个客户端的消息。
目前服务端是单线程的,每次只能处理一个客户端的消息。
TCP通信-同时接受多个客户端消息
1、之前我们的通信是否可以同时与多个客户端通信,为什么?
不可以的
单线程每次只能处理一个客户端的Socket通信
2、如何才可以让服务端可以处理多个客户端的通信需求?
引入多线程。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | 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(); } } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | 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(); } } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | 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() + "下线了!!!" ); } } } |
===服务端启动成功===
/127.0.0.1:50340它来了,上线了!
/127.0.0.1:50340说了:: 12
/127.0.0.1:50348它来了,上线了!
/127.0.0.1:50348说了:: 12
/127.0.0.1:50340说了:: 13
====客户端启动===
请说:
12
请说:
13
请说:
====客户端启动===
请说:
12
请说:
本次是如何实现服务端接收多个客户端的消息的。
主线程定义了循环负责接收客户端Socket管道连接
每接收到一个Socket通信管道后分配一个独立的线程负责处理它。
1、目前的通信架构存在什么问题?
客户端与服务端的线程模型是: N-N的关系。
客户端并发越多,系统瘫痪的越快。
线程池处理多个客户端消息
本次使用线程池的优势在哪里?
服务端可以复用线程处理多个客户端,可以避免系统瘫痪。
适合客户端通信时长较短的场景。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | 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(); } } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | 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()+ "它来了,上线了!" ); // 任务对象负责读取消息。 Runnable target = new ServerReaderRunnable(socket); pool.execute(target); } } catch (Exception e) { e.printStackTrace(); } } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | 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() + "下线了!!!" ); } } } |
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· 清华大学推出第四讲使用 DeepSeek + DeepResearch 让科研像聊天一样简单!
· 实操Deepseek接入个人知识库
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库