网络编程套接字

发布于:2025-03-10 ⋅ 阅读:(12) ⋅ 点赞:(0)
  • Socket 套接字,是由系统提供用于网络通信的技术,是基于 TCP/IP 协议的网络通信的基本操作单元。
  • 基于 Socket 套接字的网络程序开发就是网络编程。
  • TCP/IP 协议是一个由多个协议组成的协议簇,包括 FTP、SMTP、TCP、UDP、IP 等。
  • TCP 和 IP 是最具代表性的两个协议。
  • TCP(Transmission Control Protocol)是一种面向连接的协议,提供可靠的、面向连接的数据传输服务,适用于需要高可靠性的应用场景。它通过三次握手建立连接,确保数据的可靠传输‌。
  • UDP(User Datagram Protocol)是一种无连接的协议,不保证数据的可靠性,适用于对实时性要求高但可以容忍一定数据丢失的应用场景‌。

 



1 UDP socket api 的使用




1.1 DatagramSocket


DatagramSocket 是 UDP Socket,⽤于发送和接收 UDP 数据报

构造方法 


方法

输出型参数 


1.2 DatagramPacket


DatagramPacket是 UDP Socket 发送和接收的数据报

构造方法


方法


1.3 InetSocketAddress


InetSocketAddress SocketAddress 的⼦类 )

构造方法


1.4 回显服务器

































1.5 服务器代码


简单服务器:UdpEchoServer

package network;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;

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("服务器启动!");
        while (true) {
            // 每次循环就是处理一个请求-响应过程
            // 1.读取请求并解析
            DatagramPacket requestPacket = new DatagramPacket(new byte[4096], 4096);
            socket.receive(requestPacket);
            // 读到的字节数组,转成 String 方便后序的逻辑处理
            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);

            // 打印日志
            System.out.printf("[%s:%d] req: %s, resp: %s\n", requestPacket.getAddress().toString(),
                    requestPacket.getPort(), request, response);
        }
    }

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

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

有逻辑服务器:UdpDictServer

编写⼀个英译汉的服务器. 只需要重写 process
package network;

import java.io.IOException;
import java.net.SocketException;
import java.util.HashMap;

public class UdpDictServer extends UdpEchoServer{
    private HashMap<String, String> hashMap = new HashMap<>();

    public UdpDictServer(int port) throws SocketException {
        super(port);

        hashMap.put("cat", "小猫");
        hashMap.put("dog", "小狗");
        hashMap.put("dictionary", "词典");
        // 此处还可以无限添加英汉键值对
        // 像有道词典这样的专业词典程序,本质上就是里面包含了一个这样的非常大的,几十万个个键值对 hashMap
    }

    // start 方法完全从父类这里继承下来即可
    // process 方法要进行重写,加入自己的业务逻辑,进行翻译

    @Override
    public String process(String request) {
        // 参数是一个英文单词
        // 返回值是一个对应的汉语
        return hashMap.getOrDefault(request, "您查的单词不存在!");
    }

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

1.6 客户端代码


package network;

import java.io.IOException;
import java.net.*;
import java.util.Scanner;

public class UdpEchoClient {
    DatagramSocket socket = null;
    private String severIp;
    private int serverPort;

    // 此处 ip 使用的字符串,点分十进制风格. "192.168.2.100"
    public UdpEchoClient(String serverIp, int serverPort) throws SocketException {
        this.severIp = serverIp;
        this.serverPort = serverPort;
        socket = new DatagramSocket();
    }

    public void start() throws IOException {
        System.out.println("客户端启动");
        Scanner scanner = new Scanner(System.in);

        while (true) {
            // 要做四个事情
            System.out.print("-> "); // 表示提示用户接下来要输入内容
            // 1.从控制台读取要发送的请求数据
            while (!scanner.hasNext()) {
                break;
            }
            String request = scanner.next();
            // 2.构造请求并发送
            DatagramPacket requestPacket = new DatagramPacket(request.getBytes(), request.getBytes().length,
                    InetAddress.getByName(severIp), serverPort);
            socket.send(requestPacket);
            // 3.读取服务器的响应
            DatagramPacket responsePacket = new DatagramPacket(new byte[4096], 4096);
            socket.receive(responsePacket);
            // 4.把响应显示到控制台上
            String response = new String(responsePacket.getData(), 0, responsePacket.getLength());
            System.out.println(response);
        }
    }

    public static void main(String[] args) throws IOException {
        UdpEchoClient client = new UdpEchoClient("127.0.0.1", 9090);
        client.start();
    }
}

2 TCO socket api 的使用



2.1 ServerSocket


ServerSocket 是创建TCP服务端 Socket 的API

构造方法


方法


2.2 Socket


  • Socket 是客户端Socket,或服务端中接收到客户端建立连接(accept⽅法)的请求后,返回的服务端Socket
  • 不管是客⼾端还是服务端Socket,都是双⽅建⽴连接以后,保存的对端信息,及⽤来与对⽅收发数据的

构造方法


方法


2.3 建立连接







try(InputStream inputStream = clientSocket.getInputStream();
            OutputStream outputStream = clientSocket.getOutputStream()) {
            while (true) {
                // 通过 InputStream 读取数据
                byte[] buffer = new byte[1024];
                inputStream.read(buffer);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }





出现响应未返回打印



一次数据交互所产生的过程







由于可能会频繁创建销毁,所以要使用线程池




2.4 服务器代码


package network;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class TcpEchoServer {
    private ServerSocket serverSocket = null;

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

    public void start() throws IOException {
        System.out.println("服务器启动!");
        ExecutorService pool = Executors.newCachedThreadPool();
        while (true) {
            // 通过 accept 方法来 "接听电话" , 然后才能进行通信
            Socket clientSocket = serverSocket.accept();
//            Thread t = new Thread(()-> {
//                processConnection(clientSocket);
//            });
//            t.start();

            pool.submit(new Runnable() {
                @Override
                public void run() {
                    processConnection(clientSocket);
                }
            });
        }
    }

    // 通过这个方法来处理一次连接. 连接建立的过程中就会涉及到多次的请求响应交互.
    private void processConnection(Socket clientSocket) {
        System.out.printf("[%s:%d] 客户端上线!\n", clientSocket.getInetAddress(), clientSocket.getPort());
        // 循环读取客户端的请求并返回响应
        try(InputStream inputStream = clientSocket.getInputStream();
            OutputStream outputStream = clientSocket.getOutputStream()) {
            Scanner scanner = new Scanner(inputStream);
            while (true) {
                if (!scanner.hasNext()) {
                    // 读取完毕. 客户端断开连接就会产生读取完毕.
                    System.out.printf("[%s:%d] 客户端下线!\n", clientSocket.getInetAddress(), clientSocket.getPort());
                    break;
                }
                // 1.读取请求并解析, 这里注意隐藏的约定, next 读的时候要读到空白符才会结束.
                //   因此就要求客户端发来的请求必须带有空白符结尾. 比如 \n 或者空格.
                String request = scanner.next();
                // 2.根据请求计算响应
                String response = process(request);
                // 3. 把响应返回客户端
                //    通过这种方式可以写回,但是这种方式不方便给返回的响应中添加 \n
                // outputStream.write(response.getBytes(), 0, response.getBytes().length);
                //    也可以给 outputStream 套上一层, 完成更方便的写入.
                PrintWriter printWriter = new PrintWriter(outputStream);
                printWriter.println(response);
                printWriter.flush();

                System.out.printf("[%s:%d] req: %s, resp: %s\n", clientSocket.getInetAddress(), clientSocket.getPort(),
                        request, response);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            try {
                clientSocket.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }

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

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

2.5 客户端代码


package network;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;

public class TcpEchoClient {
    private Socket socket = null;

    public TcpEchoClient(String serverIp, int serverPort) throws IOException {
        // 此处可以把这里的 ip 和 port 直接传给 socket 对象
        // 由于 tcp是有连接的. 因此这个 socket 就会保存好这俩信息
        // 因此 TcpEchoClient 类就不必保存.
        socket = new Socket(serverIp, serverPort);
    }

    public void start() {
        System.out.println("客户端启动!");
        try(InputStream inputStream = socket.getInputStream();
            OutputStream outputStream = socket.getOutputStream()) {
            Scanner scannerConsole = new Scanner(System.in);
            Scanner scannerNetwork = new Scanner(inputStream);
            PrintWriter writer = new PrintWriter(outputStream);
            while (true) {
                // 这里的流程和 UDP 的客户端类似
                // 1.从控制台读取输入的字符串
                System.out.print("-> ");
                if (!scannerConsole.hasNext()) {
                    break;
                }
                String request = scannerConsole.next();
                // 2.把请求发给服务器, 这里需要使用 println 来发送, 为了让发送的请求末尾带有 \n
                //   这里是和服务器的 scanner.next 呼应的
                writer.println(request);
                // 通过这个 flush 主动刷新缓存区, 确保数据真的发出去了
                writer.flush();
                // 3.从服务器读取响应, 这里也是和服务器返回响应的逻辑对应
                String response = scannerNetwork.next();
                // 4.把响应显示出来
                System.out.println(response);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

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

网站公告

今日签到

点亮在社区的每一天
去签到