宽禁带半导体 Tomcat UI Automator HTML框架 数据算法 劝酒文化 ajax security authentication directory sql视频教程 鼠标失去焦点事件 oracle行转列函数 java二维数组赋值 mysql教程 郑州普通话 python指数函数 python实例教程 java开发接口 如何查看java版本 java获得当前日期 java连接sql linux入门基础 计算机电子书 球中的小鬼 一键换系统 h370主板 飞猪ip 矩阵分析与应用 服务器系统安装 网络适配器下载 魔兽地图七个人 8元秒电脑 R语言初学者指南 pr动态字幕 男网红头像 diskman 平原门下客三千 ps高手教程 例外被抛出且未被接住
当前位置: 首页 > 学习教程  > 编程语言

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

2020/8/31 14:05:50 文章标签:

一、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 TimeServerHandler());			
		}
		
	}
	
	private static class TimeServerHandler 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);
        }
    };

}

运行结果

 


本文链接: http://www.dtmao.cc/news_show_150178.shtml

附件下载

相关教程

    暂无相关的数据...

共有条评论 网友评论

验证码: 看不清楚?