Netty的流程和范例(自主拼凑研发)

本文深入探讨了Netty的原理,包括NIO、AIO的基本概念和比较,详细介绍了Netty的工作机制,如bossGroup和workerGroup线程模型、Buffer、Channel和Pipeline。此外,还提供了Netty服务器和客户端的实例代码,展示了如何创建和配置ChannelInitializer、ChannelInboundHandler和ChannelOutboundHandler。最后,文章提到了Netty中的一些关键知识点,如ChannelOption、HTTP与Socket的区别,以及在实际应用中的注意事项。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Netty的流程和范例(自主拼凑研发)

 

一,netty的原理

1.要了解netty,就要了解一下三个概念:nio,bio,aio

Bio:同步并阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器就需要启动一个线程进行处理,如果这个连接不做任何事情就会造成不必要的线程开销,当然可以通过线程池机制改善。它适用于连接数目小的框架,Bio适合流量很高的应用,如文件传输

例:

InputStream is = new FileInputStream("input.bin");
int byte = is.read(); // 当前线程等待结果到达直至错误

再比如Tomcat是一个Web服务器,它是采取一个请求一个线程,当有1000客户端时,会耗费很多内存。通常一个线程将花费 256kb到1mb的stack空间。

 

Nio:同步并阻塞,NIO相当于是线程池方式的BIO服务器实现模式为一个请求一个线程,即客户端发送的的连接请求都会注册到多路复合器(轮询器)上,轮询到I/O连接就启动一个线程处理。

nio类库是jdk1.4中引入的。同步阻塞IO是以流的方式处理数据NIO是基于块(Block)的,它以块为基本单位处理数据 (硬盘上存储的单位也是按Block来存储,这样性能上比基于流的方式要好一些)

 

例:

while (true) {
selector.select(); // 从多个通道请求事件
Iterator it = selector.selectedKeys().iterator();
while (it.hasNext()) {
SelectorKey key = (SelectionKey) it.next();
handleKey(key);
it.remove();
}

所以NIO适用于连接多,且等待时间较短的架构,比如聊天

Aio(NIO.2):异步非阻塞,服务器实现模式为一个请求一个线程,客户端I/O请求都是OS(操作系统)先完成了再通知服务器应用启动线程进行处理,所以比较适合连接数目多且连接时间长(重复操作)的架构,比如相册服务器。

 

2.Bio和nio的比较图

 

 

 

.netty的工作机制

流程图:

 

1. bossGroup线程和workerGroup线程

bossGroup线程:由这个线程池提供的线程是boss种类的,用于创建、连接、绑定socket,然后把这些socket传 给worker线程池。在服务器端每个监听的socket都 有一个boss线 程来处理。在客户端,只有一个boss线程来处理所有的socket。

workerGroup线程:Worker线 程执行所有的异步I/O。 他们不是通用的线程,开发人员需要注意不要把与其不同的任务赋给线程,这可能导致线程被阻塞、无法处理他们真正关心的任务,反过来会导致死锁和一些莫名其妙的性能问题。

 

ServerBootstrap是一个对服务端做配置和启动的类

EventLoopGroup它是继承于ScheduledExecutorService线程池接口的接口,而NioEventLoopGroup就是它的一个实现类。在服务器启动时,创建两个线程池,xi惯上一个叫bossGroup,一个叫workGroup,如果在构造函数中不指定创建的线程数量,会默认创建当前cpu个数的2倍个。

ChannelInitializer当一个链接建立时,我们需要知道怎么来接收或者发送数据,当然,我们有各种各样的Handler实现来处理它,那么ChannelInitializer便是用来配置这些Handler,它会提供一个ChannelPipeline,并把Handler加入到ChannelPipeline。

关系:

可以这么说,ServerBootstrap监听的一个端口对应一个boss线程,它们一 一对应。比如你需要netty监听80和443端口,那么就会有两个boss线程分别负责处理来自两个端口的socket请求。在boss线程接收了socket连接请求后,会产生一个channel(一个打开的socket对应一个打开的channel),并把这个channel交给ServerBootstrap初始化时指定的ChannelInitializer来处理,boss线程则继续处理socket的请求。从worker线程池中找出一个worker线程来继续处理这个请求。

如果是Oio的话,那个这个channel上所有的socket消息,从开始到channel(socket)关闭,都只由这个特定的worker来处理,也就是说一个打开的socket对应一个指定的worker线程,这个worker线程在socket没有关闭的情况下,也只能为这个socket处理消息,无法服务其他socket。

 

当一个连接到达,Netty会注册一个channel,然后EventLoopGroup(worker)会分配一个EventLoop绑定到这个channel,在这个channel的整个生命周期过程中,都会由绑定的这个EventLoop来为它服务,而这个EventLoop就是一个线程。

 

2.Buffer(内存):

Netty的缓存分为堆内存(HeapByteBuf)和直接内存(DirectByteBuf)缓冲区。堆内存缓冲区的特点是分配和回收速度快,可以被JVM自动回收,缺点是进行Socket的IO读写,需要额外进行一次内存复制,将堆内存对应的缓冲区复制到内核中,性能会有一定程度的下降;直接内存缓冲区的特点是非堆内存,,它在堆外进行内存分配,相比于堆内存,它的分配和回收速度会慢一些,但是将它写入或者从Socket Channel中读取时,由于少了一次内存复制,速度比堆内存快。

 

3. Channel

Netty是一个线程服务于很多请求,当从Java NIO获得一个Selector事件,将激活通道Channel。

包括了网络的读,写,客户端发起连接,主动关闭连接,获取对方网络地址等,封装了Socket的操作,当有Socket操作发生时,会触发事件相应操作如:channelRead、channelReadComplete、exceptionCaught等方法

 

 

 

 

4.Pipeline:

可以看作是一条流水线,原始的原料(字节流)进来,经过加工,最后输出

 

channel中的核心组件包括:

 

pipeline中保存了channel的引用,创建完pipeline之后,整个pipeline是这个样子的

 

 

pipeline中的每个节点是一个ChannelHandlerContext对象,每个context节点保存了它包裹的执行器 ChannelHandler 执行操作所需要的上下文,其实就是pipeline,因为pipeline包含了channel的引用,可以拿到所有的context信息。

 

pipline里添加节点:

ChannelPipeline p = ch.pipeline();

p.addLast(new ChannelInboundHandler实现类());//进站

p.addLast(new ChannelOutboundHandler实现类());//出战

 

 

在添加handler的时候,如果理解顺序。可以把pipeline理解成一个队列,addLast是添加到队列的尾部,addFirst是添加到队列的头部。

5.ChannelInboundHandle和ChannelOutboundHandler

ChannelInboundHandler——处理输入数据和所有类型的状态变化按照注册的先后顺序执行

ChannelOutboundHandler——处理输出数据,可以拦截所有操作按照注册的先后顺序逆序执行

 

NiochannelRead()方法执行后,如果没有产生异常,worker线程就执行完毕了,它会被线程池回收

 

 

ChannelInboundHandler 接口:

 


Channel状态模型

 

 

 ChannelInboundHandler接口

 

 

6.总结:

Netty之所以能够提供高并发和高性能的通讯,主要是由以下原因形成的:

1. 零拷贝内存池;

2. 异步非阻塞通讯模型;

3. 高效的Reactor(反应堆)线程模型;

4. 无锁化的串行设计;

5. 高效的并发编程;

6. 高性能的序列化框架;

 

附零拷贝:

CPU只是发出写操作这样的指令,做一些初始化工作,DMA具体执行,从内存中读取数据,然后写到磁盘,当完成写后发出一个中断事件给CPU。这段时间CPU是空闲的,可以做别的事情。这个原理称为Zero.copy零拷贝。

 

三:实例

Server端一共有5个类:HelloServer InboundHandler1 InboundHandler2 OutboundHandler1 OutboundHandler2

1、HelloServer 代码如下

Server端一共有5个类:HelloServer InboundHandler1 InboundHandler2 OutboundHandler1 OutboundHandler2

1、HelloServer 代码如下

package com.guowl.testmultihandler;

 

import io.netty.bootstrap.ServerBootstrap;

import io.netty.channel.ChannelFuture;

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;

 

public class HelloServer {

public void start(int port) throws Exception {

EventLoopGroup bossGroup = new NioEventLoopGroup();

EventLoopGroup workerGroup = new NioEventLoopGroup();//  通过nio方式来接收连接和处理连接   try {

ServerBootstrap b = new ServerBootstrap(); // 引导辅助程序 

b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class) // 设置nio类型的channel 

.childHandler(new ChannelInitializer<SocketChannel>() { ////有连接到达时会创建一个channel 目的accept channel的pipeline预添加的inboundhandler  

@Override

public void initChannel(SocketChannel ch) throws Exception {

// channel队列中添加一个handler来处理业务注册两个OutboundHandler,执行顺序为注册顺序的逆序,所以应该是OutboundHandler2 OutboundHandler1

ch.pipeline().addLast(new OutboundHandler1());

ch.pipeline().addLast(new OutboundHandler2());

// 注册两个InboundHandler,执行顺序为注册顺序,所以应该是InboundHandler1 InboundHandler2

ch.pipeline().addLast(new InboundHandler1());

ch.pipeline().addLast(new InboundHandler2());

}

}).option(ChannelOption.SO_BACKLOG, 128)

.childOption(ChannelOption.SO_KEEPALIVE, true);

 

ChannelFuture f = b.bind(port).sync(); //配置完成,开始绑定server,通过调用sync同步方法阻塞直到绑定成功  

 

f.channel().closeFuture().sync();//应用程序会一直等待,直到channel关闭  

} finally {

workerGroup.shutdownGracefully();

bossGroup.shutdownGracefully();//

//这两个方法调用会等待线程池中的线程执行完毕后再退出

}

}

 

public static void main(String[] args) throws Exception {

HelloServer server = new HelloServer();

server.start(8000);

}

}

2. InboundHandler1

package com.guowl.testmultihandler;

 

import io.netty.channel.ChannelHandlerContext;

import io.netty.channel.ChannelInboundHandlerAdapter;

 

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

 

public class InboundHandler1 extends ChannelInboundHandlerAdapter {

private static Logger logger = LoggerFactory.getLogger(InboundHandler1.class);

 

@Override

public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {

logger.info("InboundHandler1.channelRead: ctx :" + ctx);

// 通知执行下一个InboundHandler

ctx.fireChannelRead(msg);

}

 

@Override

public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {

logger.info("InboundHandler1.channelReadComplete");

ctx.flush();

}

}

3.InboundHandler2

package com.guowl.testmultihandler;

 

import io.netty.buffer.ByteBuf;

import io.netty.channel.ChannelHandlerContext;

import io.netty.channel.ChannelInboundHandlerAdapter;

 

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

 

public class InboundHandler2 extends ChannelInboundHandlerAdapter {

private static Logger logger = LoggerFactory.getLogger(InboundHandler2.class);

 

@Override

// 读取Client发送的信息,并打印出来

public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {

logger.info("InboundHandler2.channelRead: ctx :" + ctx);

ByteBuf result = (ByteBuf) msg;

byte[] result1 = new byte[result.readableBytes()];

result.readBytes(result1);

String resultStr = new String(result1);

System.out.println("Client said:" + resultStr);

result.release();

 

ctx.write(msg);

}

 

@Override

public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {

logger.info("InboundHandler2.channelReadComplete");

ctx.flush();

}

    public void exceptionCaught(ChannelHandlerContext ctx,Throwable cause) {   

        cause.printStackTrace();//捕捉异常信息  

        ctx.close();//出现异常时关闭channel   

    }  

 

}

4. OutboundHandler1

package com.guowl.testmultihandler;

 

import io.netty.buffer.ByteBuf;

import io.netty.channel.ChannelHandlerContext;

import io.netty.channel.ChannelOutboundHandlerAdapter;

import io.netty.channel.ChannelPromise;

 

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

 

public class OutboundHandler1 extends ChannelOutboundHandlerAdapter {

private static Logger logger = LoggerFactory.getLogger(OutboundHandler1.class);

@Override

// 向client发送消息

public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {

logger.info("OutboundHandler1.write");

String response = "I am ok!";

ByteBuf encoded = ctx.alloc().buffer(4 * response.length());

encoded.writeBytes(response.getBytes());

ctx.write(encoded);

ctx.flush();

}

}

5. OutboundHandler2

package com.guowl.testmultihandler;

 

import io.netty.channel.ChannelHandlerContext;

import io.netty.channel.ChannelOutboundHandlerAdapter;

import io.netty.channel.ChannelPromise;

 

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

 

public class OutboundHandler2 extends ChannelOutboundHandlerAdapter {

private static Logger logger = LoggerFactory.getLogger(OutboundHandler2.class);

@Override

public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {

logger.info("OutboundHandler2.write");

// 执行下一个OutboundHandler

super.write(ctx, msg, promise);

}

}

 

Client端有两个类:HelloClient  HelloClientIntHandler

6. HelloClient  

 

package com.guowl.testmultihandler;

 

import io.netty.bootstrap.Bootstrap;

import io.netty.channel.ChannelFuture;

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;

 

public class HelloClient {

public void connect(String host, int port) throws Exception {

EventLoopGroup workerGroup = new NioEventLoopGroup();

 

try {

Bootstrap b = new Bootstrap();

b.group(workerGroup);

b.channel(NioSocketChannel.class);

b.option(ChannelOption.SO_KEEPALIVE, true);

b.handler(new ChannelInitializer<SocketChannel>() {

@Override

public void initChannel(SocketChannel ch) throws Exception {

ch.pipeline().addLast(new HelloClientIntHandler());

}

});

 

// Start the client.

ChannelFuture f = b.connect(host, port).sync();

f.channel().closeFuture().sync();

} finally {

workerGroup.shutdownGracefully();

}

}

 

public static void main(String[] args) throws Exception {

HelloClient client = new HelloClient();

client.connect("127.0.0.1", 8000);

}

}

7. HelloClientIntHandler

package com.guowl.testmultihandler;

 

import io.netty.buffer.ByteBuf;

import io.netty.channel.ChannelHandlerContext;

import io.netty.channel.ChannelInboundHandlerAdapter;

 

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

 

public class HelloClientIntHandler extends ChannelInboundHandlerAdapter {

private static Logger logger = LoggerFactory.getLogger(HelloClientIntHandler.class);

@Override

// 读取服务端的信息

public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {

logger.info("HelloClientIntHandler.channelRead");

ByteBuf result = (ByteBuf) msg;

byte[] result1 = new byte[result.readableBytes()];

result.readBytes(result1);

result.release();

ctx.close();

System.out.println("Server said:" + new String(result1));

}

@Override

// 当连接建立的时候向服务端发送消息 ,channelActive 事件当连接建立的时候会触发

public void channelActive(ChannelHandlerContext ctx) throws Exception {

logger.info("HelloClientIntHandler.channelActive");

String msg = "Are you ok?";

ByteBuf encoded = ctx.alloc().buffer(4 * msg.length());

encoded.writeBytes(msg.getBytes());

ctx.write(encoded);

ctx.flush();

}

}

8. 注意:

在使用Handler的过程中,需要注意:

1、ChannelInboundHandler之间的传递,通过调用 ctx.fireChannelRead(msg) 实现;调用ctx.write(msg) 将传递到ChannelOutboundHandler。

2、ctx.write()方法执行后,需要调用flush()方法才能令它立即执行。

3、ChannelOutboundHandler 在注册的时候需要放在最后一个ChannelInboundHandler之前,否则将无法传递到ChannelOutboundHandler。

4.还有一点,在Client端我们的业务Handler还可以继承的是SimpleChannelInboundHandler,而在服务器端继承的是ChannelInboundHandlerAdapter,那么这两个有什么区别呢?最主要的区别就是SimpleChannelInboundHandler在接收到数据后会自动release掉数据占用的Bytebuffer资源(自动调用Bytebuffer.release())。而为何服务器端不能用呢,因为我们想让服务器把客户端请求的数据发送回去,而服务器端有可能在channelRead方法返回前还没有写完数据,因此不能让它自动release,在channelRead0()方法,此方法接收到的可能是一些数据片段,比如服务器发送了5个字节数据,Client端不能保证一次全部收到,比如第一次收到3个字节,第二次收到2个字节。我们可能还会关心收到这些片段的顺序是否可发送顺序一致,这要看具体是什么协议,比如基于TCP协议的字节流是能保证顺序的。

.知识点备注:

 

1.Channel继承层次图,UML类图,如下所示:

 

2. LengthFieldBasedFrameDecoder的使用

 pipeline.addFirst("decoder", new LengthFieldBasedFrameDecoder(100000000,0,4,0,4));

使用LengthFieldBasedFrameDecoder作为decoder实现,LengthFieldBasedFrameDecoder构造函数,第一个参数为信息最大长度,超过这个长度回报异常,第二参数为长度属性的起始(偏移)位,我们的协议中长度是0到第3个字节,所以这里写0,第三个参数为长度属性的长度,我们是4个字节,所以写4,第四个参数为长度调节值,在总长被定义为包含包头长度时,修正信息长度,第五个参数为跳过的字节数,根据需要我们跳过前4个字节,以便接收端直接接受到不含长度属性的内容。

 

3. ChannelOption的含义已及使用的场景

1ChannelOption.SO_BACKLOG

ChannelOption.SO_BACKLOG对应的是tcp/ip协议listen函数中的backlog参数,函数listen(int socketfd,int backlog)用来初始化服务端可连接队列,

    服务端处理客户端连接请求是顺序处理的,所以同一时间只能处理一个客户端连接,多个客户端来的时候,服务端将不能处理的客户端连接请求放在队列中等待处理,backlog参数指定了队列的大小

2ChannelOption.SO_REUSEADDR

ChanneOption.SO_REUSEADDR对应于套接字选项中的SO_REUSEADDR,这个参数表示允许重复使用本地地址和端口,

    比如,某个服务器进程占用了TCP80端口进行监听,此时再次监听该端口就会返回错误,使用该参数就可以解决问题,该参数允许共用该端口,这个在服务器程序中比较常使用,

    比如某个进程非正常退出,该程序占用的端口可能要被占用一段时间才能允许其他进程使用,而且程序死掉以后,内核一需要一定的时间才能够释放此端口,不设置SO_REUSEADDR

    就无法正常使用该端口。

3ChannelOption.SO_KEEPALIVE

Channeloption.SO_KEEPALIVE参数对应于套接字选项中的SO_KEEPALIVE,该参数用于设置TCP连接,当设置该选项以后,连接会测试链接的状态,这个选项用于可能长时间没有数据交流的

    连接。当设置该选项以后,如果在两小时内没有数据的通信时,TCP会自动发送一个活动探测数据报文。

4ChannelOption.SO_SNDBUFChannelOption.SO_RCVBUF

ChannelOption.SO_SNDBUF参数对应于套接字选项中的SO_SNDBUFChannelOption.SO_RCVBUF参数对应于套接字选项中的SO_RCVBUF这两个参数用于操作接收缓冲区和发送缓冲区

    的大小,接收缓冲区用于保存网络协议站内收到的数据,直到应用程序读取成功,发送缓冲区用于保存发送数据,直到发送成功。

5ChannelOption.SO_LINGER

ChannelOption.SO_LINGER参数对应于套接字选项中的SO_LINGER,Linux内核默认的处理方式是当用户调用close()方法的时候,函数返回,在可能的情况下,尽量发送数据,不一定保证

    会发生剩余的数据,造成了数据的不确定性,使用SO_LINGER可以阻塞close()的调用时间,直到数据完全发送

6ChannelOption.TCP_NODELAY

ChannelOption.TCP_NODELAY参数对应于套接字选项中的TCP_NODELAY,该参数的使用与Nagle算法有关

Nagle算法是将小的数据包组装为更大的帧然后进行发送,而不是输入一次发送一次,因此在数据包不足的时候会等待其他数据的到了,组装成大的数据包进行发送,虽然该方式有效提高网络的有效

    负载,但是却造成了延时,而该参数的作用就是禁止使用Nagle算法,使用于小数据即时传输,于TCP_NODELAY相对应的是TCP_CORK,该选项是需要等到发送的数据量最大的时候,一次性发送

    数据,适用于文件传输。

4. netty版本

ChannelHandler的变化 

 

 

 

 

Netty4使用了入站出站的概念 

 

 


Netty5则干脆取消了两者的划分,统一为一个概念 

 

 

Netty3: ChannelHandler有两个子接口ChannelUpstreamHandler,ChannelDownstreamHandler, 
上行和下行,这里的上行和下行和我们一般理解上的上下行不太一样,为何如此可以参考上面的3张图。 
相应的类有SimpleChannelUpstreamHandler,SimpleChannelDownstreamHandler,以及一个同时实现两个接口的SimpleChannelHandler 

 

 

 

Netty4: 接口变成了ChannelInboundHandler ChannelOutboundHandler,可能是为了避免原来的上下行造成误解,所以改成入站和出站了。 
相应的类有ChannelInboundHandlerAdapter,ChannelOutboundHandlerAdapter,我们只要选择一个继承就可以了。 

 

 

 

Netty5: 取消了进站、出站的划分,统一为继承ChannelHandlerAdapter,原来的ChannelInboundHandlerAdapter,ChannelOutboundHandlerAdapter被废弃。 

 

 

 

 

5. HTTPSocket的区别

要弄明白 http 和 socket 首先要熟悉网络七层:物 数 网 传 会 表 应,如图1

 

HTTP 协议:超文本传输协议,对应于应用层,用于如何封装数据.

TCP/UDP 协议:传输控制协议,对应于传输层,主要解决数据在网络中的传输。

IP 协议:对应于网络层,同样解决数据在网络中的传输。

传输数据的时候只使用 TCP/IP 协议(传输层),如果没有应用层来识别数据内容,传输后的协议都是无用的。

应用层协议很多 FTP,HTTP,TELNET等,可以自己定义应用层协议。

web 使用 HTTP 作传输层协议,以封装 HTTP 文本信息,然后使用 TCP/IP 做传输层协议,将数据发送到网络上。

 

一、HTTP 协议

http 为短连接:客户端发送请求都需要服务器端回送响应.请求结束后,主动释放链接,因此为短连接。通常的做法是,不需要任何数据,也要保持每隔一段时间向服务器发送"保持连接"的请求。这样可以保证客户端在服务器端是"上线"状态。

HTTP连接使用的是"请求-响应"方式,不仅在请求时建立连接,而且客户端向服务器端请求后,服务器才返回数据。

 

二、Socket 连接

要想明白 Socket,必须要理解 TCP 连接。

TCP 三次握手:握手过程中并不传输数据,在握手后服务器与客户端才开始传输数据,理想状态下,TCP 连接一旦建立,在通讯双方中的任何一方主动断开连接之前 TCP 连接会一直保持下去。

Socket 是对 TCP/IP 协议的封装,Socket 只是个接口不是协议,通过 Socket 我们才能使用 TCP/IP 协议,除了 TCP,也可以使用 UDP 协议来传递数据。

创建 Socket 连接的时候,可以指定传输层协议,可以是 TCP 或者 UDP,当用 TCP 连接,该Socket就是个TCP连接,反之。

Socket 原理

Socket 连接,至少需要一对套接字,分为 clientSocket,serverSocket 连接分为3个步骤:

(1) 服务器监听:服务器并不定位具体客户端的套接字,而是时刻处于监听状态;

(2) 客户端请求:客户端的套接字要描述它要连接的服务器的套接字,提供地址和端口号,然后向服务器套接字提出连接请求;

(3) 连接确认:当服务器套接字收到客户端套接字发来的请求后,就响应客户端套接字的请求,并建立一个新的线程,把服务器端的套接字的描述发给客户端。一旦客户端确认了此描述,就正式建立连接。而服务器套接字继续处于监听状态,继续接收其他客户端套接字的连接请求.

Socket为长连接:通常情况下Socket 连接就是 TCP 连接,因此 Socket 连接一旦建立,通讯双方开始互发数据内容,直到双方断开连接。在实际应用中,由于网络节点过多,在传输过程中,会被节点断开连接,因此要通过轮询高速网络,该节点处于活跃状态。

 

很多情况下,都是需要服务器端向客户端主动推送数据,保持客户端与服务端的实时同步。

若双方是 Socket 连接,可以由服务器直接向客户端发送数据。

若双方是 HTTP 连接,则服务器需要等客户端发送请求后,才能将数据回传给客户端。

因此,客户端定时向服务器端发送请求,不仅可以保持在线,同时也询问服务器是否有新数据,如果有就将数据传给客户端。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值