热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

springboot集成swing_SpringBoot(22)集成MobileIMSDK实现即时通讯

一、前言MobileIMSDK是什么?一个专为移动端开发的开源原创即时通讯框架,超轻量级、高度提炼,完全基于UDP协议,支持
4e66a9b7cc0d83f834d6d91dac7ab080.png

一、前言

MobileIMSDK是什么?

一个专为移动端开发的开源原创即时通讯框架,超轻量级、高度提炼,完全基于UDP协议,支持iOSAndroid、标准Java平台,服务端基于MinaNetty编写。MobileIMSDK还可与姊妹工程 MobileIMSDK-Web无缝互通,从而实现Web网页端聊天推送等。

本文将实现

  1. 基于springboot2.1.8.RELEASE 集成 MobileIMSDK
  2. 开发IM服务端
  3. 开发客户端
  4. 实现Java客户端与客户端之间的通信

二、SpringBoot 集成 MobileIMSDK 准备

1、MobileIMSDK下载:https://gitee.com/jackjiang/MobileIMSDK

  1. 服务端所需jar包: dist/server-xxx
  2. 客服端所需jar包: dist/client/java
17375d4ce65298aba35b416ddb6bbf2e.png

2、pom.xml中引入相关依赖

由于这里是maven项目,其中一部分jar包可通过maven仓库直接引入,而其余的则通过外部jar包引入方式使用即可~

如下4个需作为外部jar包在pom.xml中引入

f84dd5ebd79377cb3f0470a154ee445c.png


com.google.code.gsongson2.8.5

com.zhengqingMobileIMSDK4jsystem${project.basedir}/src/main/resources/lib/MobileIMSDK4j.jar

com.zhengqingMobileIMSDKServerX_metasystem${project.basedir}/src/main/resources/lib/MobileIMSDKServerX_meta.jar

com.zhengqingswing-worker-1.2(1.6-)system${project.basedir}/src/main/resources/lib/swing-worker-1.2(1.6-).jar

com.zhengqingMobileIMSDKServerX_nettysystem${project.basedir}/src/main/resources/lib/MobileIMSDKServerX_netty.jar

org.springframework.bootspring-boot-maven-plugintrue

三、开发服务端

836fd5717414afdc1c48f9333796d5c9.png

1、与客服端的所有数据交互事件(实现ServerEventListener类)

public class ServerEventListenerImpl implements ServerEventListener {private static Logger logger = LoggerFactory.getLogger(ServerEventListenerImpl.class);/*** 用户身份验证回调方法定义.*

* 服务端的应用层可在本方法中实现用户登陆验证。*
* 注意:本回调在一种特殊情况下——即用户实际未退出登陆但再次发起来登陆包时,本回调是不会被调用的!*

* 根据MobileIMSDK的算法实现,本方法中用户验证通过(即方法返回值=0时)后* ,将立即调用回调方法 {@link #onUserLoginAction_CallBack(int, String, IoSession)}。* 否则会将验证结果(本方法返回值错误码通过客户端的 ChatBaseEvent.onLoginMessage(int dwUserId, int dwErrorCode)* 方法进行回调)通知客户端)。** @param userId 传递过来的准一id,保证唯一就可以通信,可能是登陆用户名、也可能是任意不重复的id等,具体意义由业务层决定* @param token 用于身份鉴别和合法性检查的token,它可能是登陆密码,也可能是通过前置单点登陆接口拿到的token等,具体意义由业务层决定* @param extra 额外信息字符串。本字段目前为保留字段,供上层应用自行放置需要的内容* @param session 此客户端连接对应的 netty “会话”* @return 0 表示登陆验证通过,否则可以返回用户自已定义的错误码,错误码值应为:>=1025的整数*/@Overridepublic int onVerifyUserCallBack(String userId, String token, String extra, Channel session) {logger.debug("【DEBUG_回调通知】正在调用回调方法:OnVerifyUserCallBack...(extra=" + extra + ")");return 0;}/*** 用户登录验证成功后的回调方法定义(可理解为上线通知回调).*

* 服务端的应用层通常可在本方法中实现用户上线通知等。*
* 注意:本回调在一种特殊情况下——即用户实际未退出登陆但再次发起来登陆包时,回调也是一定会被调用。** @param userId 传递过来的准一id,保证唯一就可以通信,可能是登陆用户名、也可能是任意不重复的id等,具体意义由业务层决定* @param extra 额外信息字符串。本字段目前为保留字段,供上层应用自行放置需要的内容。为了丰富应用层处理的手段,在本回调中也把此字段传进来了* @param session 此客户端连接对应的 netty “会话”*/@Overridepublic void onUserLoginAction_CallBack(String userId, String extra, Channel session) {logger.debug("【IM_回调通知OnUserLoginAction_CallBack】用户:" + userId + " 上线了!");}/*** 用户退出登录回调方法定义(可理解为下线通知回调)。*

* 服务端的应用层通常可在本方法中实现用户下线通知等。** @param userId 下线的用户user_id* @param obj* @param session 此客户端连接对应的 netty “会话”*/@Overridepublic void onUserLogoutAction_CallBack(String userId, Object obj, Channel session) {logger.debug("【DEBUG_回调通知OnUserLogoutAction_CallBack】用户:" + userId + " 离线了!");}/*** 通用数据回调方法定义(客户端发给服务端的(即接收user_id="0")).*

* MobileIMSDK在收到客户端向user_id=0(即接收目标是服务器)的情况下通过* 本方法的回调通知上层。上层通常可在本方法中实现如:添加好友请求等业务实现。**

* 【版本兼容性说明】本方法用于替代v3.x中的以下方法:
* public boolean onTransBuffer_CallBack(String userId, String from_user_id* , String dataContent, String fingerPrint, int typeu, Channel session);* ** @param userId 接收方的user_id(本方法接收的是发给服务端的消息,所以此参数的值肯定==0)* @param from_user_id 发送方的user_id* @param dataContent 数据内容(文本形式)* @param session 此客户端连接对应的 netty “会话”* @return true表示本方法已成功处理完成,否则表示未处理成功。此返回值目前框架中并没有特殊意义,仅作保留吧* @since 4.0*/@Overridepublic boolean onTransBuffer_C2S_CallBack(Protocal p, Channel session) {// 接收者uidString userId = p.getTo();// 发送者uidString from_user_id = p.getFrom();// 消息或指令内容String dataContent = p.getDataContent();// 消息或指令指纹码(即唯一ID)String fingerPrint = p.getFp();// 【重要】用户定义的消息或指令协议类型(开发者可据此类型来区分具体的消息或指令)int typeu = p.getTypeu();logger.debug("【DEBUG_回调通知】[typeu=" + typeu + "]收到了客户端" + from_user_id + "发给服务端的消息:str=" + dataContent);return true;}/*** 通道数据回调函数定义(客户端发给客户端的(即接收方user_id不为“0”的情况)).*

* 注意:本方法当且仅当在数据被服务端成功在线发送出去后被回调调用.*

* 上层通常可在本方法中实现用户聊天信息的收集,以便后期监控分析用户的行为等^_^。*

* 提示:如果开启消息QoS保证,因重传机制,本回调中的消息理论上有重复的可能,请以参数 #fingerPrint* 作为消息的唯一标识ID进行去重处理。**

* 【版本兼容性说明】本方法用于替代v3.x中的以下方法:
* public void onTransBuffer_C2C_CallBack(String userId, String from_user_id* , String dataContent, String fingerPrint, int typeu);** @param userId 接收方的user_id(本方法接收的是客户端发给客户端的,所以此参数的值肯定>0)* @param from_user_id 发送方的user_id* @param dataContent* @since 4.0*/@Overridepublic void onTransBuffer_C2C_CallBack(Protocal p) {// 接收者uidString userId = p.getTo();// 发送者uidString from_user_id = p.getFrom();// 消息或指令内容String dataContent = p.getDataContent();// 消息或指令指纹码(即唯一ID)String fingerPrint = p.getFp();// 【重要】用户定义的消息或指令协议类型(开发者可据此类型来区分具体的消息或指令)int typeu = p.getTypeu();logger.debug("【DEBUG_回调通知】[typeu=" + typeu + "]收到了客户端" + from_user_id + "发给客户端" + userId + "的消息:str=" + dataContent);}/*** 通用数据实时发送失败后的回调函数定义(客户端发给客户端的(即接收方user_id不为“0”的情况)).*

* 注意:本方法当且仅当在数据被服务端在线发送失败后被回调调用.*

* 此方法存的意义何在?
* 发生此种情况的场景可能是:对方确实不在线(那么此方法里就可以作为离线消息处理了)、* 或者在发送时判断对方是在线的但服务端在发送时却没有成功(这种情况就可能是通信错误* 或对方非正常通出但尚未到达会话超时时限)。
应用层在此方法里实现离线消息的处理即可!**

* 【版本兼容性说明】本方法用于替代v3.x中的以下方法:
* public boolean onTransBuffer_C2C_RealTimeSendFaild_CallBack(String userId* , String from_user_id, String dataContent, String fingerPrint, int typeu);* ** @param userId 接收方的user_id(本方法接收的是客户端发给客户端的,所以此参数的值肯定>0),此id在本方法中不一定保证有意义* @param from_user_id 发送方的user_id* @param dataContent 消息内容* @param fingerPrint 该消息对应的指纹(如果该消息有QoS保证机制的话),用于在QoS重要机制下服务端离线存储时防止重复存储哦* @return true表示应用层已经处理了离线消息(如果该消息有QoS机制,则服务端将代为发送一条伪应答包* (伪应答仅意味着不是接收方的实时应答,而只是存储到离线DB中,但在发送方看来也算是被对方收到,只是延* 迟收到而已(离线消息嘛))),否则表示应用层没有处理(如果此消息有QoS机制,则发送方在QoS重传机制超时* 后报出消息发送失败的提示)* @see #onTransBuffer_C2C_CallBack(Protocal)* @since 4.0*/@Overridepublic boolean onTransBuffer_C2C_RealTimeSendFaild_CallBack(Protocal p) {// 接收者uidString userId = p.getTo();// 发送者uidString from_user_id = p.getFrom();// 消息或指令内容String dataContent = p.getDataContent();// 消息或指令指纹码(即唯一ID)String fingerPrint = p.getFp();// 【重要】用户定义的消息或指令协议类型(开发者可据此类型来区分具体的消息或指令)int typeu = p.getTypeu();logger.debug("【DEBUG_回调通知】[typeu=" + typeu + "]客户端" + from_user_id + "发给客户端" + userId + "的消息:str=" + dataContent+ ",因实时发送没有成功,需要上层应用作离线处理哦,否则此消息将被丢弃.");return false;}
}

2、服务端主动发起消息的QoS回调通知(实现MessageQoSEventListenerS2C类)

public class MessageQoSEventS2CListnerImpl implements MessageQoSEventListenerS2C {private static Logger logger = LoggerFactory.getLogger(MessageQoSEventS2CListnerImpl.class);@Overridepublic void messagesLost(ArrayList lostMessages) {logger.debug("【DEBUG_QoS_S2C事件】收到系统的未实时送达事件通知,当前共有"+ lostMessages.size() + "个包QoS保证机制结束,判定为【无法实时送达】!");}@Overridepublic void messagesBeReceived(String theFingerPrint) {if (theFingerPrint != null) {logger.debug("【DEBUG_QoS_S2C事件】收到对方已收到消息事件的通知,fp=" + theFingerPrint);}}
}

3、服务端配置

public class ServerLauncherImpl extends ServerLauncher {// 静态类方法:进行一些全局配置设置static {// 设置AppKey(此key目前为保留字段,请忽略之)ServerLauncher.appKey = "5418023dfd98c579b6001741";// 设置MobileIMSDK服务端的网络监听端口ServerLauncherImpl.PORT = 7901;// 开/关Demog日志的输出QoS4SendDaemonS2C.getInstance().setDebugable(true);QoS4ReciveDaemonC2S.getInstance().setDebugable(true);ServerLauncher.debug = true;// TODO 与客户端协商一致的心跳敏感模式设置
// ServerToolKits.setSenseMode(SenseMode.MODE_10S);// 关闭与Web端的消息互通桥接器(其实SDK中默认就是false)ServerLauncher.bridgeEnabled = false;// TODO 跨服桥接器MQ的URI(本参数只在ServerLauncher.bridgeEnabled为true时有意义)
// BridgeProcessor.IMMQ_URI = "amqp://js:19844713@192.168.31.190";}// 实例构造方法public ServerLauncherImpl() throws IOException {super();}/*** 初始化消息处理事件监听者.*/@Overrideprotected void initListeners() {// ** 设置各种回调事件处理实现类this.setServerEventListener(new ServerEventListenerImpl());this.setServerMessageQoSEventListener(new MessageQoSEventS2CListnerImpl());}}

4、服务端启动类

温馨小提示:这里由于小编将服务端和客户端集成在同一个项目中,因此如下配置
  1. SpringBoot的CommandLineRunner接口主要用于实现在服务初始化后,去执行一段代码块逻辑(run方法),这段初始化代码在整个应用生命周期内只会执行一次!
  2. @Order(value = 1) :按照一定的顺序去执行,value值越小越先执行

@Slf4j
@Component
@Order(value = 1)
public class ChatServerRunner implements CommandLineRunner {@Overridepublic void run(String... strings) throws Exception {log.info("================= ↓↓↓↓↓↓ 启动MobileIMSDK服务端 ↓↓↓↓↓↓ =================");// 实例化后记得startup哦,单独startup()的目的是让调用者可以延迟决定何时真正启动IM服务final ServerLauncherImpl sli = new ServerLauncherImpl();// 启动MobileIMSDK服务端的Demosli.startup();// 加一个钩子,确保在JVM退出时释放netty的资源Runtime.getRuntime().addShutdownHook(new Thread(sli::shutdown));}}

如果服务端与客户端不在同一个项目 ,服务端可直接通过如下方式启动即可~

f38c3a1ec62f63d71497ced9c7841ef2.png

四、开发客户端

78ebe5ffc5d8c402e139530e0e83818c.png

1、客户端与IM服务端连接事件

@Slf4j
public class ChatBaseEventImpl implements ChatBaseEvent {@Overridepublic void onLoginMessage(int dwErrorCode) {if (dwErrorCode == 0) {log.debug("IM服务器登录/连接成功!");} else {log.error("IM服务器登录/连接失败,错误代码:" + dwErrorCode);}}@Overridepublic void onLinkCloseMessage(int dwErrorCode) {log.error("与IM服务器的网络连接出错关闭了,error:" + dwErrorCode);}}

2、接收消息事件

@Slf4j
public class ChatTransDataEventImpl implements ChatTransDataEvent {@Overridepublic void onTransBuffer(String fingerPrintOfProtocal, String userid, String dataContent, int typeu) {log.debug("[typeu=" + typeu + "]收到来自用户" + userid + "的消息:" + dataContent);}@Overridepublic void onErrorResponse(int errorCode, String errorMsg) {log.debug("收到服务端错误消息,errorCode=" + errorCode + ", errorMsg=" + errorMsg);}}

3、消息是否送达事件

@Slf4j
public class MessageQoSEventImpl implements MessageQoSEvent {@Override // 对方未成功接收消息的回调事件 lostMessages:存放消息内容public void messagesLost(ArrayList lostMessages) {log.debug("收到系统的未实时送达事件通知,当前共有" + lostMessages.size() + "个包QoS保证机制结束,判定为【无法实时送达】!");}@Override // 对方成功接收到消息的回调事件public void messagesBeReceived(String theFingerPrint) {if (theFingerPrint != null) {log.debug("收到对方已收到消息事件的通知,fp=" + theFingerPrint);}}}

4、MobileIMSDK初始化配置

public class IMClientManager {private static IMClientManager instance = null;/*** MobileIMSDK是否已被初始化. true表示已初化完成,否则未初始化.*/private boolean init = false;public static IMClientManager getInstance() {if (instance == null) {instance = new IMClientManager();}return instance;}private IMClientManager() {initMobileIMSDK();}public void initMobileIMSDK() {if (!init) {// 设置AppKeyConfigEntity.appKey = "5418023dfd98c579b6001741";// 设置服务器ip和服务器端口ConfigEntity.serverIP = "127.0.0.1";ConfigEntity.serverUDPPort = 7901;// MobileIMSDK核心IM框架的敏感度模式设置
// ConfigEntity.setSenseMode(SenseMode.MODE_10S);// 开启/关闭DEBUG信息输出ClientCoreSDK.DEBUG = false;// 设置事件回调ClientCoreSDK.getInstance().setChatBaseEvent(new ChatBaseEventImpl());ClientCoreSDK.getInstance().setChatTransDataEvent(new ChatTransDataEventImpl());ClientCoreSDK.getInstance().setMessageQoSEvent(new MessageQoSEventImpl());init = true;}}}

6、连接IM服务端,发送消息

服务类

public interface IChatService {/*** 登录连接IM服务器请求** @param username: 用户名* @param password: 密码* @return: void*/void loginConnect(String username, String password);/*** 发送消息** @param friendId: 接收消息者id* @param msg: 消息内容* @return: void*/void sendMsg(String friendId, String msg);}

服务实现类

@Slf4j
@Service
@Transactional(rollbackFor = Exception.class)
public class ChatServiceImpl implements IChatService {@Overridepublic void loginConnect(String username, String password) {// 确保MobileIMSDK被初始化哦(整个APP生生命周期中只需调用一次哦)// 提示:在不退出APP的情况下退出登陆后再重新登陆时,请确保调用本方法一次,不然会报code=203错误哦!IMClientManager.getInstance().initMobileIMSDK();// * 异步提交登陆名和密码new LocalUDPDataSender.SendLoginDataAsync(username, password) {/*** 登陆信息发送完成后将调用本方法(注意:此处仅是登陆信息发送完成,真正的登陆结果要在异步回调中处理哦)。* @param code 数据发送返回码,0 表示数据成功发出,否则是错误码*/protected void fireAfterSendLogin(int code) {if (code == 0) {log.debug("数据发送成功!");} else {log.error("数据发送失败。错误码是:" + code);}}}.execute();}@Overridepublic void sendMsg(String friendId, String msg) {// 发送消息(异步提升体验,你也可直接调用LocalUDPDataSender.send(..)方法发送)new LocalUDPDataSender.SendCommonDataAsync(msg, friendId) {@Overrideprotected void onPostExecute(Integer code) {if (code == 0) {log.debug("数据已成功发出!");} else {log.error("数据发送失败。错误码是:" + code + "!");}}}.execute();}}

五、编写Controller进行测试

@RestController
@RequestMapping("/api")
@Api(tags = "聊天测试-接口")
public class ChatController {@Autowiredprivate IChatService chatService;@PostMapping(value = "/loginConnect", produces = Constants.CONTENT_TYPE)@ApiOperation(value = "登陆请求", httpMethod = "POST", response = ApiResult.class)public ApiResult loginConnect(@RequestParam String username, @RequestParam String password) {chatService.loginConnect(username, password);return ApiResult.ok();}@PostMapping(value = "/sendMsg", produces = Constants.CONTENT_TYPE)@ApiOperation(value = "发送消息", httpMethod = "POST", response = ApiResult.class)public ApiResult sendMsg(@RequestParam String friendId, @RequestParam String msg) {chatService.sendMsg(friendId, msg);return ApiResult.ok();}}

启动项目,访问:http://127.0.0.1:8080/swagger-ui.html

636ab66e9cb8473d187983ca86f9275b.png

1、 loginConnect接口:任意输入一个账号密码登录连接IM服务端

6111b7990a1a41ff339d26e90f36022d.png

控制台日志如下:

c8cbdf9e6975a9ad8290f21beeed5f71.png

2、 sendMsg接口:给指定用户发送消息,这里由于只有一个客户端,上一步登录了一个admin账号,因此小编给admin账号(也就是自己) 发送消息

7a431ea44d1f3c81d84647170a21f714.png

控制台日志如下:

409a31b32ce9cf8358ceb4d2fc1c500d.png

六、总结

关于集成可参考MobileIMSDK给出的文档一步一步实现,其中给出了通过Java GUI编程实现的一个小demo,我们可以先将其运行起来,先体验一下功能,代码量也不是太多,我们可以通过debug方式查看执行流程,清楚执行流程之后我们就可以将demo中的代码移植到我们自己的项目中加以修改运用于自己的业务中,切勿拿起就跑,否则一旦运气不好,将浪费更多的时间去集成,这样很不好!

74f7373c2014ef7a20188cd7e2ed4b88.png
7220157aacc896f6a14c8517095729b9.png

案例demo中相关代码注释都有,这里就简单说下整个流程吧:

  1. 首先启动IM服务端
  2. 用户在客户端登录一个用户与服务端建立连接保持通信( 客户端ChatServiceImplloginConnect方法为登录连接服务端事件;服务端ServerEventListenerImplonUserLoginAction_CallBack方法为服务端接收的上线通知事件)
  3. 客户端通过 ChatServiceImplsendMsg方法发送一条消息,如果对方在线能接收消息则走服务端ServerEventListenerImplonTransBuffer_C2C_CallBack方法,否则走onTransBuffer_C2C_RealTimeSendFaild_CallBack方法;如果对方成功接收到消息,客户端将走MessageQoSEventImplmessagesBeReceived事件,否则走messagesLost事件
  4. 客户端通过ChatTransDataEventImplonTransBuffer回调事件接收消息

本文案例demo源码

https://gitee.com/zhengqingya/java-workspace



推荐阅读
  • Java验证码——kaptcha的使用配置及样式
    本文介绍了如何使用kaptcha库来实现Java验证码的配置和样式设置,包括pom.xml的依赖配置和web.xml中servlet的配置。 ... [详细]
  • 安卓select模态框样式改变_微软Office风格的多端(Web、安卓、iOS)组件库——Fabric UI...
    介绍FabricUI是微软开源的一套Office风格的多端组件库,共有三套针对性的组件,分别适用于web、android以及iOS,Fab ... [详细]
  • flowable工作流 流程变量_信也科技工作流平台的技术实践
    1背景随着公司业务发展及内部业务流程诉求的增长,目前信息化系统不能够很好满足期望,主要体现如下:目前OA流程引擎无法满足企业特定业务流程需求,且移动端体 ... [详细]
  • 本文介绍了解决Netty拆包粘包问题的一种方法——使用特殊结束符。在通讯过程中,客户端和服务器协商定义一个特殊的分隔符号,只要没有发送分隔符号,就代表一条数据没有结束。文章还提供了服务端的示例代码。 ... [详细]
  • 本文介绍了使用AJAX的POST请求实现数据修改功能的方法。通过ajax-post技术,可以实现在输入某个id后,通过ajax技术调用post.jsp修改具有该id记录的姓名的值。文章还提到了AJAX的概念和作用,以及使用async参数和open()方法的注意事项。同时强调了不推荐使用async=false的情况,并解释了JavaScript等待服务器响应的机制。 ... [详细]
  • Java序列化对象传给PHP的方法及原理解析
    本文介绍了Java序列化对象传给PHP的方法及原理,包括Java对象传递的方式、序列化的方式、PHP中的序列化用法介绍、Java是否能反序列化PHP的数据、Java序列化的原理以及解决Java序列化中的问题。同时还解释了序列化的概念和作用,以及代码执行序列化所需要的权限。最后指出,序列化会将对象实例的所有字段都进行序列化,使得数据能够被表示为实例的序列化数据,但只有能够解释该格式的代码才能够确定数据的内容。 ... [详细]
  • 本文讨论了Alink回归预测的不完善问题,指出目前主要针对Python做案例,对其他语言支持不足。同时介绍了pom.xml文件的基本结构和使用方法,以及Maven的相关知识。最后,对Alink回归预测的未来发展提出了期待。 ... [详细]
  • 本文讨论了如何优化解决hdu 1003 java题目的动态规划方法,通过分析加法规则和最大和的性质,提出了一种优化的思路。具体方法是,当从1加到n为负时,即sum(1,n)sum(n,s),可以继续加法计算。同时,还考虑了两种特殊情况:都是负数的情况和有0的情况。最后,通过使用Scanner类来获取输入数据。 ... [详细]
  • 本文介绍了OC学习笔记中的@property和@synthesize,包括属性的定义和合成的使用方法。通过示例代码详细讲解了@property和@synthesize的作用和用法。 ... [详细]
  • Mac OS 升级到11.2.2 Eclipse打不开了,报错Failed to create the Java Virtual Machine
    本文介绍了在Mac OS升级到11.2.2版本后,使用Eclipse打开时出现报错Failed to create the Java Virtual Machine的问题,并提供了解决方法。 ... [详细]
  • 在说Hibernate映射前,我们先来了解下对象关系映射ORM。ORM的实现思想就是将关系数据库中表的数据映射成对象,以对象的形式展现。这样开发人员就可以把对数据库的操作转化为对 ... [详细]
  • 1,关于死锁的理解死锁,我们可以简单的理解为是两个线程同时使用同一资源,两个线程又得不到相应的资源而造成永无相互等待的情况。 2,模拟死锁背景介绍:我们创建一个朋友 ... [详细]
  • XML介绍与使用的概述及标签规则
    本文介绍了XML的基本概念和用途,包括XML的可扩展性和标签的自定义特性。同时还详细解释了XML标签的规则,包括标签的尖括号和合法标识符的组成,标签必须成对出现的原则以及特殊标签的使用方法。通过本文的阅读,读者可以对XML的基本知识有一个全面的了解。 ... [详细]
  • 本文介绍了RPC框架Thrift的安装环境变量配置与第一个实例,讲解了RPC的概念以及如何解决跨语言、c++客户端、web服务端、远程调用等需求。Thrift开发方便上手快,性能和稳定性也不错,适合初学者学习和使用。 ... [详细]
  • 本文介绍了Web学习历程记录中关于Tomcat的基本概念和配置。首先解释了Web静态Web资源和动态Web资源的概念,以及C/S架构和B/S架构的区别。然后介绍了常见的Web服务器,包括Weblogic、WebSphere和Tomcat。接着详细讲解了Tomcat的虚拟主机、web应用和虚拟路径映射的概念和配置过程。最后简要介绍了http协议的作用。本文内容详实,适合初学者了解Tomcat的基础知识。 ... [详细]
author-avatar
简瞳之殇
这个家伙很懒,什么也没留下!