zoukankan      html  css  js  c++  java
  • Java 网络编程基础 -- TCP 编程

    Socket 介绍

    Socket 是一个抽象概念,一个应用程序通过一个 Socket 来建立一个远程连接,而 Socket 内部通过 TCP/IP 协议把数据传输到网络。

    一个 Socket 就是由 IP 地址和端口号(范围是0~65535)组成,可以把 Socket 简单理解为 IP 地址加端口号。

    端口号总是由操作系统分配,它是一个 0~65535 之间的数字,其中,小于 1024 的端口属于特权端口,需要管理员权限,大于 1024 的端口可以由任意用户的应用程序打开。

    使用Socket进行网络编程时,本质上就是两个进程之间的网络通信。

    其中一个进程必须充当服务器端,它会主动监听某个指定的端口,另一个进程必须充当客户端,它必须主动连接服务器的 IP 地址和指定端口。

    如果连接成功,服务器端和客户端就成功地建立了一个 TCP 连接,双方后续就可以随时发送和接收数据。

    因此,当 Socket 连接成功地在服务器端和客户端之间建立后:

    • 对服务器端来说,它的Socket是指定的IP地址和指定的端口号;

    • 对客户端来说,它的 Socket 是它所在计算机的 IP 地址和一个由操作系统分配的随机端口号。

    服务器端

    要使用 Socket 编程,我们首先要编写服务器端程序。Java 标准库提供了ServerSocket来实现对指定 IP 和指定端口的监听。

    ServerSocket的典型实现代码如下:

    /*
    Server.java 文件
    */
    
    public class Server {
        public static void main(String[] args) throws IOException {
            ServerSocket serverSocket = new ServerSocket(6666); // 监听指定端口
            System.out.println("server is running");
            for (;;){ // 无限循环
                Socket socket = serverSocket.accept(); // 接收新的请求
                System.out.println("connected from "+ socket.getRemoteSocketAddress());
                Thread thread = new Handler(socket);
                thread.start();
            }
        }
    }
    
    
    /*
    Handler.java 文件
    */
    
    public class Handler extends Thread {
        Socket socket;
    
        public Handler(Socket socket) {
            this.socket = socket;
        }
    
        @Override
        public void run() {
            try (InputStream input = this.socket.getInputStream()) {
                try (OutputStream output = this.socket.getOutputStream()) {
                    handle(input, output);
                }
            } catch (Exception e) {
                try {
                    this.socket.close();
                } catch (IOException ioe) {
                }
                System.out.println("client disconnected.");
            }
        }
    
        private void handle(InputStream input, OutputStream output) throws IOException {
            BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(output, StandardCharsets.UTF_8));
            BufferedReader reader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8));
            writer.write("hello
    ");
            writer.flush();
            for (;;) {
                String s = reader.readLine();
                if (s.equals("bye")) {
                    writer.write("bye
    ");
                    writer.flush();
                    break;
                }
                writer.write("ok: " + s + "
    ");
                writer.flush();
            }
        }
    
    }
    

    服务器端通过代码:

    ServerSocket serverSocket = new ServerSocket(6666); // 监听指定端口
    

    在指定端口 6666 监听。这里我们没有指定 IP 地址,表示在计算机的所有网络接口上进行监听。

    如果 ServerSocket 监听成功,我们就使用一个无限循环来处理客户端的连接:

    for (;;){ // 无限循环
                Socket socket = serverSocket.accept(); // 接收新的请求
                Thread thread = new Handler(socket);
                thread.start();
            }
    

    注意到代码 serverSocket.accept() 表示每当有新的客户端连接进来后,就返回一个 Socket 实例,这个 Socket 实例就是用来和刚连接的客户端进行通信的。

    由于客户端很多,要实现并发处理,我们就必须为每个新的 Socket 创建一个新线程来处理,这样,主线程的作用就是接收新的连接,每当收到新连接后,就创建一个新线程进行处理。

    如果没有客户端连接进来,accept() 方法会阻塞并一直等待。如果有多个客户端同时连接进来,ServerSocket 会把连接扔到队列里,然后一个一个处理。

    对于Java程序而言,只需要通过循环不断调用 accept() 就可以获取新的连接。

    客户端

    相比服务器端,客户端程序就要简单很多。一个典型的客户端程序如下:

    public class Client {
        public static void main(String[] args) throws IOException {
            Socket sock = new Socket("localhost", 6666); // 连接指定服务器和端口
            try (InputStream input = sock.getInputStream()) {
                try (OutputStream output = sock.getOutputStream()) {
                    handle(input, output);
                }
            }
            sock.close();
            System.out.println("disconnected.");
        }
    
        private static void handle(InputStream input, OutputStream output) throws IOException {
            BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(output, StandardCharsets.UTF_8));
            BufferedReader reader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8));
            Scanner scanner = new Scanner(System.in);
            System.out.println("[server] " + reader.readLine());
            for (;;) {
                System.out.print(">>> "); // 打印提示
                String s = scanner.nextLine(); // 读取一行输入
                writer.write(s);
                writer.newLine();
                writer.flush();
                String resp = reader.readLine();
                System.out.println("<<< " + resp);
                if (resp.equals("bye")) {
                    break;
                }
            }
        }
    }
    

    客户端程序通过:

    Socket sock = new Socket("localhost", 6666);
    

    连接到服务器端,注意上述代码的服务器地址是"localhost",表示本机地址,端口号是6666。

    如果连接成功,将返回一个Socket实例,用于后续通信。

    Socket 流

    当 Socket 连接创建成功后,无论是服务器端,还是客户端,我们都使用 Socket 实例进行网络通信。

    因为 TCP 是一种基于流的协议,因此,Java 标准库使用 InputStream 和 OutputStream 来封装 Socket 的数据流,这样我们使用 Socket 的流,和普通 IO 流类似:

    // 用于读取网络数据:
    InputStream in = sock.getInputStream();
    // 用于写入网络数据:
    OutputStream out = sock.getOutputStream();
    

    写入网络数据时,要调用flush()方法,强制把缓冲区数据发送出去。

    每天学习一点点,每天进步一点点。

  • 相关阅读:
    mysqldump --skip-tz-utc
    mysql 时间格式转换 DATE_FORMAT
    redis setinel 启动就 sdown
    virtualbox 1059m 布置1G虚拟机
    virtualbox 扩展磁盘空间
    tar gzip 压缩效率比较
    堆表 索引组织表
    内核参数 kernel.shmmax
    utf8mb4 字符集能正常存储表情
    源码包中带 boost 和 不带 boost
  • 原文地址:https://www.cnblogs.com/youcoding/p/13407711.html
Copyright © 2011-2022 走看看