>  기사  >  Java  >  Java가 기본 소켓 통신 메커니즘을 구현하는 방법의 원리에 대한 자세한 설명

Java가 기본 소켓 통신 메커니즘을 구현하는 방법의 원리에 대한 자세한 설명

黄舟
黄舟원래의
2017-08-20 09:10:401839검색

이 기사에서는 주로 JAVA에서 네이티브 소켓 통신 메커니즘을 구현하는 원리를 소개합니다. 편집자는 이것이 꽤 좋다고 생각하므로 지금 공유하고 참고용으로 제공하겠습니다. 편집자를 따라가서 살펴보겠습니다. 이 기사에서는 JAVA의 기본 소켓 통신 메커니즘을 소개하고 이를 모든 사람과 공유합니다.

현재 환경


jdk == 1.8


지식 포인트

    소켓 연결 처리
  • IO 입출력 스트림 처리
  • 요청 데이터 형식 처리
  • 요청 모델 최적화
scenario


오늘은 JAVA의 소켓 통신 문제입니다. 여기서는 이제 Baidu 사이트와 통신해야 한다고 가정하고 가장 간단한 1요청 1응답 모델을 예로 들어보겠습니다. 이를 달성하기 위해 JAVA의 기본 소켓을 어떻게 사용할 수 있습니까?

소켓 연결 설정


먼저 소켓 연결을 설정해야 합니다(핵심 코드)

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);

소켓 입력 및 출력 스트림을 처리합니다


소켓 연결을 성공적으로 설정한 후 입력 출력 스트림을 얻을 수 있으므로 통신의 본질은 입력 및 출력 스트림을 처리하는 것입니다. 입력 스트림을 통해 네트워크 연결의 데이터를 읽고, 출력 스트림을 통해 로컬 데이터를 원격 엔드로 전송합니다.

소켓 연결은 실제로 파일 스트림 처리와 다소 유사하며 둘 다 IO 작업을 수행합니다.

입력 및 출력 스트림을 얻는 코드는 다음과 같습니다.

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

IO 스트림 처리와 관련하여 일반적으로 해당 패키징 클래스를 사용하여 IO 스트림을 처리하는 경우 바이트 단위로 작업해야 합니다. [], 이는 상대적으로 지루한 작업입니다. 래퍼 클래스를 사용하면 이를 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));
}

데이터 요청 및 응답


소켓 연결과 IO 입력 및 출력 스트림을 사용하여 요청 데이터를 보내고 요청의 응답 결과를 얻을 차례입니다.

IO 패키징 클래스의 지원으로 문자열 형식으로 직접 전송할 수 있으며 패키징 클래스는 데이터를 해당 바이트 스트림으로 변환하는 데 도움이 됩니다.

우리는 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);
}

이제 연결 생성, 전송을 위한 핵심 코드가 모두 완성되었습니다. 기본 소켓에서 요청 및 응답 수신.

전체 코드는 다음과 같습니다.

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));
  }

}

아래에서는 클라이언트를 인스턴스화하여 소켓 통신 결과를 보여줍니다.

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();

    }
  }
}

이 방법은 처음에는 유용해 보이지만 동시성이 높으면 애플리케이션에서 많은 스레드를 사용하게 됩니다. 우리 모두 알고 있듯이 서버에서 각 스레드는 실제로 파일 핸들을 차지합니다. 서버의 핸들 수는 제한되어 있으며 스레드 수가 많으면 스레드 간 전환에 상당한 소비가 발생합니다. 따라서 이 방법은 동시성이 큰 시나리오에서는 견딜 수 없습니다.

    멀티스레딩 + 스레드 풀 처리
  • 스레드가 너무 많기 때문에 생성되는 스레드 수만 제어하면 됩니다. 소켓 처리를 위해 고정된 수의 스레드만 시작됩니다. 이는 멀티 스레드 처리를 활용할 뿐만 아니라 시스템 자원 소비도 제어합니다.

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로 설정됩니다.

이 방법이 최적인 것 같습니다. 스레드가 동시에 여러 소켓 연결을 처리할 수 있고 각 소켓의 입력 및 출력 데이터가 준비되지 않은 경우 차단하지 않으면 더 좋은 것이 있습니까? 이 기술을 "IO 다중화"라고 합니다. 해당 구현은 JAVA의 nio 패키지에 제공됩니다.

위 내용은 Java가 기본 소켓 통신 메커니즘을 구현하는 방법의 원리에 대한 자세한 설명의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.