当前位置:   article > 正文

Socket网络编程学习笔记 (9)TCP数据发送与接收并行_socket发送tcp数据包

socket发送tcp数据包

主要实现:

  • 多线程收发并行
  • TCP多线程收发协作

1. TCP 服务端收发并行重构

1.1 启动main方法重构

原有的main逻辑如下:

 重构后如下:

  1. /**
  2. * @ClassName Server
  3. * @Description TODO
  4. * @Author wushaopei
  5. * @Date 2022/2/27 13:00
  6. * @Version 1.0
  7. */
  8. public class Server {
  9. public static void main(String[] args) throws IOException {
  10. TCPServer tcpServer = new TCPServer(TCPConstants.PORT_SERVER);
  11. boolean isSucceed = tcpServer.start();
  12. if(!isSucceed){
  13. System.out.println("Start TCP server failed.");
  14. }
  15. UDPProvider.start(TCPConstants.PORT_SERVER);
  16. // 键盘输入:
  17. BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
  18. String str;
  19. do {
  20. str = bufferedReader.readLine();
  21. tcpServer.broadcast(str);
  22. } while (!"00bye00".equalsIgnoreCase(str));
  23. UDPProvider.stop();
  24. tcpServer.stop();
  25. }
  26. }

重构后,从while循环不断读取键盘输入信息,当输入“00bye00” 时退出读取。此处只读取键盘输入数据,客户端发送的数据在会重新拆分出来新的线程单独处理。

1.2 重构分离收发消息的操作

创建 ClientHandler.java 重构收发消息操作:

  1. /**
  2. * @ClassName ClientHandler
  3. * @Description TODO
  4. * @Author wushaopei
  5. * @Date 2022/2/27 16:44
  6. * @Version 1.0
  7. */
  8. public class ClientHandler {
  9. private final Socket socket;
  10. private final ClientReadHandler readHandler;
  11. private final ClientWriteHandler writeHandler;
  12. private final CloseNotiry closeNotiry;
  13. public ClientHandler(Socket socket, CloseNotiry closeNotiry ) throws IOException {
  14. this.socket = socket;
  15. this.readHandler = new ClientReadHandler(socket.getInputStream());
  16. this.writeHandler = new ClientWriteHandler(socket.getOutputStream());
  17. this.closeNotiry = closeNotiry;
  18. System.out.println("新客户链接: " + socket.getInetAddress() + "\tP:" + socket.getPort());
  19. }
  20. }

重构接收消息的操作:

  1. /**
  2. * 接收数据
  3. */
  4. class ClientReadHandler extends Thread {
  5. private boolean done = false;
  6. private final InputStream inputStream;
  7. ClientReadHandler(InputStream inputStream){
  8. this.inputStream = inputStream;
  9. }
  10. @Override
  11. public void run(){
  12. super.run();
  13. try {
  14. // 得到输入流,用于接收数据
  15. BufferedReader socketInput = new BufferedReader(new InputStreamReader(inputStream));
  16. do {
  17. // 客户端拿到一条数据
  18. String str = socketInput.readLine();
  19. if(str == null){
  20. System.out.println("客户端已无法读取数据!");
  21. // 退出当前客户端
  22. ClientHandler.this.exitBySelf();
  23. break;
  24. }
  25. // 打印到屏幕
  26. System.out.println(str);
  27. }while (!done);
  28. socketInput.close();
  29. }catch (IOException e){
  30. if(!done){
  31. System.out.println("连接异常断开");
  32. ClientHandler.this.exitBySelf();
  33. }
  34. }finally {
  35. // 连接关闭
  36. CloseUtils.close(inputStream);
  37. }
  38. }
  39. void exit(){
  40. done = true;
  41. CloseUtils.close(inputStream);
  42. }
  43. }

创建一个单独的线程进行接收消息,该线程不需要关闭。

重构发送消息:

  1. /**
  2. * 发送数据
  3. */
  4. class ClientWriteHandler {
  5. private boolean done = false;
  6. private final PrintStream printStream;
  7. private final ExecutorService executorService;
  8. ClientWriteHandler(OutputStream outputStream) {
  9. this.printStream = new PrintStream(outputStream);
  10. // 发送消息使用线程池来实现
  11. this.executorService = Executors.newSingleThreadExecutor();
  12. }
  13. void exit(){
  14. done = true;
  15. CloseUtils.close(printStream);
  16. executorService.shutdown();
  17. }
  18. void send(String str) {
  19. executorService.execute(new WriteRunnable(str));
  20. }
  21. class WriteRunnable implements Runnable{
  22. private final String msg;
  23. WriteRunnable(String msg){
  24. this.msg = msg;
  25. }
  26. @Override
  27. public void run(){
  28. if(ClientWriteHandler.this.done){
  29. return;
  30. }
  31. try {
  32. ClientWriteHandler.this.printStream.println(msg);
  33. }catch (Exception e){
  34. e.printStackTrace();
  35. }
  36. }
  37. }
  38. }

TCPServer调用发送消息的逻辑:

  1. public void broadcast(String str) {
  2. for (ClientHandler client : clientHandlerList){
  3. // 发送消息
  4. client.send(str);
  5. }
  6. }

1.3 监听客户端链接逻辑重构

  1. private List<ClientHandler> clientHandlerList = new ArrayList<>();
  2. /**
  3. * 监听客户端链接
  4. */
  5. private class ClientListener extends Thread {
  6. private ServerSocket server;
  7. private boolean done = false;
  8. private ClientListener(int port) throws IOException {
  9. server = new ServerSocket(port);
  10. System.out.println("服务器信息: " + server.getInetAddress() + "\tP:" + server.getLocalPort());
  11. }
  12. @Override
  13. public void run(){
  14. super.run();
  15. System.out.println("服务器准备就绪~");
  16. // 等待客户端连接
  17. do{
  18. // 得到客户端
  19. Socket client;
  20. try {
  21. client = server.accept();
  22. }catch (Exception e){
  23. continue;
  24. }
  25. try {
  26. // 客户端构建异步线程
  27. ClientHandler clientHandler = new ClientHandler(client, handler -> clientHandlerList.remove(handler));
  28. // 启动线程
  29. clientHandler.readToPrint();
  30. clientHandlerList.add(clientHandler);
  31. } catch (IOException e) {
  32. e.printStackTrace();
  33. System.out.println("客户端连接异常: " + e.getMessage());
  34. }
  35. }while (!done);
  36. System.out.println("服务器已关闭!");
  37. }
  38. void exit(){
  39. done = true;
  40. try {
  41. server.close();
  42. }catch (IOException e){
  43. e.printStackTrace();
  44. }
  45. }
  46. }
clientHandlerList作为类变量,用于管理当前用户的信息。接收与发送都使用该变量。

1.4 Socket、流的退出与关闭:

  1. /**
  2. * 退出、关闭流
  3. */
  4. public void exit(){
  5. readHandler.exit();
  6. writeHandler.exit();
  7. CloseUtils.close(socket);
  8. System.out.println("客户端已退出:" + socket.getInetAddress() + "\tP:" + socket.getPort());
  9. }
  10. /**
  11. * 发送消息
  12. * @param str
  13. */
  14. public void send(String str){
  15. writeHandler.send(str);
  16. }
  17. /**
  18. * 接收消息
  19. */
  20. public void readToPrint() {
  21. readHandler.exit();
  22. }
  23. /**
  24. * 接收、发送消息异常,自动关闭
  25. */
  26. private void exitBySelf() {
  27. exit();
  28. closeNotiry.onSelfClosed(this);
  29. }
  30. /**
  31. * 关闭流
  32. */
  33. public interface CloseNotiry{
  34. void onSelfClosed(ClientHandler handler);
  35. }

2. TCP 客户端收发并行重构

2.1 客户端 main函数重构

  1. public static void main(String[] args) {
  2. // 定义10秒的搜索时间,如果超过10秒未搜索到,就认为服务器端没有开机
  3. ServerInfo info = UDPSearcher.searchServer(10000);
  4. System.out.println("Server:" + info);
  5. if( info != null){
  6. try {
  7. TCPClient.linkWith(info);
  8. }catch (IOException e){
  9. e.printStackTrace();
  10. }
  11. }
  12. }

2.2 客户端接收消息重构

  1. static class ReadHandler extends Thread{
  2. private boolean done = false;
  3. private final InputStream inputStream;
  4. ReadHandler(InputStream inputStream){
  5. this.inputStream = inputStream;
  6. }
  7. @Override
  8. public void run(){
  9. try {
  10. // 得到输入流,用于接收数据
  11. BufferedReader socketInput = new BufferedReader(new InputStreamReader(inputStream));
  12. do {
  13. // 客户端拿到一条数据
  14. String str = null;
  15. try {
  16. str = socketInput.readLine();
  17. }catch (SocketTimeoutException e){
  18. }
  19. if(str == null){
  20. System.out.println("连接已关闭,无法读取数据!");
  21. break;
  22. }
  23. // 打印到屏幕
  24. System.out.println(str);
  25. }while (!done);
  26. socketInput.close();
  27. }catch (IOException e){
  28. if(!done){
  29. System.out.println("连接异常断开:" + e.getMessage());
  30. }
  31. }finally {
  32. // 连接关闭
  33. CloseUtils.close(inputStream);
  34. }
  35. }
  36. void exit(){
  37. done = true;
  38. CloseUtils.close(inputStream);
  39. }
  40. }

创建ReadHandler用单独的线程去接收服务端的消息。连接关闭则exit() 关闭客户端。

2.3 客户端发送消息重构

  1. private static void write(Socket client) throws IOException {
  2. // 构建键盘输入流
  3. InputStream in = System.in;
  4. BufferedReader input = new BufferedReader(new InputStreamReader(in));
  5. // 得到Socket输出流,并转换为打印流
  6. OutputStream outputStream = client.getOutputStream();
  7. PrintStream socketPrintStream = new PrintStream(outputStream);
  8. boolean flag = true;
  9. do {
  10. // 键盘读取一行
  11. String str = input.readLine();
  12. // 发送到服务器
  13. socketPrintStream.println(str);
  14. // 从服务器读取一行
  15. if("00bye00".equalsIgnoreCase(str)){
  16. break;
  17. }
  18. }while(flag);
  19. // 资源释放
  20. socketPrintStream.close();
  21. }

在linkWith() 中调用write() 发送方法,由 do-while 循环读取本地键盘输入信息进行发送操作。当满足 “00bye00” 时,关闭循环,关闭socket连接,结束该线程。

2.4 客户端 linkWith 主方法重构

  1. public static void linkWith(ServerInfo info) throws IOException {
  2. Socket socket = new Socket();
  3. // 超时时间
  4. socket.setSoTimeout(3000);
  5. // 端口2000;超时时间300ms
  6. socket.connect(new InetSocketAddress(Inet4Address.getByName(info.getAddress()),info.getPort()));//
  7. System.out.println("已发起服务器连接,并进入后续流程~");
  8. System.out.println("客户端信息: " + socket.getLocalAddress() + "\tP:" + socket.getLocalPort());
  9. System.out.println("服务器信息:" + socket.getInetAddress() + "\tP:" + socket.getPort());
  10. try {
  11. ReadHandler readHandler = new ReadHandler(socket.getInputStream());
  12. readHandler.start();
  13. // 发送接收数据
  14. write(socket);
  15. }catch (Exception e){
  16. System.out.println("异常关闭");
  17. }
  18. // 释放资源
  19. socket.close();
  20. System.out.println("客户端已退出~");
  21. }

原有的逻辑里,是调用 todo() 方法,在todo() 方法里同时进行收发操作。现在是进行读写分离。

3. TCP 收发并行重构测试:

服务端重构后日志:

  1. J:\folder\JDK1.8\bin\java -ja…… - Channel\out\production\classes" server.Server
  2. 服务器信息: 0.0.0.0/0.0.0.0 P:30401
  3. 服务器准备就绪~
  4. UDDProvider Started.
  5. ServerProvider receive from ip:169.254.178.74 port:169.254.178.74 port:49878 dataValid:true
  6. ServerProvider response to:169.254.178.74 port:30202 dataLen: 50
  7. 新客户链接: /169.254.178.74 P:51094
  8. ping
  9. pong
  10. 00bye0000bye00
  11. 客户端已无法读取数据!
  12. 客户端已退出:/169.254.178.74 P:51094

客户端重构后执行日志:

  1. J:\folder\JDK1.8\bin\java -javaagent:J:\……- Channel\out\production\classes" client.Client
  2. UDPSearcher Started.
  3. UDPSearcher start listen.
  4. UDPSearcher sendBroadcast started.
  5. UDPSearcher sendBroadcast finished.
  6. UDPSearch receive form ip:169.254.178.74 port:30201 dataValid:true
  7. UDPSearcher Finished.
  8. UDPSearcher listener finished.
  9. Server:ServerInfo{sn='4cd6143b-205f-4e80-9b22-a6eae8e85cdd', port=30401, address='169.254.178.74'}
  10. 已发起服务器连接,并进入后续流程~
  11. 客户端信息: /169.254.178.74 P:51094
  12. 服务器信息:/169.254.178.74 P:30401
  13. ping
  14. pong
  15. 00bye00
  16. 客户端已退出~
  17. Process finished with exit code 0
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Cpp五条/article/detail/146956
推荐阅读
相关标签
  

闽ICP备14008679号