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 의 나머지 부분 을 읽 었 는데 이것 을 뜯 는 것 이 라 고 합 니 다
TCP 프로 토 콜 에서 데 이 터 를 TCP 가 보 내기 에 가장 적합 하 다 고 생각 하 는 데이터 블록 으로 분할 하 는 것 을 알 고 있 습 니 다.이 부분 은'MSS'(최대 패 킷 길이)옵션 을 통 해 제어 되 는데 보통 이런 메커니즘 은 일종 의 협상 체제 라 고도 부 릅 니 다.MSS 는 TCP 가 다른 한쪽 으로 전달 하 는 최대 데이터 블록 의 길 이 를 규정 합 니 다.이 값 TCP 프로 토 콜 은 실 현 될 때 MTU 값 으로 대체 되 기 때문에 MSS 는 1460 이다.통신 쌍방 은 쌍방 이 제공 한 MSS 의 최소 값 에 따라 이번 연결 의 최대 MSS 값 으로 확정 할 것 이다.
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 패 키 지 를 뜯 는 내용 은 우리 의 이전 글 을 검색 하거나 아래 의 관련 글 을 계속 조회 하 시기 바 랍 니 다.앞으로 많은 응원 부탁드립니다!
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
[ 네티 인 액션 ] 7. EventLoop와 스레딩 모델또한 애플리케이션의 동시성 요건이나 전반적인 복잡성 때문에 프로젝트의 수명주기 동안 다른 스레드 관련 문제가 발생할 수 있다. 1. io.netty.util.concurrent 패키지는 JDK 패키지인 java.ut...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.