본문 바로가기

Java/Vert.x

버텍스 코어: TCP 서버

더보기

Vertx를 사용하면 간단하게 논 블로킹(Non blocking) TCP 서버와 클라이언트를 구현 할 수 있습니다.

TCP 서버를 생성하려면 Vertx 객체의 createNetServer()를 호출합니다.

NetServer server = vertx.createNetServer();

TCP 서버 구성

TCP 서버 환경 설정을 변경하려면 NetServerOptions를 사용하여 TCP 서버를 생성합니다.

NetServerOptions options = new NetServerOptions().setPort(10200);
NetServer server = vertx.createNetServer(options);

listen()

TCP 서버가 클라이언트의 연결을 수락하기 위해서 listen()을 호출합니다. TCP 서버 구성에 따라서 호스트포트가 지정되며 별도의 구성을 입력하지 않았다면 디폴트로 처리됩니다.

NetServer server = vertx.createNetServer(options);
server.listen();
더보기

디폴트 호스트 및 디폴트 포트는 NetServerOptions에서 정의됩니다.

public static final int DEFAULT_PORT = 0;
public static final String DEFAULT_HOST = "0.0.0.0";

디폴트 호스트: 0.0.0.0은 호스트에서 사용 가능한 모든 IP 주소에 대한 연결을 허용합니다.

디폴트 포트: 0은 로컬 호스트에서 아직 할당되지 않은 임의의 포트를 사용합니다.

임의의 호스트:두 번째 인자 및 포트:첫 번째 인자를 지정하려면 다음과 같이 작성합니다.

NetServer server = vertx.createNetServer();
server.listen(10200, "localhost");

listen 상태를 요청한 이후 바인딩까지는 비동기로 처리되며 메소드가 리턴된 이후 얼마동안은 서버가 실제로 수신하고 있지 않을 수 있습니다.

listen 상태가 되었음을 콜백받으려면 핸들러 함수를 사용합니다.

NetServer server = vertx.createNetServer();
server.listen(10200, "localhost", res -> {
	if (res.succeeded()) {
		System.out.println("Server is now listening!");
	} else {
		System.out.println("Failed to bind!");
	}
});

임의의 포트:0과 actualPort()

임의의 포트: 0을 listen 포트로 사용하게 되면 로컬 호스트에서 아직 할당되지 않은 임의의 포트를 사용합니다.

서버에서 실제 바인딩 된 포트를 찾으려면 actualPort()를 사용합니다.

NetServer server = vertx.createNetServer();
server.listen(0, "localhost", res -> {
	if (res.succeeded()) {
		System.out.println("Server is now listening on actual port: " + server.actualPort());
	} else {
		System.out.println("Failed to bind!");
	}
});

TCP 클라이언트 연결에 대한 콜백, 소켓 처리

클라이언트가 TCP 서버에 연결되면 connectHandler()를 등록하여 콜백 받습니다.

NetServer server = vertx.createNetServer();
server.connectHandler(socket -> {
	// Handle the connection in here
});

연결이 성사되면 핸들러에서는 NetSocket 객체에 접근 할 수 있습니다.

NetSocket은 실제 연결에 대한 인터페이스이며, 물리적인 소켓으로부터 데이터 읽기 데이터 쓰기 소켓 닫기 등의 작업을 수행 할 수 있습니다.

소켓으로부터 데이터를 읽어오려면 handler()를 등록합니다.

이 핸들러는 소켓으로부터 데이터를 읽어 올 수 있을 때 Buffer 객체와 함께 호출됩니다. 버퍼에는 전달된 데이터가 작성되어 있습니다.

NetServer server = vertx.createNetServer();
server.connectHandler(socket -> {
	socket.handler(buffer -> {
		System.out.println("I received some bytes: " + buffer.length());
	});
});

소켓에 데이터를 작성하려면 write()를 사용합니다.

Buffer buffer = Buffer.buffer().appendFloat(12.34f).appendInt(123);
socket.write(buffer);

// Write a string in UTF-8 encoding
socket.write("some data");

// Write a string using the specified encoding
socket.write("some data", "UTF-16");
더보기

소켓에 버퍼를 쓰는 작업 write()는 비동기로 처리됩니다.

메소드가 리턴된 이후에도 아직 버퍼가 스트리밍되지 않았거나 또는 스트리밍 중일 수 있습니다.

따라서 버퍼를 재사용하지 않도록 주의합니다.

소켓에 연결된 IP 주소를 확인하려면 localAddress() 또는 remoteAddress()를 사용합니다.

SocketAddress local = socket.localAddress();
SocketAddress remote = socket.remoteAddress();

소켓이 닫히면 closeHandler()를 등록하여 콜백 받습니다.

socket.closeHandler(v -> {
	System.out.println("The socket has been closed");
});

예외 처리를 위한 핸들러 등록

NetSocketexceptionHandler()를 등록하면 소켓이 연결된 상태에서 발생하는 예외(Exception)을 콜백 받을 수 있습니다.

소켓이 연결되기 이전에 발생하는 예외를 콜백 받으려면 NetServerexceptionHandler()를 등록합니다(e.g. TLS handshake 과정 중 발생하는 예외 추적 등).

이벤트 버스(Event Bus) 주소를 통해 소켓에 데이터 작성

모든 소켓은 이벤트 버스에 핸들러를 자동으로 등록합니다.

이벤트 버스에 등록된 핸들러가 버퍼를 수신하게 되면 소켓에서도 버퍼를 동일하게 수신하게 됩니다.

이 방식은 클러스터링 환경으로 라우팅 되지 않는 로컬 구독(Local subscriptions)에 해당합니다.

소켓에 직접 엑세스하지 않고도, 어떤 버티클이라도 이벤트 버스의 핸들러에 버퍼를 작성하면 특정된 소켓에도 버퍼가 작성되는 효과를 낼 수 있습니다.

핸들러의 주소는 writeHandlerID()로 확인 할 수 있습니다.

vertx.createNetServer().listen().connectHandler(socket -> {
	String handlerID = socket.writeHandlerID();
	vertx.eventBus().send(handlerID, Buffer.buffer().appendString("write packet by eventbus"));
});

정리 및 복습

  • createNetServer()를 사용해 TCP 서버를 생성합니다.
  • TCP 서버 생성에 필요한 구성은 NetServerOptions을 사용합니다.
  • listen 과정에서 호스트:0.0.0.0사용 가능한 모든 IP 주소에 대해서 연결을 허용합니다. 
  • 임의의 포트:0은 아직 로컬 호스트에 할당되지 않은 임의의 포트를 사용해 listen 포트를 엽니다.
  • TCP 클라이언트가 연결되면 connectHandler()를 등록해 콜백 받습니다. 연결이 성사되면 NetSocket 객체에 접근 할 수 있습니다.
  • 소켓으로 클라이언트가 보낸 데이터를 읽고, 클라이언트로 데이터를 작성 할 수 있습니다.
  • 소켓은 자동으로 이벤트 버스 핸들러에 등록되며 핸들러 ID를 알면 소켓에 데이터를 보낼 수 있습니다.
  • 이 방식은 클러스터링 환경으로 라우팅 되지 않는 로컬 구독(Local subscriptions)에 한정됩니다.
  • 예외 처리를 위한 핸들러를 등록하면 소켓이 연결된 상태에서 발생하거나 소켓이 연결되는 과정에서 발생하는 예외를 콜백 받을 수 있습니다.