Java --> 网络编程2【TCP通信】
步骤:客户端发送数据
需求:
客户端实现步骤:
- 创建客户端的Soecket对象,请求与服务端的连接
- 使用Socket对象调用getOutputStream( )方法得到字节输出流
- 使用字节输出流完成数据的发送
- 释放资源:关闭socket管道
服务端实现步骤:
- 创建ServerSocket对象,注册服务端端口
- 调用ServerSocket对象的accept()方法,等待客户端的连接,并得到Socket管道对象
- 通过Socket对象调用getInputStream()方法得到字节输入流,完成数据的接收
- 释放资源:关闭socket管道
1 import java.io.BufferedReader;
2 import java.io.InputStream;
3 import java.io.InputStreamReader;
4 import java.net.ServerSocket;
5 import java.net.Socket;
6
7 //开发Socket网络编程入门代码的服务端,实现接收消息
8 public class ServerDemo2 {
9 public static void main(String[] args) {
10 System.out.println("————————————服务端启动——————————————");
11 try {
12 //1、注册服务端的端口
13 ServerSocket serverSocket = new ServerSocket(5555);
14
15 //2、调用accept方法,等待接收客户端的Socket连接请求,建立Socket通道
16 Socket socket = serverSocket.accept();
17
18 //3、从socket管道中得到一个字节输出流
19 InputStream inputStream = socket.getInputStream();
20
21 //4、把字符输入流包装成换成字符输入流进行消息的接收
22 BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
23
24 //5、按照行来读取消息
25 String str;
26 if ((str = bufferedReader.readLine()) != null){
27 System.out.print(socket.getRemoteSocketAddress() + "说了:" + str);
28 }
29 } catch (Exception e) {
30 e.printStackTrace();
31 }
32 }
33 }
1 import java.io.OutputStream;
2 import java.io.PrintStream;
3 import java.net.Socket;
4
5 //完成Socket网络编程入门案例的客户端开发,实现一发、一收
6 public class ClientDemo1 {
7 public static void main(String[] args) {
8 System.out.println("————————————客户端启动——————————————");
9 try {
10 //1、创建Socket管道请求与服务端连接
11 //public Socket(String host, int port)
12 /**
13 * host : 服务器地址【主机】
14 * port : 服务端的端口
15 */
16 Socket socket = new Socket("127.0.0.1",5555);
17 //2、从socket通信管道中得到一个字节输出流,负责发送数据
18 OutputStream outputStream = socket.getOutputStream();
19
20 //3、把低级的字节流包装成打印流
21 PrintStream printStream = new PrintStream(outputStream);
22
23 //4、发送消息
24 //此处的println <-->对应服务端的newLine()
25 //print <--> 对应服务端的print
26 printStream.println("我是TCP的客户端,此时已经与你建立连接,并发出邀请~");
27 printStream.flush();
28
29 //5、关闭资源
30 //socket.close();
31 } catch (Exception e) {
32 e.printStackTrace();
33 }
34 }
35 }
- 案例:实现TCP通信实现:多发、多收消息
- 可以使用死循环控制服务端收完消息继续等待下一条消息
- 客户端也可以使用死循环等在用户不断输入消息
- 客户端一旦输入了exit,则关闭客户端程序,并释放资源
同样是上述代码,做少许改动即可:
客户端:-->
服务端 -->
缺点:因为现在服务端只有一个线程,只能与一个客户端进行通信,所以不可以实现多收【并发】
改进:TCP通信 -- 同时可以接收多个客户端的消息【重点】
- 引入多线程
模型:
实现思路:
- 主线程定义了循环负责接收客户端Socket管道连接
- 每接受到一个Socket通信管道后分配一个独立的线程负责处理它
1 import java.net.ServerSocket;
2 import java.net.Socket;
3
4 //实现服务端可以同时处理多个客户的消息
5 public class ServerDemo2 {
6 public static void main(String[] args) {
7 System.out.println("————————————服务端启动——————————————");
8 try {
9 //1、注册服务端的端口
10 ServerSocket serverSocket = new ServerSocket(5555);
11 //定义一个死循环由主线程负责不断地接收客户端的socket连接请求,建立socket管道
12 while(true){
13 //2、调用accept方法,等待接收客户端的Socket连接请求,建立Socket通道
14 Socket socket = serverSocket.accept();
15 //每接收到一个客户端的Socket管道,交给一个独立的子线程负责读取消息
16 System.out.println(socket.getRemoteSocketAddress() + "来了,上线了~");
17 //3、创建独立线程并处理socket
18 new ServerReaderThread(socket).start();
19 }
20 } catch (Exception e) {
21 e.printStackTrace();
22 }
23 }
24 }
1 import java.io.*;
2 import java.net.Socket;
3
4 public class ServerReaderThread extends Thread{
5 private Socket socket;
6 public ServerReaderThread(Socket socket){
7 this.socket = socket;
8 }
9 @Override
10 public void run() {
11 try{
12 //从socket管道中得到一个字节输入流
13 InputStream inputStream = socket.getInputStream();
14 //把字节输入流包装成缓冲字节输入流进行消息的接收
15 BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
16 //按行来读取消息
17 String message;
18 while ((message = bufferedReader.readLine()) != null){
19 System.out.println(socket.getRemoteSocketAddress() + "说了:" + message);
20 }
21 }catch (Exception e){
22 //e.printStackTrace(); //出异常代表此客户端已经下线
23 System.out.println(socket.getRemoteSocketAddress() + "下线了");
24 }
25 }
26 }
1 import java.io.OutputStream;
2 import java.io.PrintStream;
3 import java.net.Socket;
4 import java.util.Scanner;
5
6 //完成Socket网络编程入门案例的客户端开发,实现一发、一收
7 public class ClientDemo1 {
8 public static void main(String[] args) {
9 System.out.println("————————————客户端启动——————————————");
10 try {
11 //1、创建Socket管道请求与服务端连接
12 //public Socket(String host, int port)
13 /**
14 * host : 服务器地址【主机】
15 * port : 服务端的端口
16 */
17 Socket socket = new Socket("127.0.0.1",5555);
18 //2、从socket通信管道中得到一个字节输出流,负责发送数据
19 OutputStream outputStream = socket.getOutputStream();
20
21 //3、把低级的字节流包装成打印流
22 PrintStream printStream = new PrintStream(outputStream);
23
24 //4、发送消息
25 //此处的println <-->对应服务端的newLine()
26 //print <--> 对应服务端的print
27 Scanner sc = new Scanner(System.in);
28 while (true){
29 System.out.println("请说:");
30 String message = sc.nextLine();
31 if ("exit".equals(message)){
32 break;
33 }
34 printStream.println(message);
35 printStream.flush();
36 }
37 } catch (Exception e) {
38 e.printStackTrace();
39 }
40 }
41 }
上述过程实现了TCP通信的多发、多收 。
存在问题:客户端与服务端的线程模型是N - N的关系,解决方法 --> 引入线程池处理多个客户端消息
1 import java.io.OutputStream;
2 import java.io.PrintStream;
3 import java.net.Socket;
4 import java.util.Scanner;
5
6 //拓展 : 使用线程池优化,解决TCP通信模式
7 public class ClientDemo1 {
8 public static void main(String[] args) {
9 System.out.println("----------客户端启动------------");
10 try{
11 //1、创建Socket通信管道,请求与服务端进行连接
12 Socket socket = new Socket("127.0.0.1",5656);
13 //2、从socket管道中得到一个字节输出流,负责发送数据
14 OutputStream outputStream = socket.getOutputStream();
15 //3、把低级的字节流包装成为打印流
16 PrintStream printStream = new PrintStream(outputStream);
17
18 Scanner sc = new Scanner(System.in);
19 while (true){
20 System.out.println("请说:");
21 String message = sc.nextLine();
22 if ("exit".equals(message)){
23 break;
24 }
25 printStream.println(message);
26 printStream.flush();
27 }
28 }catch (Exception e){
29 e.printStackTrace();
30 }
31 }
32 }
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.*;
//服务端可以实现
public class ServerDemo2 {
//使用静态变量定义一个线程池
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) {
System.out.println("--------服务端启动----------");
try{
//注册端口
ServerSocket serverSocket = new ServerSocket(5656);
//定义死循环,每接收到一个客户端的socket管道,把它交给一个独立的子线程进行处理
while (true) {
Socket socket = serverSocket.accept();
System.out.println(socket.getRemoteSocketAddress() + "来了,上线了~");
Runnable target = new ServerReaderRunnable(socket); //充当任务
pool.execute(target);
}
}catch (Exception e){
e.printStackTrace();
}
}
}
1 import java.io.BufferedReader;
2 import java.io.InputStream;
3 import java.io.InputStreamReader;
4 import java.net.Socket;
5
6 public class ServerReaderRunnable implements Runnable{
7 private Socket socket;
8 public ServerReaderRunnable(Socket socket){
9 this.socket = socket;
10 }
11 @Override
12 public void run() {
13 try{
14 //从socket管道中得到一个字节输入流,接收数据
15 InputStream inputStream = socket.getInputStream();
16 //把字节输入流包装成缓冲字符输入流
17 BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
18 //一行一行地读取数据
19 String message;
20 while((message = bufferedReader.readLine()) != null){
21 System.out.println(socket.getRemoteSocketAddress() + "说:" + message);
22 }
23 }catch (Exception e){
24 System.out.println(socket.getRemoteSocketAddress() + "下线了~");
25 }
26 }
27 }
优点:
- 服务端可以复用线程处理多个客户端,可以避免系交通瘫痪
- 适合客户端通信时长较短地场景【通信较长则会长时间占用线程,导致其它任务不能被服务】
案例:TCP通信实战案例 - - 即时通信
- 即时通信,是指一个客户端的消息发送出去,其它客户端可以接收到
- 即时通信需要进行端口转发的设计思想
- 服务端需要把在线的Socket管道存储起来
- 一旦接收到一个消息要推送给其它设备管理
1 import java.io.OutputStream;
2 import java.io.PrintStream;
3 import java.net.Socket;
4 import java.util.Scanner;
5
6 /**
7 * 1、客户端发送消息、
8 * 2、客户端收消息
9 */
10 public class ClientDemo1 {
11 public static void main(String[] args) {
12 System.out.println("-------------客户端启动---------------");
13 try{
14 //创建socket管道,与服务端建立连接
15 Socket socket = new Socket("127.0.0.1",5252);
16 //创建一个独立地线程专门用来负责读取从来自服务端地消息
17 ClientReadThread clientReadThread= new ClientReadThread(socket);
18 clientReadThread.start();
19 //从socket管道中得到一个字节输出流管道
20 OutputStream outputStream = socket.getOutputStream();
21 //把低级地输出流包装成为打印流
22 PrintStream printStream = new PrintStream(outputStream);
23 //发送消息
24 Scanner sc = new Scanner(System.in);
25 while (true){
26 String message;
27 message = sc.nextLine();
28 if ("exit".equals(message)){
29 break;
30 }
31 printStream.println(message);
32 printStream.flush();
33 }
34 }catch (Exception e){
35 e.printStackTrace();
36 }
37 }
38 }
1 import java.io.BufferedReader;
2 import java.io.InputStream;
3 import java.io.InputStreamReader;
4 import java.net.Socket;
5
6 public class ClientReadThread extends Thread{
7 private Socket socket;
8 public ClientReadThread(Socket socket){
9 this.socket = socket;
10 }
11 @Override
12 public void run() {
13 try{
14 //创建字节输入流管道从socket中地到一个字节输入流对象
15 InputStream inputStream = socket.getInputStream();
16 //把低级地字节输入流包装成缓冲字符输入流
17 BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
18 //按照行进行读取
19 String message;
20 while((message = bufferedReader.readLine()) != null){
21 System.out.println("收到消息:" + message);
22 }
23 }catch (Exception e){
24 //出异常表示服务端把该线程移除掉了
25 System.out.println("服务端把你踢出去了");
26 }
27 }
28 }
1 import java.net.ServerSocket;
2 import java.net.Socket;
3 import java.util.ArrayList;
4 import java.util.List;
5
6 //服务端
7 public class ServerDemo2 {
8 //定义一个静态的List集合用于存储所有的客户端管道
9 public static List<Socket> allClientSockets = new ArrayList<>();
10 public static void main(String[] args) {
11 System.out.println("---------服务端启动----------");
12 try {
13 //注册端口
14 ServerSocket serverSocket = new ServerSocket(5252);
15 //定义死循环不断地接收客户端的连接请求
16 //注:在这里等待客户端的socket管道连接
17 while (true) {
18 Socket socket = serverSocket.accept();
19 allClientSockets.add(socket);
20 ServerReadThread target = new ServerReadThread(socket);
21 target.start();
22 }
23 } catch (Exception e) {
24 e.printStackTrace();
25 }
26 }
27 }
1 import java.io.*;
2 import java.net.Socket;
3
4 public class ServerReadThread extends Thread{
5 private Socket socket;
6 public ServerReadThread(Socket socket){
7 this.socket = socket;
8 }
9 @Override
10 public void run() {
11 try{
12 //字节输入流
13 InputStream inputStream = socket.getInputStream();
14 //把低级的输入流包装成缓冲字符输入流
15 BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
16 //按照行来进行读取
17 String message;
18 while ((message = bufferedReader.readLine()) != null){
19 System.out.println(socket.getRemoteSocketAddress() + "来了~~~");
20 //把这个消息进行端口转发给全部客户端的socket管道
21 sendMessageToAll(message);
22 }
23 }catch (Exception e){
24 //出现异常表示此用户已经下线
25 System.out.println(socket.getRemoteSocketAddress() + "下线了~~~");
26 //从集合中移除改用户
27 System.out.println(ServerDemo2.allClientSockets.remove(socket));
28 }
29 }
30
31 //转发消息给所有人
32 private void sendMessageToAll(String message) throws Exception{
33 for (Socket socket : ServerDemo2.allClientSockets) {
34 //得到每个socket管道的输出流
35 OutputStream outputStream = socket.getOutputStream();
36 //把低级的字节输出流管道包装成打印流
37 PrintStream printStream = new PrintStream(outputStream);
38 printStream.println(message);
39 printStream.flush();
40 }
41 }
42 }
- TCP通信实战案例 -- 模拟BS系统【了解】
特点:
- 客户端使用浏览器发起请求【不需要开发客户端】
- 服务端必须按照浏览器的协议规则响应数据
1 import java.net.ServerSocket;
2 import java.net.Socket;
3 import java.util.concurrent.*;
4
5 //使用TCP模拟BS架构通信系统
6 public class BSserverDemo {
7 //定义一个静态变量记住一个线程池对象
8 public static ExecutorService pool = new ThreadPoolExecutor(3,5,6, TimeUnit.SECONDS,
9 new ArrayBlockingQueue<>(2), Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());
10 public static void main(String[] args) {
11 System.out.println("------服务端启动--------");
12 try{
13 //1、注册端口
14 ServerSocket serverSocket = new ServerSocket(8080); //连接浏览器
15 //2、创建死循环可以接收多个客户端的请求
16 while (true){
17 //每接收到一个客户端的管道都将其做成任务,扔到线程池中【达到线程复用】
18 Socket socket = serverSocket.accept();
19 ServerReaderRunnable target = new ServerReaderRunnable(socket);
20 pool.execute(target);
21 }
22 }catch (Exception e){
23 e.printStackTrace();
24 }
25 }
26 }
1 import java.io.*;
2 import java.net.Socket;
3
4 public class ServerReaderRunnable implements Runnable{
5 private Socket socket;
6 public ServerReaderRunnable(Socket socket){
7 this.socket = socket;
8 }
9 @Override
10 public void run() {
11 try{
12 //浏览器已经与本线程建立了通信管道
13 //相应消息给浏览器显示
14 OutputStream outputStream = socket.getOutputStream();
15 PrintStream printStream = new PrintStream(outputStream);
16 //必须相应HTTP协议格式数据,否则浏览器不认识信息
17 printStream.println("HTTP/1.1 200 OK"); //协议类型和版本、响应成功的消息
18 printStream.println("Content-Type:text/html;charset=UTF-8"); //响应的数据类型:文本/网页
19 printStream.println(); //必须发送要给空行
20 //响应数据给浏览器
21 printStream.println("<span style='color:green;font-size:50px'>我是服务器返回给客户端的一条数据</span>" ); //响应正文
22 printStream.close();
23 }catch (Exception e){
24 e.printStackTrace();
25 }
26 }
27 }
1 //不使用线程池
2
3 //import java.io.OutputStream;
4 //import java.io.PrintStream;
5 //import java.net.Socket;
6 //
7 //public class ServerReaderThread extends Thread{
8 // private Socket socket;
9 // public ServerReaderThread(Socket socket){
10 // this.socket = socket;
11 // }
12 // //创建输出流给网页
13 // @Override
14 // public void run() {
15 // try{
16 // //浏览器已经与本线程建立了通信管道
17 // //相应消息给浏览器显示
18 // OutputStream outputStream = socket.getOutputStream();
19 // PrintStream printStream = new PrintStream(outputStream);
20 // //必须相应HTTP协议格式数据,否则浏览器不认识信息
21 // printStream.println("HTTP/1.1 200 OK"); //协议类型和版本、响应成功的消息
22 // printStream.println("Content-Type:text/html;charset=UTF-8"); //响应的数据类型:文本/网页
23 // printStream.println(); //必须发送要给空行
24 // //响应数据给浏览器
25 // printStream.println("<span style='color:red;font-size:50px'>我是服务器返回给客户端的一条数据</span>" ); //响应正文
26 // printStream.close();
27 // }catch (Exception e){
28 // e.printStackTrace();
29 // }
30 // }
31 //}
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程使用 AI 从 0 到 1 写了个小工具
· 快收藏!一个技巧从此不再搞混缓存穿透和缓存击穿
· AI 插件第二弹,更强更好用
· Blazor Hybrid适配到HarmonyOS系统
· 支付宝 IoT 设备入门宝典(下)设备经营篇