쩨이엠 개발 블로그

[ TCP ] TCP Server 만들기 본문

개발/JAVA

[ TCP ] TCP Server 만들기

쩨이엠 2021. 1. 5. 17:13
728x90
반응형

TCP 프로토콜 통신을 위해 TCP Server를 만들어보기로 한다

 

Client에서  통신을 보내면 Server에서 받도록 프로젝트를 만들어보기로 한다

TCP Client --> TCP Server

 

 

개발환경

  • Spring Boot 2.3.4.RELEASE
  • Gradle 7.0
  • Java 11

Package

 

 

build.gradle

 
 plugins {
    id 'java'
    id 'org.springframework.boot' version '2.3.4.RELEASE'
    id 'io.spring.dependency-management' version '1.0.10.RELEASE'
 }

 group 'com.test.iot'
 version '1.0-SNAPSHOT'

 repositories {
    mavenCentral()
 }

 dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-validation'
    implementation 'org.springframework.boot:spring-boot-starter-integration'
    implementation 'org.springframework.integration:spring-integration-ip'

    compileOnly 'org.projectlombok:lombok'
    annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
    annotationProcessor 'org.projectlombok:lombok'
    
 }
 

TCP 통신을 위한 dependency들을 넣어준다

lombok은 compile과 annotation으로 넣어준다

 

 

application.yml

 
 tcp:
   server:
     port: 8092
     

TCP 통신할 포트를 적어준다

이는 Config파일에서 쓰일 예정

 

 

TCPServerProperties.java

 
 package com.test.iot.config;

 import lombok.Getter;
 import lombok.Setter;
 import org.springframework.boot.context.properties.ConfigurationProperties;

 @ConfigurationProperties("tcp.server")
 @Getter
 @Setter
 public class TcpServerProperties {
    int port;
 }
 

ConfigurationProperties에서 prefix로 tcp.server를 가져오면 application.yml의 port인 8092가 TCPServerProperties.port의 값에 들어간다

 

TcpServerConfig.java

 
 @Configuration
 @EnableIntegration
 @EnableConfigurationProperties(TcpServerProperties.class)
 public class TcpServerConfig {

    @Bean
    public CustomTcpSerializer serializer() {
        return new CustomTcpSerializer();
    }

    @Bean
    public AbstractServerConnectionFactory connectionFactory(TcpServerProperties tcpServerProperties, CustomTcpSerializer serializer) {
        TcpNioServerConnectionFactory connectionFactory = new TcpNioServerConnectionFactory(tcpServerProperties.getPort());
        connectionFactory.setSerializer(serializer);
        connectionFactory.setDeserializer(serializer);
        connectionFactory.setSingleUse(true);
        return connectionFactory;
    }

    @Bean
    public MessageChannel inboundChannel() {
        return new DirectChannel();
    }

    @Bean
    public MessageChannel replyChannel() {
        return new DirectChannel();
    }

    @Bean
    public TcpInboundGateway inboundGateway(AbstractServerConnectionFactory connectionFactory, MessageChannel inboundChannel, MessageChannel replyChannel) {
        TcpInboundGateway tcpInboundGateway = new TcpInboundGateway();
        tcpInboundGateway.setConnectionFactory(connectionFactory);
        tcpInboundGateway.setRequestChannel(inboundChannel);
        tcpInboundGateway.setReplyChannel(replyChannel);
        return tcpInboundGateway;
    }
 }
 

TCP Properties(port)와 bean등록을 해준다

 

 

TcpServerEndpoint.java

 
 @MessageEndpoint
 public class TcpServerEndpoint {
    private final MessageService messageService;

    public TcpServerEndpoint(MessageService messageService) {
        this.messageService = messageService;
    }

    @ServiceActivator(inputChannel = "inboundChannel", async = "true")
    public byte[] process(byte[] message) {
        return messageService.processMessage(message);
    }
 }
 

TCP Endpoint를 지정해준다 inputChannel은 bean등록되어있는 것과 같은 이름으로 넣어준다

 

 

CustomTcpSerializer.java

 
 public class CustomTcpSerializer extends AbstractPooledBufferByteArraySerializer {

    public static final CustomTcpSerializer INSTANCE = new CustomTcpSerializer();

    public static final int STX = 0x02;

    public static final int ETX = 0x03;

    @Override
    public byte[] doDeserialize(InputStream inputStream, byte[] buffer) throws IOException {
        int bite = inputStream.read();
        if (bite < 0) {
            throw new SoftEndOfStreamException("Stream closed between payloads");
        }
        buffer[0] = (byte) bite;
        int length = inputStream.read();
        if (length < 0) {
            throw new SoftEndOfStreamException("Stream closed between payloads");
        }
        buffer[1] = (byte) length;
        int n = 2;
        try {
            if (bite != STX) {
                throw new MessageMappingException("Expected STX to begin message");
            }
            while (length != n) {
                bite = inputStream.read();
                checkClosure(bite);
                buffer[n++] = (byte) bite;
                if (n >= getMaxMessageSize()) {
                    throw new IOException("ETX not found before max message length: " + getMaxMessageSize());
                }
            }
            if (bite != ETX) {
                throw new MessageMappingException("Expected ETX to end message");
            }
            return copyToSizedArray(buffer, n);
        } catch (IOException e) {
            publishEvent(e, buffer, n);
            throw e;
        } catch (RuntimeException e) {
            publishEvent(e, buffer, n);
            throw e;
        }
    }

    @Override
    public void serialize(byte[] bytes, OutputStream outputStream) throws IOException {
        outputStream.write(bytes);
    }
 }

MessageService로 넘기기 전에 inputStream을 읽어와야한다

TCP 통신에서 STX(Start of Text)와 ETX(End of Text) 사이에 있는 메세지를 array로 넘길 예정이다

inputStream.read()로 한 개씩 읽어온다(이건 받는 규칙마다 다름)

 

 

MessageService.java

 
 @Slf4j
 @RequiredArgsConstructor
 @Service
 public class MessageService {
    private static final byte[] DEFAULT_RESPONSE = new byte[0];

    public byte[] processMessage(byte[] message) {
        log.info("Receive message : {}", message);
        ...
        return DEFAULT_RESPONSE;
    }
 }
 

실제 메세지를 받고나서 비지니스로직이 돌아가는 Service단

 

 

TcpApplication.java

 
 @SpringBootApplication
 public class TcpApplication {

    public static void main(String[] args) {
        SpringApplication.run(TcpApplication.class, args);
    }

 }
 

spring boot application 돌아가기 위해선 꼭 필요한 @SpringBootApplication

 

 

Application을 시작해보면 잘 올라오는 것을 확인 할 수 있다

테스트는 Client를 만들어 확인해보기로 한다

728x90
반응형
Comments