Netty TCP 패 키 지 를 해결 하 는 방법

가방 을 뜯다
       일반적으로 TCP 패 키 지 는 한 번 에 데 이 터 를 받 으 면 완전한 메시지 데 이 터 를 완전히 나타 내지 못 한다.TCP 통신 에는 왜 스티커 가 존재 합 니까?주요 원인 은 TCP 가 흐 르 는 방식 으로 데 이 터 를 처리 하 는 데다 가 인터넷 에서 MTU 는 응용 처리 하 는 메시지 데이터 보다 작 기 때문에 한 번 에 받 은 데이터 가 메시지 의 수 요 를 만족 시 키 지 못 해 패 킷 이 존재 하기 때문이다.패 킷 을 처리 하 는 유일한 방법 은 응용 층 의 데이터 통신 협 의 를 제정 하고 협 의 를 통 해 기 존 에 받 은 데이터 가 메시지 데이터 의 수 요 를 만족 시 키 는 지 규범화 하 는 것 이다.
TCP 는 바이트 흐름 을 기반 으로 하 는 전송 프로 토 콜 이라는 것 을 잘 알 고 있 습 니 다.
그러면 데이터 가 통신 층 에서 전파 되 는 것 은 강물 처럼 뚜렷 한 경계선 이 없고 데이터 가 구체 적 으로 무슨 뜻 을 나타 내 는 지,어떤 곳 에 마침표 가 있 는 지,어떤 곳 에 분점 이 있 는 지 는 TCP 밑바닥 에 대해 잘 모른다.응용 층 은 TCP 층 에 네트워크 간 전송 에 사용 되 는 8 비트 바이트 로 표 시 된 데이터 흐름 을 보 낸 다음 에 TCP 는 데이터 흐름 을 적당 한 길이 의 메시지 세그먼트 로 나 눈 다음 에 TCP 는 결 과 를 IP 층 에 전송 하여 네트워크 를 통 해 수신 단 실체의 TCP 층 에 패 키 지 를 전송 합 니 다.
그래서 이 데 이 터 를 큰 가방 으로 나 누 는 문 제 는 바로 우리 가 오늘 말 하고 자 하 는 접착 과 가방 을 뜯 는 문제 이다.
1.TCP 패키지 해체 문제 설명
가방 을 붙 이 고 뜯 는 다 는 두 가지 개념 은 아직 잘 모 르 실 것 같 습 니 다.다음 그림 을 통 해 분석 해 보 겠 습 니 다.

클 라 이언 트 가 각각 두 개의 패 킷 D1,D2 개의 서버 를 보낸다 고 가정 하지만 전송 과정 에서 데이터 가 어떤 형식 으로 전파 되 는 지 는 잘 모 르 겠 습 니 다.각각 다음 과 같은 4 가지 상황 이 있 습 니 다.
4.567917.서버 에서 D1 과 D2 두 개의 패 킷 을 한 번 에 받 았 고 두 개의 패 킷 이 붙 어 있 는 것 을 패 킷 이 라 고 합 니 다4.567917.서버 에서 두 번 에 걸 쳐 패 킷 D1 과 D2 를 읽 었 고 패 킷 을 붙 이 고 뜯 는 일이 발생 하지 않 았 습 니 다4.567917.서버 에서 두 번 에 걸 쳐 패 킷 을 읽 었 고 D1 과 D2 의 일부 내용 을 처음 읽 었 으 며 두 번 째 로 D2 의 나머지 부분 을 읽 었 는데 이것 을 뜯 는 것 이 라 고 합 니 다
  • 서버 는 세 번 에 걸 쳐 데이터 부분 을 읽 었 고,첫 번 째 는 D1 팩,두 번 째 는 D2 팩 의 일부 내용 을 읽 었 고,세 번 째 는 D2 팩 의 나머지 내용 을 읽 었 다
  • 2.TCP 접착 원인
    TCP 프로 토 콜 에서 데 이 터 를 TCP 가 보 내기 에 가장 적합 하 다 고 생각 하 는 데이터 블록 으로 분할 하 는 것 을 알 고 있 습 니 다.이 부분 은'MSS'(최대 패 킷 길이)옵션 을 통 해 제어 되 는데 보통 이런 메커니즘 은 일종 의 협상 체제 라 고도 부 릅 니 다.MSS 는 TCP 가 다른 한쪽 으로 전달 하 는 최대 데이터 블록 의 길 이 를 규정 합 니 다.이 값 TCP 프로 토 콜 은 실 현 될 때 MTU 값 으로 대체 되 기 때문에 MSS 는 1460 이다.통신 쌍방 은 쌍방 이 제공 한 MSS 의 최소 값 에 따라 이번 연결 의 최대 MSS 값 으로 확정 할 것 이다.
    tcp 는 성능 을 향상 시 키 기 위해 전송 단 은 보 내야 할 데 이 터 를 버퍼 에 보 내 고 버퍼 가 가득 찬 후에 버퍼 에 있 는 데 이 터 를 수신 자 에 게 보 냅 니 다.마찬가지 로 수신 자 에 게 도 버퍼 와 같은 메커니즘 이 있어 데 이 터 를 받는다.
    포장 을 뜯 는 원인 은 주로 다음 과 같다.
  • 응용 프로그램 이 데 이 터 를 기록 하 는 바이트 크기 가 소켓 전송 버퍼 의 크기 보다 크 면 패 키 지 를 뜯 을 수 있 습 니 다
  • MSS 크기 의 TCP 분 단 을 진행한다.MSS 는 TCP 메시지 세그먼트 의 데이터 필드 의 최대 길이 로 TCP 메시지 길이-TCP 머리 길이>mss 일 때 패 키 지 를 뜯 습 니 다4.567917.응용 프로그램 이 기록 한 데 이 터 는 소켓 버퍼 크기 보다 작 습 니 다.네트워크 카드 는 여러 번 기록 한 데 이 터 를 네트워크 에 보 내 면 붙 임 이 발생 합 니 다4
  • 데이터 팩 이 MTU 보다 클 때 슬라이스 를 합 니 다.MTU 즉(Maxitum Transmission Unit)의 최대 전송 부 서 는 이 더 넷 전송 전기 방면 의 제한 으로 인해 모든 이 더 넷 프레임 은 최소 크기 의 64bytes 가 있 고 최대 1518 bytes 를 초과 할 수 없 으 며 이 더 넷 프레임 의 프레임 헤드 14Bytes 와 프레임 꼬리 CRC 검사 부분 4Bytes 를 제외 하고그러면 상부 협 의 를 적재 하 는 곳,즉 Data 필드 가 가장 크 면 1500 Bytes 라 는 값 만 있 을 수 있 습 니 다.우 리 는 이 를 MTU 라 고 부 릅 니 다.이것 이 바로 네트워크 계층 프로 토 콜 이 매우 관심 을 가 지 는 부분 이다.왜냐하면 네트워크 계층 프로 토 콜,예 를 들 어 IP 프로 토 콜 은 이 값 에 따라 상부 에서 전 달 된 데 이 터 를 나 눌 지 여 부 를 결정 하기 때문이다
  • 3.TCP 접착 가방 을 어떻게 해결 합 니까?
    우 리 는 tcp 가 무한 한 데이터 흐름 이라는 것 을 알 고 있 으 며,프로 토 콜 자체 가 패 키 지 를 붙 이 고,패 키 지 를 뜯 는 것 을 피 할 수 없다.그러면 우 리 는 응용 층 데이터 프로 토 콜 에서 만 제어 할 수 있다.보통 전송 데 이 터 를 만 들 때 다음 과 같은 방법 을 사용 할 수 있 습 니 다.
    4.567917.정 해진 메 시 지 를 설정 하고 서버 에서 정 해진 길이 의 내용 을 읽 을 때마다 전체 메시지 로 합 니 다4.567917.메시지 헤더 가 있 는 프로 토 콜,메시지 헤더 로 메시지 시작 표지 와 메시지 길이 정 보 를 저장 하고 서버 에서 메시지 헤드 를 얻 을 때 메시지 길 이 를 분석 한 다음 에 이 길이 의 내용 을 뒤로 읽 습 니 다4.567917.메시지 경 계 를 설정 하고 서버 는 네트워크 흐름 에서 메시지 경계 에 따라 메시지 내용 을 분리 합 니 다.예 를 들 어 메시지 의 끝 에 줄 바 꿈 자 를 붙 여 메시지 의 끝 을 구분 하 는 데 사용 합 니 다물론 응용 층 은 이 문 제 를 해결 할 수 있 는 더 복잡 한 방법 이 있 습 니 다.이것 은 네트워크 층 의 문제 에 속 합 니 다.우 리 는 자바 가 제공 하 는 방식 으로 이 문 제 를 해결 하 는 것 이 좋 습 니 다.우 리 는 먼저 예 를 들 어 끈 적 임 이 어떻게 발생 하 는 지 보 자.
    서버:
    
    public class HelloWordServer {
        private int port;
    
        public HelloWordServer(int port) {
            this.port = port;
        }
    
        public void start(){
            EventLoopGroup bossGroup = new NioEventLoopGroup();
            EventLoopGroup workGroup = new NioEventLoopGroup();
    
            ServerBootstrap server = new ServerBootstrap().group(bossGroup,workGroup)
                                        .channel(NioServerSocketChannel.class)
                                        .childHandler(new ServerChannelInitializer());
    
            try {
                ChannelFuture future = server.bind(port).sync();
                future.channel().closeFuture().sync();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                bossGroup.shutdownGracefully();
                workGroup.shutdownGracefully();
            }
        }
    
        public static void main(String[] args) {
            HelloWordServer server = new HelloWordServer(7788);
            server.start();
        }
    }
    서버 초기 화:
    
    public class ServerChannelInitializer extends ChannelInitializer<SocketChannel> {
        @Override
        protected void initChannel(SocketChannel socketChannel) throws Exception {
            ChannelPipeline pipeline = socketChannel.pipeline();
    
            //           
            pipeline.addLast("decoder", new StringDecoder());
            pipeline.addLast("encoder", new StringEncoder());
    
            //      Handler
            pipeline.addLast("handler", new HelloWordServerHandler());
        }
    }
    서버 핸들 러:
    
    public class HelloWordServerHandler extends ChannelInboundHandlerAdapter {
        private int counter;
    
        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
            String body = (String)msg;
            System.out.println("server receive order : " + body + ";the counter is: " + ++counter);
        }
    
        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
            super.exceptionCaught(ctx, cause);
        }
    }
    클 라 이언 트:
    
    public class HelloWorldClient {
        private  int port;
        private  String address;
    
        public HelloWorldClient(int port,String address) {
            this.port = port;
            this.address = address;
        }
    
        public void start(){
            EventLoopGroup group = new NioEventLoopGroup();
    
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(group)
                    .channel(NioSocketChannel.class)
                    .handler(new ClientChannelInitializer());
    
            try {
                ChannelFuture future = bootstrap.connect(address,port).sync();         
                future.channel().closeFuture().sync();
            } catch (Exception e) {
                e.printStackTrace();
            }finally {
                group.shutdownGracefully();
            }
    
        }
    
        public static void main(String[] args) {
            HelloWorldClient client = new HelloWorldClient(7788,"127.0.0.1");
            client.start();
        }
    }
    클 라 이언 트 초기 화:
    
    public class ClientChannelInitializer extends  ChannelInitializer<SocketChannel> {
    
        protected void initChannel(SocketChannel socketChannel) throws Exception {
            ChannelPipeline pipeline = socketChannel.pipeline();
    
            pipeline.addLast("decoder", new StringDecoder());
            pipeline.addLast("encoder", new StringEncoder());
    
            //       
            pipeline.addLast("handler", new HelloWorldClientHandler());
        }
    }
    클 라 이언 트 핸들 러:
    
    public class HelloWorldClientHandler extends ChannelInboundHandlerAdapter {
        private byte[] req;
        private int counter;
    
        public BaseClientHandler() {
            req = ("Unless required by applicable law or agreed to in writing, software
    " + " distributed under the License is distributed on an \"AS IS\" BASIS,
    " + " WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    " + " See the License for the specific language governing permissions and
    " + " limitations under the License.This connector uses the BIO implementation that requires the JSSE
    " + " style configuration. When using the APR/native implementation, the
    " + " penSSL style configuration is required as described in the APR/native
    " + " documentation.An Engine represents the entry point (within Catalina) that processes
    " + " every request. The Engine implementation for Tomcat stand alone
    " + " analyzes the HTTP headers included with the request, and passes them
    " + " on to the appropriate Host (virtual host)# Unless required by applicable law or agreed to in writing, software
    " + "# distributed under the License is distributed on an \"AS IS\" BASIS,
    " + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    " + "# See the License for the specific language governing permissions and
    " + "# limitations under the License.# For example, set the org.apache.catalina.util.LifecycleBase logger to log
    " + "# each component that extends LifecycleBase changing state:
    " + "#org.apache.catalina.util.LifecycleBase.level = FINE" ).getBytes(); } @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { ByteBuf message; // message = Unpooled.buffer(req.length); message.writeBytes(req); ctx.writeAndFlush(message); } @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { String buf = (String)msg; System.out.println("Now is : " + buf + " ; the counter is : "+ (++counter)); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { ctx.close(); } }
    클 라 이언 트 와 서버 를 실행 하면 다음 을 볼 수 있 습 니 다.

    우 리 는 이 긴 문자열 이 2 단 으로 절단 되 어 보 내 는 것 을 보 았 는데,이것 이 바로 가방 을 뜯 는 현상 이 발생 한 것 이다.마찬가지 로 패 키 지 를 붙 이 는 것 도 시 뮬 레이 션 하기 쉽 습 니 다.BaseClient Handler 의 channel Active 방법 에 있 는 것 을:
    
    message = Unpooled.buffer(req.length);
    message.writeBytes(req);
    ctx.writeAndFlush(message);
    이 몇 줄 의 코드 는 우리 위의 긴 문자열 문 자 를 바 꾸 어 byte 배열 로 흐름 에 써 서 보 내 는 것 입 니 다.그러면 우 리 는 여기에서 위 에서 메 시 지 를 보 낸 이 몇 줄 을 몇 번 순환 할 수 있 습 니 다.이렇게 보 내 는 내용 이 증가 하면 가방 을 뜯 을 때 위의 메시지 의 일부분 을 다음 메시지 에 배분 할 수 있 습 니 다.다음 과 같이 수정 할 수 있 습 니 다.
    
    for (int i = 0; i < 3; i++) {
        message = Unpooled.buffer(req.length);
        message.writeBytes(req);
        ctx.writeAndFlush(message);
    }
    수정 한 후에 우 리 는 다시 실행 합 니 다.출력 이 너무 길 어서 캡 처 하기 어렵 습 니 다.우 리 는 출력 결과 에서 세 번 순환 한 메 시 지 를 볼 수 있 습 니 다.서버 에서 받 은 것 은 이전의 완전한 것 이 아니 라 네 번 으로 나 누 어 보 냈 습 니 다.
    위 에 나타 난 패 키 지 를 붙 이 고 뜯 는 문제 에 대해 Netty 는 이미 고려 하고 실시 하 는 방안 이 있 습 니 다:LineBased Frame Decoder.
    ServerChannel Initializer 를 다시 쓰 겠 습 니 다.
    
    public class ServerChannelInitializer extends ChannelInitializer<SocketChannel> {
        @Override
        protected void initChannel(SocketChannel socketChannel) throws Exception {
            ChannelPipeline pipeline = socketChannel.pipeline();
    
    
            pipeline.addLast(new LineBasedFrameDecoder(2048));       
            //           
            pipeline.addLast("decoder", new StringDecoder());
            pipeline.addLast("encoder", new StringEncoder());
    
            //      Handler
            pipeline.addLast("handler", new BaseServerHandler());
        }
    }
    신규:pipeline.addLast(new LineBasedFrameDecoder(2048).또한,우 리 는 위 에서 보 낸 메 시 지 를 BaseClient Handler 로 개조 해 야 합 니 다.
    
    public class BaseClientHandler extends ChannelInboundHandlerAdapter {
        private byte[] req;
        private int counter;
    
        req = ("Unless required by applicable dfslaw or agreed to in writing, software" +
                    "  distributed under the License is distributed on an \"AS IS\" BASIS," +
                    "  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied." +
                    "  See the License for the specific language governing permissions and" +
                    "  limitations under the License.This connector uses the BIO implementation that requires the JSSE" +
                    "  style configuration. When using the APR/native implementation, the" +
                    "  penSSL style configuration is required as described in the APR/native" +
                    "  documentation.An Engine represents the entry point (within Catalina) that processes" +
                    "  every request.  The Engine implementation for Tomcat stand alone" +
                    "  analyzes the HTTP headers included with the request, and passes them" +
                    "  on to the appropriate Host (virtual host)# Unless required by applicable law or agreed to in writing, software" +
                    "# distributed under the License is distributed on an \"AS IS\" BASIS," +
                    "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied." +
                    "# See the License for the specific language governing permissions and" +
                    "# limitations under the License.# For example, set the org.apache.catalina.util.LifecycleBase logger to log" +
                    "# each component that extends LifecycleBase changing state:" +
                    "#org.apache.catalina.util.LifecycleBase.level = FINE
    " ).getBytes(); @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { ByteBuf message; message = Unpooled.buffer(req.length); message.writeBytes(req); ctx.writeAndFlush(message); } @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { String buf = (String)msg; System.out.println("Now is : " + buf + " ; the counter is : "+ (++counter)); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { ctx.close(); } }
    "모든 것 을 제거 합 니 다."문자열 끝 에 있 는 이것 만 유지 합 니 다.이 유 는 잠시 후에 다시 이야기 하 자.channelActive 방법 에서 우 리 는 더 이상 순환 으로 메 시 지 를 여러 번 보 낼 필요 가 없습니다.한 번 만 보 내 면 됩 니 다.(첫 번 째 예 에서 한 번 보 낼 때 가방 을 뜯 는 일이 발생 했 습 니 다)그리고 다시 실행 합 니 다.이렇게 긴 문자열 이 한 줄 만 보 내 면 끝 납 니 다.프로그램 출력 은 캡 처 하지 않 겠 습 니 다.다음은 LineBased Frame Decoder 를 설명 하 겠 습 니 다.
    LineBased FrameDecoder 의 작업 원 리 는 ByteBuf 의 읽 을 수 있 는 바 이 트 를 순서대로 옮 겨 다 니 며'있 는 지'또는'\r'가 있 는 지 판단 하 는 것 이다.있 으 면 이 위 치 를 끝 위치 로 하고 읽 을 수 있 는 색인 부터 끝 위치 구간 의 바이트 까지 한 줄 로 구성 된다.줄 바 꿈 자 를 끝 표시 로 하 는 디코더 입 니 다.끝 자 를 지 니 거나 끝 자 를 지 니 지 않 는 두 가지 디 코딩 방식 을 지원 하 며 한 줄 의 최대 길 이 를 설정 할 수 있 습 니 다.최대 길 이 를 연속 으로 읽 은 후에 도 줄 바 꿈 자 를 발견 하지 못 하면 이상 을 던 지고 이전에 읽 은 이상 코드 흐름 을 무시 합 니 다.이것 은 우리 가 메시지 의 최대 길 이 를 확정 하 는 응용 장면 에 도 도움 이 된다.
    위의 판단 에 대해 서 는'또는'r'가 있 는 지 없 는 지 를 마지막 표지 로 삼 아 우 리 는'또는'r'가 없 으 면 메시지 가 끝 났 는 지 를 판단 할 수 있 는 다른 방법 이 있 을 까?걱정 하지 마 세 요.Netty 는 이것 에 대해 이미 고려 하고 있 습 니 다.또 다른 디코더 가 우리 가 문 제 를 해결 하 는 데 도움 을 줄 수 있 습 니 다.
    여기 서 Netty 가 TCP 패 키 지 를 뜯 는 방법 에 관 한 글 을 소개 합 니 다.더 많은 Netty 해결 TCP 패 키 지 를 뜯 는 내용 은 우리 의 이전 글 을 검색 하거나 아래 의 관련 글 을 계속 조회 하 시기 바 랍 니 다.앞으로 많은 응원 부탁드립니다!

    좋은 웹페이지 즐겨찾기