一、基础知识
1.单聊流程(两个用户之间相互聊天):
A和B聊天,首先A和B需要与服务端分别建立连接,然后进行登录,服务端用来保存用户标识和tcp连接的映射关系
A要给发消息,需要先将带有B标识的消息数据包发送到服务端,然后服务端从消息数据包中获取B的标识找到对应的B的连接,然后将消息发送给B
任意一方发消息给对方,如果对方不在线则需要将消息缓存然后等对方上线再发送
2.单聊指令:
3.群聊流程(群聊指一个组多个用户之间聊天,一个用户发到群组的消息会被组内任何成员接收):
A B C依然经历登录流程,服务端保存用户标识对应的tcp连接
A发起群聊时,将A B C的标识发送到服务端,服务端拿到标识之后建立一个群ID,然后把这个ID与A B C标识绑定
群聊中任意一方在群里聊天的时候,将群ID发送到服务端,服务端得到群ID后,取出对应的用户标识,遍历用户标识对应的tcp连接,将消息发送到每一个群聊成员
4.群聊要实现的指令集:
5.netty是什么?
IO编程:客户端每隔两秒发送一个带有时间戳的hello world给服务端,服务端收到后打印它。代码示例如下:
public class IOServer{
public static void main(String args[]) throws Exception
ServerSocket serversocket=new ServerSocket(8000);
//接收新连接线程
new Thread(()->{
while(true){
try{
//阻塞方式获取新连接
Socket socket=serverSocket.accpet();
//为每一个新连接都创建一个新线程,负责读取数据
new Thread(()->{
try{
int len;
byte[] data=new byte[1024];
InputStream inputStream=socket.getInputStream();
//按字节流的方式读取数据
while((len=inputstream.read(data))!=-1)
{
System.out.println(new String(data,0,len));
}
}catch(IOException e){
}
}).start();
}catch(IOException e){
}
}
}).start();
}
}
这段代码是服务端首先创建一个serverSocket来监听8000端口,然后创建一个线程,线程里不断调用阻塞方法serverSocket.accept()获取新连接,当获取新连接之后,为每一个新连接都创建一个新线程,这个线程负责从该连接中读物数据(以字节流的方式读取)
public class IOClient {
public static void main(String[] args) {
new Thread(() -> {
try {
Socket socket = new Socket("127.0.0.1", 8000);
while (true) {
try {
socket.getOutputStream().write((new Date() + ": hello
world").getBytes());
Thread.sleep(2000);
} catch (Exception e) {
}
}
} catch (IOException e) {
}
}).start();
}
}
客户端是连接上服务器的8000端口之后,每隔两秒就向服务器写一个带有时间戳的hello world
以上这种IO编程的模式在客户端较少的情况下运行比较良好名单对于客户端比较多的业务来说,每个连接创建成功之后都需要一个线程来维护,每个线程都包含一个while死循环,会带来以下几个问题:
线程资源受限,线程是操作操作系统的宝贵资源,同一时刻有大量的线程处于阻塞状态
线程切换效率低下,单机cpu核数固定,线程爆炸之后操作系统频繁进行线程切换
NIO编程:
在NIO模型中新来一个连接不再创建新线程,而是将这个直接绑定再某个固定的线程上,这个连接的所有的读写都有这个线程负责
两者的区别举例说明:
1.每个小朋友都配一个老师。每个老师都隔段时间询问小朋友 是否要上厕所。如果要上,就领他去厕所,100个小朋友就需 要100个老师来询问,并且每个小朋友上厕所的时候都需要一 个老师领着他去,这就是IO模型,一个连接对应一个线程。
2.所有的小朋友都配同一个老师。这个老师隔段时间询问所有 的小朋友是否有人要上厕所,然后每一时刻把所有要上厕所的 小朋友批量领到厕所,这就是NIO模型。所有小朋友都注册到 同一个老师,对应的就是所有的连接都注册到同一个线程,然 后批量轮询。
IO读写面向流,一次性只能从流中读取一个字节或者多个字节,并且读完之后流无法再读取,需要自己缓存数据,NIO的读写是面向Buffer的,可以随意读取里面的任何字节数据,不需要自己缓存数据,只需要移动读写指针即可
NIO的核心思路:
NIO模型中有两个线程,每个线程都绑定一个轮询器Selector,serverSelector负责轮询是否有新连接,clientSelector负责轮询是否有数据可读
服务端检测到新连接之后,不再创建新线程,而是直接将新连接绑定到clientSelector上
clientSelector被一个while死循环包裹,如果在某一时刻有多个连接有数据可读
总的来说,netty是封装了jdk的nio,是异步事件驱动的网络应用框架
首先需要引入netty的maven依赖
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.6.Final</version>
</dependency>
6.netty服务端实现部分:
public class NettyServer {
public static void main(String[] args) {
ServerBootstrap serverBootstrap = new ServerBootstrap();
NioEventLoopGroup boss = new NioEventLoopGroup();
NioEventLoopGroup worker = new NioEventLoopGroup();
serverBootstrap
.group(boss, worker)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<NioSocketChannel>() {
protected void initChannel(NioSocketChannel ch) {
ch.pipeline().addLast(new StringDecoder());
ch.pipeline().addLast(new SimpleChannelInboundHandler<S
tring>() {
@Override
protected void channelRead0(ChannelHandlerContext ctx,
String msg) {
System.out.println(msg);
}
});
}
})
.bind(8000);
}
}
其中boss对应IO线程中负责接收线连接的线程,主要负责创建新连接
worker对应IO线程中负责读取数据的线程,主要用于读取数据和业务逻辑处理
7.netty客户端实现代码:
public class NettyClient {
public static void main(String[] args) throws InterruptedException {
Bootstrap bootstrap = new Bootstrap();
NioEventLoopGroup group = new NioEventLoopGroup();
bootstrap.group(group)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel ch) {
ch.pipeline().addLast(new StringEncoder());
}
});
Channel channel = bootstrap.connect("127.0.0.1", 8000).channel();
while (true) {
channel.writeAndFlush(new Date() + ": hello world!");
Thread.sleep(2000);
}
}
}