开源一个自用的Android IM库,基于Netty+TCP(2),c毕业设计参考题目是什么

我们根据自定义的消息类型来编写proto文件。 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 然后执行命令(我用的mac,windows命令应该也差不多): 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 然后就会看到,在和proto文件同级目录下,会生成一个java类,这个就是我们需要用到的东东: 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 我们打开瞄一眼: 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 东西比较多,不用去管,这是google为我们生成的protobuf类,直接用就行,怎么用呢?直接用这个类文件,拷到我们开始指定的项目包路径下就可以啦: 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 添加依赖后,可以看到,MessageProtobuf类文件已经没有报错了,顺便把netty的jar包也导进来一下,还有fastjson的: 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 建议用netty-all-x.x.xx.Final的jar包,后续熟悉了,可以用精简的jar包。

至此,准备工作已结束,下面,我们来编写java代码,实现即时通讯的功能。


封装

为什么需要封装呢?说白了,就是为了解耦,为了方便日后切换到不同框架实现,而无需到处修改调用的地方。举个栗子,比如Android早期比较流行的图片加载框架是Universal ImageLoader,后期因为某些原因,原作者停止了维护该项目,目前比较流行的图片加载框架是Picasso或Glide,因为图片加载功能可能调用的地方非常多,如果不作一些封装,早期使用了Universal ImageLoader的话,现在需要切换到Glide,那改动量将非常非常大,而且还很有可能会有遗漏,风险度非常高。

那么,有什么解决方案呢?

很简单,我们可以用工厂设计模式进行一些封装,工厂模式有三种:简单工厂模式、抽象工厂模式、工厂方法模式。在这里,我采用工厂方法模式进行封装,具体区别,可以参见:通俗讲讲我对简单工厂、工厂方法、抽象工厂三种设计模式的理解

我们分析一下,ims(IM Service,下文简称ims)应该是有初始化建立连接重连关闭连接释放资源判断长连接是否关闭发送消息等功能,基于上述分析,我们可以进行一个接口抽象: 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 OnEventListener是与应用层交互的listener: 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 IMConnectStatusCallback是ims连接状态回调监听器: 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

然后写一个Netty tcp实现类: 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

接下来,写一个工厂方法: 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

封装部分到此结束,接下来,就是实现了。


初始化

我们先实现init(Vector serverUrlList, OnEventListener listener, IMSConnectStatusCallback callback)方法,初始化一些参数,以及进行第一次连接等: 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

其中,MsgDispatcher是消息转发器,负责将接收到的消息转发到应用层: 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

ExecutorServiceFactory是线程池工厂,负责调度重连及心跳线程: 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传


连接及重连

resetConnect()方法作为连接的起点,首次连接以及重连逻辑,都是在resetConnect()方法进行逻辑处理,我们来瞄一眼: 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 可以看到,非首次进行连接,也就是连接一个周期失败后,进行重连时,会先让线程休眠一段时间,因为这个时候也许网络状况不太好,接着,判断ims是否已关闭或者是否正在进行重连操作,由于重连操作是在子线程执行,为了避免重复重连,需要进行一些并发处理。开始重连任务后,分四个步骤执行:

  • 改变重连状态标识
  • 回调连接状态到应用层
  • 关闭之前打开的连接channel
  • 利用线程池执行一个新的重连任务

ResetConnectRunnable是重连任务,核心的重连逻辑都放到这里执行: 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

toServer()是真正连接服务器的地方: 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

initBootstrap()是初始化Netty Bootstrap: 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 注:NioEventLoopGroup线程数设置为4,可以满足QPS是一百多万的情况了,至于应用如果需要承受上千万上亿流量的,需要另外调整线程数。参考自:netty实战之百万级流量NioEventLoopGroup线程数配置

接着,我们来看看TCPChannelInitializerHanlder外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 其中,ProtobufEncoderProtobufDecoder是添加对protobuf的支持,LoginAuthRespHandler是接收到服务端握手认证消息响应的处理handler,HeartbeatRespHandler是接收到服务端心跳消息响应的处理handler,TCPReadHandler是接收到服务端其它消息后的处理handler,先不去管,我们重点来分析下LengthFieldPrependerLengthFieldBasedFrameDecoder,这就需要引申到TCP的拆包与粘包啦。


TCP的拆包与粘包

  • 什么是TCP拆包?为什么会出现TCP拆包?

简单地说,我们都知道TCP是以“流”的形式进行数据传输的,而且TCP为提高性能,发送端会将需要发送的数据刷入缓冲区,等待缓冲区满了之后,再将缓冲区中的数据发送给接收方,同理,接收方也会有缓冲区这样的机制,来接收数据。
拆包就是在socket读取时,没有完整地读取一个数据包,只读取一部分。

  • 什么是TCP粘包?为什么会出现TCP粘包?

同上。
粘包就是在socket读取时,读到了实际意义上的两个或多个数据包的内容,同时将其作为一个数据包进行处理。

引用网上一张图片来解释一下在TCP出现拆包、粘包以及正常状态下的三种情况,如侵请联系我删除: 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 了解了TCP出现拆包/粘包的原因,那么,如何解决呢?通常来说,有以下四种解决方式:

  • 消息定长
  • 用回车换行符作为消息结束标志
  • 用特殊分隔符作为消息结束标志,如\t、\n等,回车换行符其实就是特殊分隔符的一种。
  • 将消息分为消息头和消息体,在消息头中用字段标识消息总长度。

netty针对以上四种场景,给我们封装了以下四种对应的解码器:

  • FixedLengthFrameDecoder,定长消息解码器
  • LineBasedFrameDecoder,回车换行符消息解码器
  • DelimiterBasedFrameDecoder,特殊分隔符消息解码器
  • LengthFieldBasedFrameDecoder,自定义长度消息解码器。

我们用到的就是LengthFieldBasedFrameDecoder自定义长度消息解码器,同时配合LengthFieldPrepender编码器使用,关于参数配置,建议参考netty–最通用TCP黏包解决方案:LengthFieldBasedFrameDecoder和LengthFieldPrepender这篇文章,讲解得比较细致。我们配置的是消息头长度为2个字节,所以消息包的最大长度需要小于65536个字节,netty会把消息内容长度存放消息头的字段里,接收方可以根据消息头的字段拿到此条消息总长度,当然,netty提供的LengthFieldBasedFrameDecoder已经封装好了处理逻辑,我们只需要配置lengthFieldOffset、lengthFieldLength、lengthAdjustment、initialBytesToStrip即可,这样就可以解决TCP的拆包与粘包,这也就是netty相较于原生nio的便捷性,原生nio需要自己处理拆包/粘包等问题。


长连接握手认证

接着,我们来看看LoginAuthHandlerHeartbeatRespHandler

  • LoginAuthRespHandler是当客户端与服务端长连接建立成功后,客户端主动向服务端发送一条登录认证消息,带入与当前用户相关的参数,比如token,服务端收到此消息后,到数据库查询该用户信息,如果是合法有效的用户,则返回一条登录成功消息给该客户端,反之,返回一条登录失败消息给该客户端,这里,就是在接收到服务端返回的登录状态后的处理handler,比如:外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

可以看到,当接收到服务端握手消息响应后,会从扩展字段取出status,如果status=1,则代表握手成功,这个时候就先主动向服务端发送一条心跳消息,然后利用Netty的IdleStateHandler读写超时机制,定期向服务端发送心跳消息,维持长连接,以及检测长连接是否还存在等。

  • HeartbeatRespHandler是当客户端接收到服务端登录成功的消息后,主动向服务端发送一条心跳消息,心跳消息可以是一个空包,消息包体越小越好,服务端收到客户端的心跳包后,原样返回给客户端,这里,就是收到服务端返回的心跳消息响应的处理handler,比如:外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这个就比较简单,收到心跳消息响应,无需任务处理,直接打印一下方便我们分析即可。


心跳机制及读写超时机制

心跳包是定期发送,也可以自己定义一个周期,比如Android微信智能心跳方案,为了简单,此处规定应用在前台时,8秒发送一个心跳包,切换到后台时,30秒发送一次,根据自己的实际情况修改一下即可。心跳包用于维持长连接以及检测长连接是否断开等。

接着,我们利用Netty的读写超时机制,来实现一个心跳消息管理handler: 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 可以看到,利用userEventTriggered()方法回调,通过IdleState类型,可以判断读超时/写超时/读写超时,这个在添加IdleStateHandler时可以配置,下面会贴上代码。首先我们可以在READER_IDLE事件里,检测是否在规定时间内没有收到服务端心跳包响应,如果是,那就触发重连操作。在WRITER_IDEL事件可以检测客户端是否在规定时间内没有向服务端发送心跳包,如果是,那就主动发送一个心跳包。发送心跳包是在子线程中执行,我们可以利用之前写的work线程池进行线程管理。
addHeartbeatHandler()代码如下: 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 从图上可看到,在IdleStateHandler里,配置的读超时为心跳间隔时长的3倍,也就是3次心跳没有响应时,则认为长连接已断开,触发重连操作。写超时则为心跳间隔时长,意味着每隔heartbeatInterval会发送一个心跳包。读写超时没用到,所以配置为0。

onConnectStatusCallback(int connectStatus)为连接状态回调,以及一些公共逻辑处理: 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 连接成功后,立即发送一条握手消息,再次梳理一下整体流程:

  • 客户端根据服务端返回的host及port,进行第一次连接。
  • 连接成功后,客户端向服务端发送一条握手认证消息(1001)
  • 服务端在收到客户端的握手认证消息后,从扩展字段里取出用户token,到本地数据库校验合法性。
  • 校验完成后,服务端把校验结果通过1001消息返回给客户端,也就是握手消息响应。
  • 客户端收到服务端的握手消息响应后,从扩展字段取出校验结果。若校验成功,客户端向服务端发送一条心跳消息(1002),然后进入心跳发送周期,定期间隔向服务端发送心跳消息,维持长连接以及实时检测链路可用性,若发现链路不可用,等待一段时间触发重连操作,重连成功后,重新开始握手/心跳的逻辑。

看看TCPReadHandler收到消息是怎么处理的: 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 可以看到,在channelInactive()及exceptionCaught()方法都触发了重连,channelInactive()方法在当链路断开时会调用,exceptionCaught()方法在当出现异常时会触发,另外,还有诸如channelUnregistered()、channelReadComplete()等方法可以重写,在这里就不贴了,相信聪明的你一眼就能看出方法的作用。
我们仔细看一下channelRead()方法的逻辑,在if判断里,先判断消息类型,如果是服务端返回的消息发送状态报告类型,则判断消息是否发送成功,如果发送成功,从超时管理器中移除,这个超时管理器是干嘛的呢?下面讲到消息重发机制的时候会详细地讲。在else里,收到其他消息后,会立马给服务端返回一个消息接收状态报告,告诉服务端,这条消息我已经收到了,这个动作,对于后续需要做的离线消息会有作用。如果不需要支持离线消息功能,这一步可以省略。最后,调用消息转发器,把接收到的消息转发到应用层即可。

代码写了这么多,我们先来看看运行后的效果,先贴上缺失的消息发送代码及ims关闭代码以及一些默认配置项的代码。
发送消息:外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 关闭ims:外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 ims默认配置:外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 还有,应用层实现的ims client启动器: 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 由于代码有点多,不太方便全部贴上,如果有兴趣可以下载demo体验。 额,对了,还有一个简易的服务端代码,如下: 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传


调试

我们先来看看连接及重连部分(由于录制gif比较麻烦,体积较大,所以我先把重连间隔调小成3秒,方便看效果)。

  • 启动服务端:外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
  • 启动客户端:外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

可以看到,正常的情况下已经连接成功了,接下来,我们来试一下异常情况,比如服务端没启动,看看客户端的重连情况: 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 这次我们先启动的是客户端,可以看到连接失败后一直在进行重连,由于录制gif比较麻烦,在第三次连接失败后,我启动了服务端,这个时候客户端就会重连成功。

然后,我们再来调试一下握手认证消息即心跳消息: 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 可以看到,长连接建立成功后,客户端会给服务端发送一条握手认证消息(1001),服务端收到握手认证消息会,给客户端返回了一条握手认证状态消息,客户端收到握手认证状态消息后,即启动心跳机制。gif不太好演示,下载demo就可以直观地看到。

接下来,在讲完消息重发机制及离线消息后,我会在应用层做一些简单的封装,以及在模拟器上运行,这样就可以很直观地看到运行效果。


消息重发机制

消息重发,顾名思义,即使对发送失败的消息进行重发。考虑到网络环境的不稳定性、多变性(比如从进入电梯、进入地铁、移动网络切换到wifi等),在消息发送的时候,发送失败的概率其实不小,这时消息重发机制就很有必要了。
我们先来看看实现的代码逻辑。 MsgTimeoutTimer: 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 MsgTimeoutTimerManager: 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
然后,我们看看收消息的TCPReadHandler的改造: 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 最后,看看发送消息的改造: 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

说一下逻辑吧:发送消息时,除了心跳消息、握手消息、状态报告消息外,消息都加入消息发送超时管理器,立马开启一个定时器,比如每隔5秒执行一次,共执行3次,在这个周期内,如果消息没有发送成功,会进行3次重发,达到3次重发后如果还是没有发送成功,那就放弃重发,移除该消息,同时通过消息转发器通知应用层,由应用层决定是否再次重发。如果消息发送成功,服务端会返回一个消息发送状态报告,客户端收到该状态报告后,从消息发送超时管理器移除该消息,同时停止该消息对应的定时器即可。
另外,在用户握手认证成功时,应该检查消息发送超时管理器里是否有发送超时的消息,如果有,则全部重发: 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传


离线消息

由于离线消息机制,需要服务端数据库及缓存上的配合,代码就不贴了,太多太多,我简单说一下实现思路吧: 客户端A发送消息到客户端B,消息会先到服务端,由服务端进行中转。这个时候,客户端B存在两种情况:

  • 1.长连接正常,就是客户端网络环境良好,手机有电,应用处在打开的情况。
  • 2.废话,那肯定就是长连接不正常咯。这种情况有很多种原因,比如wifi不可用、用户进入了地铁或电梯等网络不好的场所、应用没打开或已退出登录等,总的来说,就是没有办法正常接收消息。

如果是长连接正常,那没什么可说的,服务端直接转发即可。
如果长连接不正常,需要这样处理:服务端接收到客户端A发送给客户端B的消息后,先给客户端A回复一条状态报告,告诉客户端A,我已经收到消息,这个时候,客户端A就不用管了,消息只要到达服务端即可。然后,服务端先尝试把消息转发到客户端B,如果这个时候客户端B收到服务端转发过来的消息,需要立马给服务端回一条状态报告,告诉服务端,我已经收到消息,服务端在收到客户端B返回的消息接收状态报告后,即认为此消息已经正常发送,不需要再存库。如果客户端B不在线,服务端在做转发的时候,并没有收到客户端B返回的消息接收状态报告,那么,这条消息就应该存到数据库,直到客户端B上线后,也就是长连接建立成功后,客户端B主动向服务端发送一条离线消息询问,服务端在收到离线消息询问后,到数据库或缓存去查客户端B的所有离线消息,并分批次返回,客户端B在收到服务端的离线消息返回后,取出消息id(若有多条就取id集合),通过离线消息应答把消息id返回到服务端,服务端收到后,根据消息id从数据库把对应的消息删除即可。
以上是单聊离线消息处理的情况,群聊有点不同,群聊的话,是需要服务端确认群组内所有用户都收到此消息后,才能从数据库删除消息,就说这么多,如果需要细节的话,可以私信我。


不知不觉,NettyTcpClient中定义了很多变量,为了防止大家不明白变量的定义,还是贴上代码吧: 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

应用层封装

这个就见仁见智啦,每个人代码风格不同,我把自己简单封装的代码贴上来吧:
MessageProcessor消息处理器: 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 IMSEventListener与ims交互的listener: 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 MessageBuilder消息转换器: 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 AbstractMessageHandler抽象的消息处理handler,每个消息类型对应不同的messageHandler: 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 SingleChatMessageHandler单聊消息处理handler: 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 GroupChatMessageHandler群聊消息处理handler: 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 MessageHandlerFactory消息handler工厂: 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 MessageType消息类型枚举: 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 IMSConnectStatusListenerIMS连接状态监听器: 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 由于每个人代码风格不同,封装代码都有自己的思路,所以,在此就不过多讲解,只是把自己简单封装的代码全部贴上来,作一个参考即可。只需要知道,接收到消息时,会回调OnEventListener的dispatchMsg(MessageProtobuf.Msg msg)方法: 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 发送消息需要调用imsClient的sendMsg(MessageProtobuf.Msg msg)方法: 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 即可,至于怎样去封装得更好,大家自由发挥吧。


最后,为了测试消息收发是否正常,我们需要改动一下服务端: 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 可以看到,当有用户握手成功后,会保存该用户对应的channel到容器里,给用户发送消息时,根据用户id从容器里取出对应的channel,利用该channel发送消息。当用户断开连接后,会把该用户对应的channel从容器里移除掉。

运行一下,看看效果吧: 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • 首先,启动服务端。
  • 然后,修改客户端连接的ip地址为192.168.0.105(这是我本机的ip地址),端口号为8855,fromId,也就是userId,定义成100001,toId为100002,启动客户端A。
  • 再然后,fromId,也就是userId,定义成100002,toId为100001,启动客户端B。
  • 客户端A给客户端B发送消息,可以看到在客户端B的下面,已经接收到了消息。
  • 用客户端B给客户端A发送消息,也可以看到在客户端A的下面,也已经接收到了消息。

至于,消息收发测试成功。至于群聊或重连等功能,就不一一演示了,还是那句话,下载demo体验一下吧。。。

由于gif录制体积较大,所以只能简单演示一下消息收发,具体下载demo体验吧。。。

如果有需要应用层UI实现(就是聊天页及会话页的封装)的话,我再分享出来吧。

github地址




发现的bug

  1. MsgTimeoutTimer

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 这个bug是自己在检查代码时发现的,可能是连续熬几天夜写文章魔怔了。。。 修改如下: 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

一个人精力有限,大家在使用过程中,如果发现其它bug,烦请告诉我,反正我是会虚心接受,坚决不改,呸,一定改,一定改。另外,欢迎fork,期待大家与我一起完善。。。




写在最后

终于写完了,这篇文章大概写了10天左右,有很大部分的原因是自己有拖延症,每次写完一小段,总静不下心来写下去,导致一直拖到现在,以后得改改。第一次写技术分享文章,有很多地方也许逻辑不太清晰,由于篇幅有限,也只是贴了部分代码,建议大家把源码下载下来看看。一直想写这篇文章,以前在网上也尝试过找过很多im方面的文章,都找不到一篇比较完善的,本文谈不上完善,但包含的模块很多,希望起到一个抛砖引玉的作用,也期待着大家跟我一起发现更多的问题并完善,最后,如果这篇文章对你有用,希望在github上给我一个star哈。。。

应大家要求,精简了netty-all-4.1.33.Final.jar包。原netty-all-4.1.33.Final.jar包大小为3.9M,经测试发现目前im_lib库只需要用到以下jar包:

  • netty-buffer-4.1.33.Final.jar

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数同学面临毕业设计项目选题时,很多人都会感到无从下手,尤其是对于计算机专业的学生来说,选择一个合适的题目尤为重要。因为毕业设计不仅是我们在大学四年学习的一个总结,更是展示自己能力的重要机会。

因此收集整理了一份《2024年计算机毕业设计项目大全》,初衷也很简单,就是希望能够帮助提高效率,同时减轻大家的负担。
img
img
img

既有Java、Web、PHP、也有C、小程序、Python等项目供你选择,真正体系化!

由于项目比较多,这里只是将部分目录截图出来,每个节点里面都包含素材文档、项目源码、讲解视频

如果你觉得这些内容对你有帮助,可以添加VX:vip1024c (备注项目大全获取)
img

81)]
[外链图片转存中…(img-AAnOJlLc-1712578360582)]

既有Java、Web、PHP、也有C、小程序、Python等项目供你选择,真正体系化!

由于项目比较多,这里只是将部分目录截图出来,每个节点里面都包含素材文档、项目源码、讲解视频

如果你觉得这些内容对你有帮助,可以添加VX:vip1024c (备注项目大全获取)
[外链图片转存中…(img-K6UGrrgX-1712578360582)]

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值