热门标签 | HotTags
当前位置:  开发笔记 > 运维 > 正文

JAVA中实现原生的socket通信机制原理

本文介绍了JAVA中实现原生的 socket 通信机制原理,分享给大家,具体如下: 当前环境 jdk == 1.8 知识点

本文介绍了JAVA中实现原生的 socket 通信机制原理,分享给大家,具体如下:

当前环境

jdk == 1.8

知识点

  • socket 的连接处理
  • IO 输入、输出流的处理
  • 请求数据格式处理
  • 请求模型优化

场景

今天,和大家聊一下 JAVA 中的 socket 通信问题。这里采用最简单的一请求一响应模型为例,假设我们现在需要向 baidu 站点进行通信。我们用 JAVA 原生的 socket 该如何实现。

建立 socket 连接

首先,我们需要建立 socket 连接(核心代码)

import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
    
// 初始化 socket
Socket socket = new Socket();
// 初始化远程连接地址
SocketAddress remote = new InetSocketAddress(host, port);
// 建立连接
socket.connect(remote);

处理 socket 输入输出流

成功建立 socket 连接后,我们就能获得它的输入输出流,通信的本质是对输入输出流的处理。通过输入流,读取网络连接上传来的数据,通过输出流,将本地的数据传出给远端。

socket 连接实际与处理文件流有点类似,都是在进行 IO 操作。

获取输入、输出流代码如下:

// 输入流
InputStream in = socket.getInputStream();
// 输出流
OutputStream out = socket.getOutputStream();

关于 IO 流的处理,我们一般会用相应的包装类来处理 IO 流,如果直接处理的话,我们需要对 byte[] 进行操作,而这是相对比较繁琐的。如果采用包装类,我们可以直接以string、int等类型进行处理,简化了 IO 字节操作。

下面以 BufferedReader PrintWriter 作为输入输出的包装类进行处理。

// 获取 socket 输入流
private BufferedReader getReader(Socket socket) throws IOException {
  InputStream in = socket.getInputStream();
  return new BufferedReader(new InputStreamReader(in));
}

// 获取 socket 输出流
private PrintWriter getWriter(Socket socket) throws IOException {
  OutputStream out = socket.getOutputStream();
  return new PrintWriter(new OutputStreamWriter(out));
}

数据请求与响应

有了 socket 连接、IO 输入输出流,下面就该向发送请求数据,以及获取请求的响应结果。

因为有了 IO 包装类的支持,我们可以直接以字符串的格式进行传输,由包装类帮我们将数据装换成相应的字节流。

因为我们与 baidu 站点进行的是 HTTP 访问,所有我们不需要额外定义输出格式。采用标准的 HTTP 传输格式,就能进行请求响应了(某些特定的 RPC 框架,可能会有自定义的通信格式)。

请求的数据内容处理如下:

public class HttpUtil {

  public static String compositeRequest(String host){

    return "GET / HTTP/1.1\r\n" +
        "Host: " + host + "\r\n" +
        "User-Agent: curl/7.43.0\r\n" +
        "Accept: */*\r\n\r\n";
  }
  
}

发送请求数据代码如下:

// 发起请求
PrintWriter writer = getWriter(socket);
writer.write(HttpUtil.compositeRequest(host));
writer.flush();
接收响应数据代码如下:

// 读取响应
String msg;
BufferedReader reader = getReader(socket);
while ((msg = reader.readLine()) != null){
  System.out.println(msg);
}

至此,讲完了原生 socket 下的创建连接、发送请求与接收响应的所有核心代码。

完整代码如下:

import java.io.*;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
import com.test.network.util.HttpUtil;

public class SocketHttpClient {

  public void start(String host, int port) {

    // 初始化 socket
    Socket socket = new Socket();

    try {
      // 设置 socket 连接
      SocketAddress remote = new InetSocketAddress(host, port);
      socket.setSoTimeout(5000);
      socket.connect(remote);

      // 发起请求
      PrintWriter writer = getWriter(socket);
      System.out.println(HttpUtil.compositeRequest(host));
      writer.write(HttpUtil.compositeRequest(host));
      writer.flush();

      // 读取响应
      String msg;
      BufferedReader reader = getReader(socket);
      while ((msg = reader.readLine()) != null){
        System.out.println(msg);
      }

    } catch (IOException e) {
      e.printStackTrace();
    } finally {
      try {
        socket.close();
      } catch (IOException e) {
        e.printStackTrace();
      }
    }

  }

  private BufferedReader getReader(Socket socket) throws IOException {
    InputStream in = socket.getInputStream();
    return new BufferedReader(new InputStreamReader(in));
  }

  private PrintWriter getWriter(Socket socket) throws IOException {
    OutputStream out = socket.getOutputStream();
    return new PrintWriter(new OutputStreamWriter(out));
  }

}

下面,我们通过实例化一个客户端,来展示 socket 通信的结果。

public class Application {

  public static void main(String[] args) {

    new SocketHttpClient().start("www.baidu.com", 80);

  }
}

结果输出:

 

请求模型优化

这种方式,虽然实现功能没什么问题。但是我们细看,发现在 IO 写入与读取过程,是发生了 IO 阻塞的情况。即:

// 会发生 IO 阻塞
writer.write(HttpUtil.compositeRequest(host));
reader.readLine();

所以如果要同时请求10个不同的站点,如下:

public class SingleThreadApplication {

  public static void main(String[] args) {

    // HttpConstant.HOSTS 为 站点集合
    for (String host: HttpConstant.HOSTS) {

      new SocketHttpClient().start(host, HttpConstant.PORT);

    }

  }
}

它一定是第一个请求响应结束后,才会发起下一个站点处理。

这在服务端更明显,虽然这里的代码是客户端连接,但是具体的操作和服务端是差不多的。请求只能一个个串行处理,这在响应时间上肯定不能达标。

  • 多线程处理

有人觉得这根本不是问题,JAVA 是多线程的编程语言。对于这种情况,采用多线程的模型再合适不过。

public class MultiThreadApplication {

  public static void main(String[] args) {

    for (final String host: HttpConstant.HOSTS) {

      Thread t = new Thread(new Runnable() {
        public void run() {
          new SocketHttpClient().start(host, HttpConstant.PORT);
        }
      });

      t.start();

    }
  }
}

这种方式起初看起来挺有用的,但并发量一大,应用会起很多的线程。都知道,在服务器上,每一个线程实际都会占据一个文件句柄。而服务器上的句柄数是有限的,而且大量的线程,造成的线程间切换的消耗也会相当的大。所以这种方式在并发量大的场景下,一定是承载不住的。

  • 多线程 + 线程池 处理

既然线程太多不行,那我们控制一下线程创建的数目不就行了。只启动固定的线程数来进行 socket 处理,既利用了多线程的处理,又控制了系统的资源消耗。

public class ThreadPoolApplication {

  public static void main(String[] args) {

    ExecutorService executorService = Executors.newFixedThreadPool(8);

    for (final String host: HttpConstant.HOSTS) {

      Thread t = new Thread(new Runnable() {
        public void run() {
          new SocketHttpClient().start(host, HttpConstant.PORT);
        }
      });

      executorService.submit(t);
      new SocketHttpClient().start(host, HttpConstant.PORT);

    }

  }
}

关于启动的线程数,一般 CPU 密集型会设置在 N+1(N为CPU核数),IO 密集型设置在 2N + 1。

这种方式,看起来是最优的了。那有没有更好的呢,如果一个线程能同时处理多个 socket 连接,并且在每个 socket 输入输出数据没有准备好的情况下,不进行阻塞,那是不是更优呢。这种技术叫做“IO多路复用”。在 JAVA 的 nio 包中,提供了相应的实现。

后续

  • JAVA 中是如何实现 IO多路复用
  • Netty 下的实现异步请求的

github地址:https://github.com/jasonGeng88/blog/tree/master/201708

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。


推荐阅读
  • 从零开始构建完整手机站:Vue CLI 3 实战指南(第一部分)
    本系列教程将引导您使用 Vue CLI 3 构建一个功能齐全的移动应用。我们将深入探讨项目中涉及的每一个知识点,并确保这些内容与实际工作中的需求紧密结合。 ... [详细]
  • 在现代网络环境中,两台计算机之间的文件传输需求日益增长。传统的FTP和SSH方式虽然有效,但其配置复杂、步骤繁琐,难以满足快速且安全的传输需求。本文将介绍一种基于Go语言开发的新一代文件传输工具——Croc,它不仅简化了操作流程,还提供了强大的加密和跨平台支持。 ... [详细]
  • 本文基于对相关论文和开源代码的研究,详细介绍了LOAM(激光雷达里程计与建图)的工作原理,并对其关键技术进行了分析。 ... [详细]
  • 资源推荐 | TensorFlow官方中文教程助力英语非母语者学习
    来源:机器之心。本文详细介绍了TensorFlow官方提供的中文版教程和指南,帮助开发者更好地理解和应用这一强大的开源机器学习平台。 ... [详细]
  • 本文介绍了如何在具备多个IP地址的FTP服务器环境中,通过动态地址端口复用和地址转换技术优化网络配置。重点讨论了2Mb/s DDN专线连接、Cisco 2611路由器及内部网络地址规划。 ... [详细]
  • 构建基于BERT的中文NL2SQL模型:一个简明的基准
    本文探讨了将自然语言转换为SQL语句(NL2SQL)的任务,这是人工智能领域中一项非常实用的研究方向。文章介绍了笔者在公司举办的首届中文NL2SQL挑战赛中的实践,该比赛提供了金融和通用领域的表格数据,并标注了对应的自然语言与SQL语句对,旨在训练准确的NL2SQL模型。 ... [详细]
  • 数据库内核开发入门 | 搭建研发环境的初步指南
    本课程将带你从零开始,逐步掌握数据库内核开发的基础知识和实践技能,重点介绍如何搭建OceanBase的开发环境。 ... [详细]
  • 本文介绍如何使用 Sortable.js 库实现元素的拖拽和位置交换功能。Sortable.js 是一个轻量级、无依赖的 JavaScript 库,支持拖拽排序、动画效果和多种插件扩展。通过简单的配置和事件处理,可以轻松实现复杂的功能。 ... [详细]
  • 探讨一个显示数字的故障计算器,它支持两种操作:将当前数字乘以2或减去1。本文将详细介绍如何用最少的操作次数将初始值X转换为目标值Y。 ... [详细]
  • 本文详细介绍了Java编程语言中的核心概念和常见面试问题,包括集合类、数据结构、线程处理、Java虚拟机(JVM)、HTTP协议以及Git操作等方面的内容。通过深入分析每个主题,帮助读者更好地理解Java的关键特性和最佳实践。 ... [详细]
  • 如何配置Unturned服务器及其消息设置
    本文详细介绍了Unturned服务器的配置方法和消息设置技巧,帮助用户了解并优化服务器管理。同时,提供了关于云服务资源操作记录、远程登录设置以及文件传输的相关补充信息。 ... [详细]
  • 网络攻防实战:从HTTP到HTTPS的演变
    本文通过一系列日记记录了从发现漏洞到逐步加强安全措施的过程,探讨了如何应对网络攻击并最终实现全面的安全防护。 ... [详细]
  • UNP 第9章:主机名与地址转换
    本章探讨了用于在主机名和数值地址之间进行转换的函数,如gethostbyname和gethostbyaddr。此外,还介绍了getservbyname和getservbyport函数,用于在服务器名和端口号之间进行转换。 ... [详细]
  • 扫描线三巨头 hdu1928hdu 1255  hdu 1542 [POJ 1151]
    学习链接:http:blog.csdn.netlwt36articledetails48908031学习扫描线主要学习的是一种扫描的思想,后期可以求解很 ... [详细]
  • 本文详细探讨了HTTP 500内部服务器错误的成因、解决方案及其在Web开发中的影响。通过对具体案例的分析,帮助读者理解并解决此类问题。 ... [详细]
author-avatar
lou123456_541
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有