网络编程^

Socket

Socket (套接字) : 是操作系统提供的可以进行网络通信的接口。Socket 是应用程序(应用层)访问传输层通信服务的接口。传输层 2 种最主要的协议 : TCP 协议 / UDP协议。TCP 和 UPD 提供了 2 组不同的 Socket API

TCP / UPD 的特点

TCP 协议特点 : 有连接、可靠传输、面向字节流、全双工

UDP 协议特点 : 无连接、不可靠传输、面向数据报、全双工

  • 有链接 : 通信前通信双方的主机互相保存了对方的核心信息(IP 、端口),通信后删除对方的关键信息
  • 无链接 : 不需要保存对方的信息,直接发送数据
  • 可靠传输 :是指发送方和接收方通过一定的机制,尽可能确保数据能够正确、完整、按顺序地到达接收方
  • 不可靠传输 :不保证数据一定到达接收方,也不保证数据按顺序到达或不会重复
  • 面向字节流 :字节为单位,进行灵活的数据读写
  • 面向数据报 :一个数据报为单位读写,一次读或写都是一个完整的数据报
  • 全双工 : 通信双方可以同时发送和接收数据(双向通信)

UDP Socket

UPD Socket 主要是提供了 2 个 类 :

  1. DatagramSocket :⽤于发送和接收UDP数据报。是一个 Socket 对象,表示对网卡设备的抽象。后序操作 Socket 对象,就能操作网卡
  2. DatagramPacket :是 UDPSocket 发送和接收的数据报,使用 UDP 传输数据的基本单位。每次发送/接收数据,都是在传输一个 DatagramPacket 对象

回显服务

回显服务就是请求内容是啥,响应就是啥。我们把整个回显服务分为两部分:

  • UdpEchoServer 服务器部分。
  • UdpEchoClient 客户端部分。

服务器

  1. 创建 Socket 对象,网络编程都是 Socket 对象 作为中心来展开的
java 复制代码
private DatagramSocket socket = null;
  1. 指定服务器的端口号
java 复制代码
public UdpEchoServer(int port) throws SocketException {
    socket = new DatagramSocket(port);
}

启动服务器

  1. 读取客户端请求并解析。用 socket.receive() 来接收客户端发来的数据、如果客户端没有发来数据、socket.receive() 就会堵塞等待 等待数据到达、 构造DatagramPacket对象(接收数据的容器)传给 socket.receive()来接受客户端发来的数据
  2. 根据请求计算响应。这一般是个复杂的过程,由于是回响服务器没有计算的过程、直接返回接收的数据
  3. 返回响应给客户端。把响应再次放进 DatagramPacket对象 、传给socket.send()返回给客户端、
    由于UDP协议是不会保存对方的信息的、因此返回的时候需要知道 "返回给谁" 、构造DatagramPacket对象
    的时候要把 客户端的 IP 和 端口号 传过去
  • 服务器代码 :
java 复制代码
  public class UdpEchoServer {
    private DatagramSocket socket = null;
    public UdpEchoServer(int port) throws SocketException {
        socket = new DatagramSocket(port);
    }

    //启动服务器
    public void start() throws IOException {
        System.out.println("server start!");

        while (true) {
            // 1. 读取请求并解析. 此处 requestPacket 是 receive 的输出型参数.
            DatagramPacket requestPacket = new DatagramPacket(new byte[1024], 1024);
            socket.receive(requestPacket);
            // 此处把二进制数据, 转成字符串.
            String request = new String(requestPacket.getData(), 0, requestPacket.getLength());
            // 2. 根据请求计算响应.
            String response = process(request);
            // 3. 把响应返回给客户端. 再构造一个 DatagramPacket
            DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),
                    response.getBytes().length, requestPacket.getSocketAddress());
            socket.send(responsePacket);
            // 4. 打印日志
            System.out.printf("[%s:%d] req: %s, resp: %s\n", requestPacket.getAddress().toString(),
                    requestPacket.getPort(), request, response);
        }
    }
    private String process(String request) {
        return request;
    }

    public static void main(String[] args) throws IOException {
        UdpEchoServer server = new UdpEchoServer(9090);
        server.start();
    }
}

传参 requestPacket.getSocketAddress() 把客户端的 IP 和 端口号 传给 responsePacket

客户端

客户端的端口号不需要指定、由操作系统来分配空闲的端口号

构造 Socket 对象 和 保存 服务器的 IP 和 端口号

java 复制代码
//创建 Socker 对象
private DatagramSocket socket;
//保存服务器的端口号和IP
private String serverIp;
private int serverPort;
public UdpEchoClient(String serverIP, int serverPort) throws IOException {
		// 端口号由系统指定
    socket = new DatagramSocket();
    this.serverIp = serverIP;
    this.serverPort = serverPort;
}

启动客户端

  1. 客户端从控制台读取用户输入的内容
  2. 把用户输入的内容构造成 UPD数据报 发送给服务器
  3. 接收服务器返回的响应、解析响应输出在控制台
  • 客户端代码 :
java 复制代码
public class UdpEchoClient {
    //创建 Socker 对象
    private DatagramSocket socket;
    //保存服务器的端口号和IP
    private String serverIp;
    private int serverPort;
    public UdpEchoClient(String serverIP, int serverPort) throws IOException {
        socket = new DatagramSocket();
        this.serverIp = serverIP;
        this.serverPort = serverPort;
    }

    public void start() throws IOException {
        System.out.println("Client start");
        Scanner sc = new Scanner(System.in);
        while(true){
            //从控制台读取输入
            System.out.print("-> ");
            String request = sc.next();
            //退出
            if(request.equals("exit")){
                break;
            }
            //将用户输入的字符串构造成数据报    发送 UDP 数据报还需要包含 服务器的 IP 和 端口
            DatagramPacket requestPacket = new DatagramPacket(
                    request.getBytes(),
                    request.getBytes().length,
                    InetAddress.getByName(this.serverIp),
                    this.serverPort
            );
            //发送请求给服务器
            socket.send(requestPacket);
            // 接收服务器的返回的响应
            DatagramPacket responsePacket = new DatagramPacket(new byte[1024], 1024);
            //从服务器读取响应
            socket.receive(responsePacket);
            // 将二进制数转化为字符串
            String response = new String(responsePacket.getData(), 0, responsePacket.getLength());
            //显示响应内容
            System.out.println(response);
        }
    }

    public static void main(String[] args) throws IOException {
        //服务器的 IP 和 端口号传给客户端
        UdpEchoClient client = new UdpEchoClient("127.0.0.1",9090);
        client.start();
    }

需要把 IP 地址 字符串转换为 InetAddress 对象

  • 运行程序 :

字典服务器

在回显服务器的基础上、增加查字典的功能。字典服务器的只是需要改变 process 的代码逻辑。通过继承UdpEchoServer 来复用其他代码、重写 process 方法来实现字典服务器

java 复制代码
public class UdpDictServer extends UdpEchoServer{
		// 通过 hash 表把 英语和翻译绑定
    private Map<String, String> dict = new HashMap<>();
    public UdpDictServer(int port) throws SocketException {
        super(port);
        dict.put("hello", "你好");
        dict.put("world", "世界");
        dict.put("dog", "狗");
        dict.put("cat", "猫");
    }

    @Override
    public String process(String request){
        return dict.getOrDefault(request,"字典没有这个单词");
    }

    public static void main(String[] args) throws IOException {
        UdpDictServer server = new UdpDictServer(9090);
        server.start();
    }
}

TCP Socket

TCP 传输的基本单位是字节、主要涉及 2 个类

  1. ServerSocket : 专门给服务器使用
  2. Socket : 既给客户端又给服务器使用

回显服务

服务器

创建 ServerSocket 对象、启动服务器 建立连接 。因为 TCP 协议是有连接的、在通信前需要建立连接

java 复制代码
  ServerSocket serverSocket = null;

  public TcpEchoServer(int port) throws IOException {
      serverSocket = new ServerSocket(port);
  }

  public void start() throws IOException {
      System.out.println("server start");
      while (true) {
          //1 接受客户端的连接
          Socket socket = serverSocket.accept();
          prcessConnection(socket);
      }
  }

处理请求

java 复制代码
private void prcessConnection(Socket socket) {
		// 打印客户端的信息
     System.out.printf("[%s:%d] 客户端已连接\n", socket.getInetAddress(), socket.getPort());

     try(InputStream inputStream = socket.getInputStream();
         OutputStream outputStream = socket.getOutputStream();) {

         Scanner scanner = new Scanner(inputStream);
         PrintWriter writer = new PrintWriter(outputStream);
         while(true){
             //读取请求并解析
             if(!scanner.hasNext()){
                 //客户端关闭了连接
                 System.out.printf("[%s:%d] 客户端断开连接\n",  socket.getInetAddress(), socket.getPort());
                 break;
             }
             String request = scanner.next();
             //根据请求计算响应
             String response = process(request);
             //把响应写回给客户端
             writer.println(response);
             // 将文件缓冲区的数据 强制写入到 IO 设备
             //(文件缓冲区的数据没满,数据会在一直在缓冲区,满了一起把数据写入到 IO 设备)
             writer.flush();
             //打印日志
             System.out.printf("[%s:%d] req : %s  resp: %s\n",
                     socket.getInetAddress(),socket.getPort(),
                     request, response);
         }
     } catch (IOException e) {
         throw new RuntimeException(e);
     } finally {
         try {
         		// 关闭每次接收客户端连接的 Socket (文件资源)
             socket.close();
         } catch (IOException e) {
             throw new RuntimeException(e);
         }
     }
 }

对端的 IP 和 端口号就保存在 Socket 中

  • 服务器代码 :
java 复制代码
public class TcpEchoServer {
    ServerSocket serverSocket = null;

    public TcpEchoServer(int port) throws IOException {
        serverSocket = new ServerSocket(port);
    }

    public void start() throws IOException {
        System.out.println("server start");
        while (true) {
            //1 接受客户端的连接
            Socket socket = serverSocket.accept();
            //处理整个链接过程
            prcessConnection(socket);
        }
    }

    private void prcessConnection(Socket socket) {
        System.out.printf("[%s:%d] 客户端已连接\n", socket.getInetAddress(), socket.getPort());

        try(InputStream inputStream = socket.getInputStream();
            OutputStream outputStream = socket.getOutputStream();) {

            Scanner scanner = new Scanner(inputStream);
            PrintWriter writer = new PrintWriter(outputStream);
            while(true){
                //读取请求并解析
                if(!scanner.hasNext()){
                    //客户端关闭了连接
                    System.out.printf("[%s:%d] 客户端断开连接\n",  socket.getInetAddress(), socket.getPort());
                    break;
                }
                String request = scanner.next();
                //根据请求计算响应
                String response = process(request);
                //把响应写回给客户端
                writer.println(response);
                writer.flush();
                //打印日志
                System.out.printf("[%s:%d] req : %s  resp: %s\n",
                        socket.getInetAddress(),socket.getPort(),
                        request, response);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            try {
                socket.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }

    }

    private String process(String request) {
        return request;
    }

    public static void main(String[] args) throws IOException {
        TcpEchoServer server = new TcpEchoServer(9090);
        server.start();
    }
}

客户端

对于 TCP 的 Socket 来说,构造方法指定的端口。表示要链接的服务器的端口

  • 客户端代码 :
java 复制代码
public class TcpEchoClient {
    private Socket socket = null;
    public TcpEchoClient(String serverIp, int serverPort) throws IOException {
    		// 传入 服务器的 IP 和 端口号 和服务器建立连接
        socket = new Socket(serverIp,serverPort);
    }

    public void start(){
        System.out.println("client start");

        try(InputStream inputStream = socket.getInputStream();
            OutputStream outputStream = socket.getOutputStream()){

            Scanner scanner = new Scanner(System.in);
            Scanner scannerNetwork = new Scanner(inputStream);
            PrintWriter writer = new PrintWriter(outputStream);
            while(true) {
                //控制台读取数据
                System.out.println("-> ");
                String request = scanner.next();
                //把请求发送给服务器
                writer.println(request);
                writer.flush();
                //从服务器读取响应
                if(!scannerNetwork.hasNext()){
                    break;
                }
                String response = scannerNetwork.next();
                //把结果显示到控制台上
                System.out.println(response);
            }

        }catch(IOException e){
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws IOException {
        TcpEchoClient client = new TcpEchoClient("127.0.0.1",9090);
        client.start();
    }
}
  • 运行程序 :

线程池版回响服务器

java 复制代码
public class TcpEchoServer {
    ServerSocket serverSocket = null;

    public TcpEchoServer(int port) throws IOException {
        serverSocket = new ServerSocket(port);
    }

    public void start() throws IOException {
        System.out.println("server start");
        ExecutorService executor = Executors.newCachedThreadPool();
        while (true) {
            //1 接受客户端的连接
            Socket socket = serverSocket.accept();
            //处理整个链接过程
            executor.submit(() -> {
                prcessConnection(socket);
            });
        }
    }

    private void prcessConnection(Socket socket) {
        System.out.printf("[%s:%d] 客户端已连接\n", socket.getInetAddress(), socket.getPort());

        try(InputStream inputStream = socket.getInputStream();
            OutputStream outputStream = socket.getOutputStream();) {

            Scanner scanner = new Scanner(inputStream);
            PrintWriter writer = new PrintWriter(outputStream);
            while(true){
                //读取请求并解析
                if(!scanner.hasNext()){
                    //客户端关闭了连接
                    System.out.printf("[%s:%d] 客户端断开连接\n",  socket.getInetAddress(), socket.getPort());
                    break;
                }
                String request = scanner.next();
                //根据请求计算响应
                String response = process(request);
                //把响应写回给客户端
                writer.println(response);
                writer.flush();
                //打印日志
                System.out.printf("[%s:%d] req : %s  resp: %s\n",
                        socket.getInetAddress(),socket.getPort(),
                        request, response);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            try {
                socket.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }

    }

    private String process(String request) {
        return request;
    }

    public static void main(String[] args) throws IOException {
        TcpEchoServer server = new TcpEchoServer(9090);
        server.start();
    }
  • 运行程序 :


相关推荐
Thinker QAQ1 小时前
并发编程(十一):Shared Memory 与 Message Passing——关系与组合使用
java·python·go·并发编程·shared memory·message passing
齐 飞2 小时前
idea中git误操作撤回git reset --hard ‘HEAD@{1}‘
java·git·intellij-idea
你的保护色2 小时前
多AS互联BGP4综合实验设计
java
某不知名網友2 小时前
ROS2进阶:服务通信、自定义SRV接口、参数服务器与基于服务的参数通信详解
java·开发语言·microsoft
Wang's Blog2 小时前
Java 项目实战: 交友项目 - MongoDB 入门:体系结构和命令行操作
java·mongodb·交友
小溪学编程3 小时前
将 Bean 放入 Spring 容器中的方式有哪些?
java·后端·spring
java_nnnn3 小时前
LeetCode刷题滑动窗口(水果成篮+找到字符串中所有字母的异位词)Java
java·开发语言·leetcode
用户3721574261353 小时前
Java 读取、修改和清空 PDF 文档属性(元数据)
java
SL_staff3 小时前
从‘改一行代码等三天’到‘改完即生效’:实施工程师的逻辑引擎避坑手记
java·产品·全栈