一、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);
}
};
}
运行结果