• 【Java成王之路】EE初阶第九篇:(网络编程) 3


    上节回顾

    socket(插槽,操作系统提供的网络编程的API的统称)

    进行网络编程的核心就是通过代码操作网卡这个硬件设备

    操作系统对于网卡进行了抽象,进程想去操作网卡的时候,就会打开一个"socket 文件"

    通过读写这个socket文件,就能读写网卡了.

    系统提供的socketAPI主要有两种风格:

    1.基于UDP的(数据报)

    2.基于TCP的(字节流)

    UDP:

    1.无连接

    2.不可靠

    3.面向数据报

    4.全双工

    TCP:

    1.有连接

    2.可靠数据流

    3.面向字节流

    4.全双工 

    在UDP socket中,主要提供了这么两个类:

    DatagramSocket:对 socket 文件进行了封装.

    构造方法:

    1.无参:客户端使用,此时端口号由系统分配.

    2.传入端口号:服务器使用,此时端口号是用户指定

    receive()方法:读取一个数据,并且放到DatagramPacket中.(可能会阻塞)

    send()方法:发送一个UDP数据报

    DatagramPacket:对 一个 UDP 数据报进行了封装 

    构造方法:

    1.传入空的缓冲区,构造一个空的packet(receive的时候使用的)

    2.传入一个有数据的缓冲区,指定一下目标ip 和 端口

    3.传入一个有数据的缓冲区,指定一下目标ip 和 端口(inetSockAddress类来完成)

    2和3一般都是send的时候使用

    一个服务器的核心流程:

    1.读取请求并解析.

    2.根据请求计算响应

    3.把响应写回给客户端

    一个客户端的核心流程:

    1.根据用户输入,构造请求.

    2.发送请求个服务器

    3.读取服务器的响应

    4.解析响应并显示

    在这一系列流程中,哪个环节是最复杂,或者是消耗代码最多的呢?

    根据请求计算响应

    因为上一篇咱们写的是回显服务器,不涉及这里的逻辑.

    但是其他一些有意义的服务器程序,这个环节往往就很复杂了.

    这是服务器程序最核心的部分.

    体现了具体的业务逻辑.  

    第二个版本的UDP程序.

    不再是回显服务了,而是"翻译程序"

     英译汉,客户端输入的请求是英文单词,返回的响应是对应的中文解释.

    服务器代码

    1. import java.io.IOException;
    2. import java.net.DatagramPacket;
    3. import java.net.DatagramSocket;
    4. import java.net.SocketException;
    5. import java.util.HashMap;
    6. /**
    7. * Created with IntelliJ IDEA.
    8. * Description:
    9. * User: 灯泡和大白
    10. * Date: 2022-07-25
    11. * Time: 21:45
    12. */
    13. public class UdpDictServer {
    14. private DatagramSocket socket = null;
    15. private HashMap dict = new HashMap<>();
    16. public UdpDictServer(int port) throws SocketException {
    17. socket = new DatagramSocket(port);
    18. //对哈希表的值进行初始化
    19. //此处这个表的数据可以非常多.
    20. dict.put("hello","你好");
    21. dict.put("cat","猫咪");
    22. dict.put("dog","小狗");
    23. }
    24. private String process(String request) {
    25. // 根据请求计算响应
    26. // 例如用户的请求是 "hello", 就应该返回一个 "你好"
    27. // 这个逻辑的实现, 核心就是 "查表"
    28. // 像有道这样的专业的词典, 应该是把数据都放到数据库中的.
    29. // 这里的查表就是查数据库的表了.
    30. // 当前咱们简单期间, 就直接查内存的 hash 表. 我们也完全可以去把这里的数据放到数据库中.
    31. // 此处使用 getOrDefault 来查. 如果是 get 的话, key 不存在, 就返回 null 了.
    32. // 此处期望返回的不是 null, 而是给客户端一个提示.
    33. return dict.getOrDefault(request, "[单词在词典中不存在!]");
    34. }
    35. public void start() throws IOException {
    36. System.out.println("服务器启动!");
    37. while (true) {
    38. // 1. 读取请求并解析
    39. DatagramPacket requestPacket = new DatagramPacket(new byte[4096], 4096);
    40. socket.receive(requestPacket);
    41. String request = new String(requestPacket.getData(), 0, requestPacket.getLength());
    42. //2.根据请求计算响应
    43. String response = process(request);
    44. //3.把响应写回到客户端
    45. DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),
    46. response.getBytes().length,
    47. requestPacket.getSocketAddress());
    48. socket.send(responsePacket);
    49. String log = String.format("[%s : %d] req: %s; resp: %s",requestPacket.getAddress().toString(),
    50. requestPacket.getPort(),request,response);
    51. System.out.println(log);
    52. }
    53. }
    54. public static void main(String[] args) throws IOException {
    55. UdpDictServer sever = new UdpDictServer(9090);
    56. sever.start();
    57. }
    58. }

    客户端代码

    1. import java.io.IOException;
    2. import java.net.*;
    3. import java.util.Scanner;
    4. /**
    5. * Created with IntelliJ IDEA.
    6. * Description:
    7. * User: 灯泡和大白
    8. * Date: 2022-07-25
    9. * Time: 22:34
    10. */
    11. //这个类和上一篇的UdpEchoClient基本类似
    12. //主要是因为客户端要负责和用户交互.而当下这个程序和回显程序都是用户交互的方式差不多.
    13. //此时就不需要有太多的改变.
    14. public class UdpDictClient {
    15. private DatagramSocket socket = null;
    16. private String serverIP ;
    17. private int serverPort;
    18. public UdpDictClient(String serverIP, int serverPort) throws SocketException {
    19. this.serverIP = serverIP;
    20. this.serverPort = serverPort;
    21. this.socket = new DatagramSocket();
    22. }
    23. public void start() throws IOException {
    24. Scanner scanner = new Scanner(System.in);
    25. while (true) {
    26. //1.读取输入的数据
    27. System.out.println("->");
    28. String request = scanner.next();
    29. if (request.equals("exit")) {
    30. System.out.println("goodbye");
    31. return;
    32. }
    33. //2.构造请求并发送给服务器
    34. DatagramPacket requestPacket = new DatagramPacket(request.getBytes(),
    35. request.getBytes().length,
    36. InetAddress.getByName(serverIP), serverPort);
    37. socket.send(requestPacket);
    38. // 3. 从服务器读取响应
    39. DatagramPacket responsePacket = new DatagramPacket(new byte[4096], 4096);
    40. socket.receive(responsePacket);
    41. String response = new String(responsePacket.getData(), 0, responsePacket.getLength());
    42. // 4. 把数据显示给用户
    43. String log = String.format("req: %s; resp: %s", request, response);
    44. System.out.println(log);
    45. }
    46. }
    47. public static void main(String[] args) throws IOException {
    48. UdpDictClient client = new UdpDictClient("127.0.0.1", 9090);
    49. client.start();
    50. }
    51. }

    UDP暂告一段落

    TCP流套接字API

    相关的两个类:

    ServerSocket类:

    accept()方法 和 TCP "有连接" 这样的特性密切相关.

    accpet就是接电话这个动作!

    客户端尝试建立连接,首先是服务器操作系统这一层来和客户端进行一些相关的流程,把这个连接先准备好.

    用户代码调用accept,才能真的把这个连接拿到用户代码中

    socket也是对应到文件了.

    也会有一个close方法.
    一个socket理论上用完了之后是要关闭的.

    但是咱们前面写的UDP版本的程序其实不太需要关闭.(也不能关闭)

    当前这里的UdpServer UdpClient 里面的 socket 是有生命周期的,都是要跟随整个程序的.

    如果socket/文件没有关闭,当进程结束的时候,对应的资源也就自然释放了.

     

    Socket类:

    使用TCP写一个简单的程序

    回显服务器,回显客户端

    服务器代码

    1. import sun.rmi.transport.tcp.TCPChannel;
    2. import java.io.IOException;
    3. import java.io.InputStream;
    4. import java.io.OutputStream;
    5. import java.io.PrintWriter;
    6. import java.net.ServerSocket;
    7. import java.net.Socket;
    8. import java.util.Scanner;
    9. public class TcpEchoServer {
    10. private ServerSocket listenSocket = null;
    11. public TcpEchoServer(int port) throws IOException {
    12. listenSocket = new ServerSocket(port);
    13. }
    14. public void start() throws IOException {
    15. System.out.println("服务器启动!");
    16. while (true) {
    17. // UDP 的服务器进入主循环, 就直接尝试 receive 读取请求了.
    18. // 但是 TCP 是有连接的. 先需要做的是, 建立好连接
    19. // 当服务器运行的时候, 当前是否有客户端来建立连接, 不确定~~
    20. // 如果客户端没有建立连接, accept 就会阻塞等待
    21. // 如果有客户端建立连接了, 此时 accept 就会返回一个 Socket 对象
    22. // 进一步的服务器和客户端之间的交互, 就交给 clientSocket 来完成了~
    23. Socket clientSocket = listenSocket.accept();
    24. processConnection(clientSocket);
    25. }
    26. }
    27. private void processConnection(Socket clientSocket) throws IOException {
    28. // 处理一个连接. 在这个连接中可能会涉及客户端和服务器之间的多次交互
    29. String log = String.format("[%s:%d] 客户端上线!",
    30. clientSocket.getInetAddress().toString(), clientSocket.getPort());
    31. System.out.println(log);
    32. try (InputStream inputStream = clientSocket.getInputStream();
    33. OutputStream outputStream = clientSocket.getOutputStream()) {
    34. while (true) {
    35. // 1. 读取请求并解析
    36. // 可以直接通过 inputStream 的 read 把数据读到一个 byte[] , 然后再转成一个 String
    37. // 但是比较麻烦. 还可以借助 Scanner 来完成这个工作.
    38. Scanner scanner = new Scanner(inputStream);
    39. if (!scanner.hasNext()) {
    40. log = String.format("[%s:%d] 客户端下线!",
    41. clientSocket.getInetAddress().toString(), clientSocket.getPort());
    42. System.out.println();
    43. break;
    44. }
    45. String request = scanner.next();
    46. // 2. 根据请求计算响应
    47. String response = process(request);
    48. // 3. 把响应写回给客户端
    49. PrintWriter writer = new PrintWriter(outputStream);
    50. writer.println(response);
    51. writer.flush();
    52. log = String.format("[%s:%d] req: %s; resp: %s",
    53. clientSocket.getInetAddress().toString(), clientSocket.getPort(),
    54. request, response);
    55. System.out.println(log);
    56. }
    57. } catch (IOException e) {
    58. e.printStackTrace();
    59. } finally {
    60. // 当前的 clientSocket 生命周期, 不是跟随整个程序, 而是和连接相关.
    61. // 因此就需要每个连接结束, 都要进行关闭.
    62. // 否则随着连接的增多, 这个 socket 文件就可能出现资源泄露的情况
    63. clientSocket.close();
    64. }
    65. }
    66. // 当前是实现一个回显服务器
    67. // 客户端发啥, 服务器就返回啥.
    68. private String process(String request) {
    69. return request;
    70. }
    71. public static void main(String[] args) throws IOException {
    72. TcpEchoServer server = new TcpEchoServer(9090);
    73. server.start();
    74. }
    75. }

     

    服务器代码

    1. import java.io.IOException;
    2. import java.io.InputStream;
    3. import java.io.OutputStream;
    4. import java.io.PrintWriter;
    5. import java.net.Socket;
    6. import java.util.Scanner;
    7. public class TcpEchoClient {
    8. private String serverIp;
    9. private int serverPort;
    10. private Socket socket = null;
    11. public TcpEchoClient(String serverIp, int serverPort) throws IOException {
    12. this.serverIp = serverIp;
    13. this.serverPort = serverPort;
    14. // 让 socket 创建的同时, 就和服务器尝试建立连接
    15. this.socket = new Socket(serverIp, serverPort);
    16. }
    17. public void start() {
    18. Scanner scanner = new Scanner(System.in);
    19. try (InputStream inputStream = socket.getInputStream();
    20. OutputStream outputStream = socket.getOutputStream()) {
    21. while (true) {
    22. // 1. 从键盘上, 读取用户输入的内容.
    23. System.out.print("->");
    24. String request = scanner.next();
    25. if (request.equals("exit")) {
    26. break;
    27. }
    28. // 2. 把这个读取的内容构造成请求, 发送给服务器
    29. PrintWriter printWriter = new PrintWriter(outputStream);
    30. printWriter.println(request);
    31. // println 只是把数据写到缓冲区里, 至于是不是真的写到 IO 设备, 不好说.
    32. // 加上一个 flush 强制发送一下~
    33. printWriter.flush();
    34. // 3. 从服务器读取响应并解析
    35. Scanner respScanner = new Scanner(inputStream);
    36. String response = respScanner.next();
    37. // 4. 把结果显示到界面上.
    38. String log = String.format("req: %s; resp: %s", request, response);
    39. System.out.println(log);
    40. }
    41. } catch (IOException e) {
    42. e.printStackTrace();
    43. }
    44. }
    45. public static void main(String[] args) throws IOException {
    46. TcpEchoClient client = new TcpEchoClient("127.0.0.1", 9090);
    47. client.start();
    48. }
    49. }

     

     

  • 相关阅读:
    (c语言)用冒泡排序模拟实现qsort()函数交换整数
    Yalmip使用教程(6)-将约束条件写成矩阵形式
    jwbasta-vue 平台上线
    IDEA通过Docker插件部署SpringBoot项目
    尚硅谷设计模式学习(八)桥接模式
    软件方法(下)第9章分析类图案例篇Part05-案例二-智能建模工具
    提交Spark作业遇到的NoSuchMethodError问题
    wy的leetcode刷题记录_Day35
    面了个腾讯30k+出来的,他让我见识到什么叫精通MySQL调优
    Web自动化成长之路:selenium中的PO模式
  • 原文地址:https://blog.csdn.net/m0_64397675/article/details/125983690