IOS和Android与Netty服务端通信,并解决拆包/粘包问题

1 篇文章 0 订阅
1 篇文章 0 订阅

一、Netty服务端

    导入netty依赖

<dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-all</artifactId>
    <version>4.1.51.Final</version>
</dependency>

服务端开启两组线程用来接受连接和处理io读写,用界定符"$_$"做为消息的结束标志,监听8088端口,收到消息后连续向客户端发送10条测试消息。

package com.guoliang.server;

import java.nio.charset.Charset;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;

public class NettyServer {
	public static void main(String[] args) throws InterruptedException {
		
		EventLoopGroup bossGroup = new NioEventLoopGroup(1);
		EventLoopGroup childGroup = new NioEventLoopGroup(5);
		
		try {
			ServerBootstrap b = new ServerBootstrap();
			b.group(bossGroup, childGroup)
			 .channel(NioServerSocketChannel.class)
			 .option(ChannelOption.SO_BACKLOG, 1024)
			 .childHandler(new ChildChannelHandler());
			
			ChannelFuture f =  b.bind(8088).sync();			
			f.channel().closeFuture().sync();

		} finally {
			childGroup.shutdownGracefully();
			bossGroup.shutdownGracefully();
		}
		 
	}
	
	
	private static class ChildChannelHandler extends ChannelInitializer<SocketChannel> {

		@Override
		protected void initChannel(SocketChannel ch) throws Exception {
			ByteBuf buf = Unpooled.copiedBuffer("$_$".getBytes("utf-8"));
			ch.pipeline()
			  //.addLast(new LineBasedFrameDecoder(1024))
			  .addLast(new DelimiterBasedFrameDecoder(1024, buf))
			  .addLast(new StringDecoder(Charset.forName("utf-8")))
			  .addLast(new ServerHandler());			
		}
		
	}
	
	private static class ServerHandler extends ChannelInboundHandlerAdapter {
		
		@Override
		public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
			String message = (String) msg;
			System.out.println("received a message:" + message);
			
			for (int i = 0; i < 10; i++) {
				String timeStamp = "test message" + i + "$_$";
				ByteBuf resp = Unpooled.copiedBuffer(timeStamp.getBytes("utf-8"));
				ctx.write(resp);
			}


		}
		
		@Override
		public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
			ctx.flush();
		}
		
		@Override
		public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
			ctx.close();
		}
	}
}

二、IOS客户端

导入CocoaAsyncSocket库

pod 'CocoaAsyncSocket'

引入GCDAsyncSocket头文件

#import <GCDAsyncSocket.h>

读取消息的基本思路为创建一个长度为1024的byte数组用于接收读取的数据,每次读取数据后记录offSet以便下次读取,并进行如下操作

  (1) 匹配byte数组中是否存在界定符"$_$",如果存在返回第一个"$"的index进行(2)操作,否则返回-1继续读取数据到byte数组中

  (2) 接收(1)返回的index进行如下操作

      ① 取出byte数组中[0,index)的所有byte即为一个消息,转为String
      ② 将此次匹配到的界定符"$_$"从byte数组中丢弃,并把剩余byte移到byte数组头部,重置offSet
      ③ 继续进行(1)操作,匹配是否还存在界定符


#import "AppDelegate.h"
#import <GCDAsyncSocket.h>

@interface AppDelegate ()<GCDAsyncSocketDelegate>
{
    GCDAsyncSocket *socket;
    Byte *msgByte; //store the received bytes
    int off;      //offSet of the msgByte
    int maxLen;   //max length of the msgByte
    
    NSString *delimitStr;
    Byte *delimiterByte;  // byte array of delimiter
    int delimiterByteLen; // length of delimiterByte
}
@end

@implementation AppDelegate


- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
   
    NSString *host = @"192.168.101.14";
    int port = 8088;
    
    maxLen = 1024;
    msgByte = (Byte*) malloc(maxLen);
    off = 0;
    
    delimitStr = @"$_$";
    
    NSData *delimitData = [delimitStr dataUsingEncoding:NSUTF8StringEncoding];
    delimiterByteLen = (int)[delimitData length];
    delimiterByte = (Byte *) malloc(delimiterByteLen);
    memcpy(delimiterByte, [delimitData bytes], delimiterByteLen);
           
    socket =  [[GCDAsyncSocket alloc] initWithDelegate:self 
           delegateQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)];
    
    NSError *error = nil;
    [socket connectToHost:host onPort:port error:&error];
    
    if (error) {
        NSLog(@"connect failed %@...........", error);
    }
  
    return YES;
}

#Socket delegate
- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port {
    NSLog(@"connect success......");
    //write data
    NSString *str = [NSString stringWithFormat:@"Hello I am ios%@", delimitStr];
    NSDate *data = [str dataUsingEncoding:NSUTF8StringEncoding];
    [socket writeData:data withTimeout:-1 tag:10086] ;
}

- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err {
    if (err) {
        NSLog(@"lost connect....");
    } else {
        NSLog(@"connect alive");
    }
}

- (void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag {
    
    [sock readDataWithTimeout:-1 tag:tag];
}

// 读取数据
-(void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag {
    
    int len = (int)data.length;
    Byte *byteData = (Byte*)malloc(len);
    memcpy(byteData, [data bytes], len);
    off = [self read:msgByte source:byteData offSet:off length:len];
    free(byteData);
    //match delimiter until offset
    
    int index;
    while ((index = [self delimiterIndex:msgByte end:off delimiterByte:delimiterByte delimiterLength:delimiterByteLen]) != -1) {
        //match delimiter success,fetch valid bytes from msgByte
        NSString *message = [[NSString alloc] initWithBytes:msgByte length:index encoding:NSUTF8StringEncoding];
        NSLog(@"received a message:%@", message);
        
        //move remain bytes to the msgByte head and reset offset
        int begin = index + delimiterByteLen; //first byte index of remain bytes
        for (int i = begin; i < off; i++) {
            msgByte[i - begin] = msgByte[i];
        }
        off = off - begin;
        
    }
    
    if (off >= maxLen) {
         NSLog(@"data too long,with %d bytes not found dilimiter",maxLen);
         [socket disconnect];
    }
    
}

//获取源byte数组在0-end中,界定符在byte数组中的index,不存在返回-1
-(int) delimiterIndex:(Byte *)  src end:(int) end delimiterByte:(Byte *) delimiters delimiterLength:(int) len  {
    int j = 0;
    for (int i = 0; i < end; i++) {
        if (src[i] == delimiters[j]) {
            j++;
        } else {
            j = 0;
            continue;
        }
        if (j == len) {
            return i - len +1;
        }
    }
    return -1;
}

-(int) read:(Byte *) dest source:(Byte *) src offSet:(int) off length:(int) len{
    for (int i = 0; i < len; i++) {
        dest[off] = src[i];
        off ++;
        if (off >= maxLen) {
            break;
        }
    }
    return off;
}

//......................

}

@end

三、Android客户端

导入netty依赖

implementation group: 'io.netty', name: 'netty-all', version: '4.1.51.Final'

网络权限

<uses-permission android:name="android.permission.INTERNET" />

android的network操作不能在mian线程中,所以新建一个线程建立连接

package com.fusibang.nettyclient;

import android.os.Bundle;

import androidx.appcompat.app.AppCompatActivity;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.widget.TextView;
import java.nio.charset.Charset;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;

public class MainActivity extends AppCompatActivity {

    private TextView textView;
    private final String delimiter = "$_$";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        this.textView = findViewById(R.id.home_text);

        new Thread(new Runnable() {
            @Override
            public void run() {
                EventLoopGroup group = new NioEventLoopGroup();
                try {
                    Bootstrap b = new Bootstrap();
                    final ClientHandler client = new  ClientHandler();
                    b.group(group)
                            .channel(NioSocketChannel.class)
                            .option(ChannelOption.TCP_NODELAY, true)
                            .handler(new ChannelInitializer<SocketChannel>() {

                                @Override
                                protected void initChannel(SocketChannel ch) throws Exception {
                                    ByteBuf buf = Unpooled.copiedBuffer(delimiter.getBytes());
                                    ch.pipeline()
                                            //.addLast(new LineBasedFrameDecoder(5))
                                            .addLast(new DelimiterBasedFrameDecoder(1024, buf))
                                            .addLast(new StringDecoder(Charset.forName("utf-8")))
                                            .addLast(client);

                                }
                            });

                    ChannelFuture f = b.connect("192.168.101.14", 8088).sync();
                    f.channel().closeFuture().sync();
                } catch (Exception e) {
                    e.printStackTrace();
                }
                finally {
                    group.shutdownGracefully();
                }
            }
        },"netty-thread").start();

    }

    private class ClientHandler extends ChannelInboundHandlerAdapter {

        @Override
        public void channelActive(ChannelHandlerContext ctx) throws Exception {
            String message = "Hello I am android" + delimiter;
            ByteBuf buf = Unpooled.copiedBuffer(message.getBytes("utf-8"));
            ctx.writeAndFlush(buf);

        }

        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
            String message = (String) msg;
            Message mg = Message.obtain();
            mg.obj = message;
            handler.sendMessage(mg);

        }

        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
            cause.printStackTrace();
            ctx.close();
        }

    }

    private Handler handler = new Handler() {
        @Override
        public void handleMessage(Message message) {
            String msg = (String) message.obj;
            textView.setText(msg);
            Log.i("tag", msg);
        }
    };

}

运行结果

 

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值