步骤:客户端发送数据
需求:
客户端实现步骤:
- 创建客户端的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 //}
原创文章,作者:,如若转载,请注明出处:https://blog.ytso.com/277240.html