`
Codestarter
  • 浏览: 11324 次
  • 性别: Icon_minigender_1
  • 来自: 上海
文章分类
社区版块
存档分类

网络通信基础总结4——基于简易的XMPP协议的网络通信工具

阅读更多

      在上一篇总结中,我基本完成了一个简易的聊天工具。可是那个聊天工具的太过死板,必须严格遵循一定的顺序,收发消息。比如服务器是按照什么顺序写消息的,那么客户端就必须得按什么顺序读消息。这样以来的话当一个通信的工程过于繁杂的时候难免会出错。这就需要我们制定一个通信协议来规定这个通信过程的规则!如此一来,就不用顾虑读写的顺序,而只用在乎读写的消息类型!同时,制定协议以后一条消息中可能包含多个内容,这也方便我们做更多的扩展!

     首先让我来大体分析一下交互的流程, 交互流程描述如下:
1.客户机与服务器建立tcp/ip连结后,发送的第一条消息,只能是登陆/注意请求消息
2.某客户机登陆成功,服务器对其发送在线用户列表,并对在线用户发送有人上线的消息;
3.服务器接到客户机发送的聊天消息后,将这条消息发送给指定的客户机用户

4.某个用户下线后,对所有客户机发送下线的消息;

    在规定了这样的一个流程以后,我再来制定一个XMPP协议,也就是一个通信的规则!我写的XMPP是基于XML标签语言进行扩展的,具体分为以下8类:

1.登录请求 :
  <m><type>login</type><name>用户名</name><pwd>密码</pwd></m>

2.登录应答:
  <m><type>loginResp</type><state>登录结果</state></m>  //0:成功,1:用户名错误 ,2:密码错误
 
 3.注册消息:
 <m><type>reg</type><name>用户</name><pwd>密码</pwd></m>
 
 4.注册应答消息
 <m><type>regResp</type><state>注册结果</state></m>  //0表示成功
 
5.聊天消息:
  <m><type>chat</type><sender>发送者名字</sender><reciever>接受者名字</reciever><msg>聊天内容</msg></m>
 
6.在线用户表信息:
  <m><type>budyList</type><user>用户1,用户2,...</user></m>  //在线用户以逗号分离
 
7.上线消息:
  <m><type>onLine</type><user>用户</user></m>  //上线者名字
 
8.下线消息:
  <m><type>offLine</type><user>用户</user></m>  //下线者名字

 

      在分析完以上的流程和规则之后,要做的只是把这样的一个流程和规则加到之前写过的那个以文本和换行来执行通信的群聊工具中去就可以了!唯一不同的是,此时发送的都是XMPP风格的消息。服务器和客户端,通过解析这个XMPP串来确定要发送的消息类型和消息内容既可!以下我分析几个主要的代码,一个是通信辅助类ChatTools类,一个是服务器执行通信的线程类ServerThread类,一个是客户端收发消息的ClientConn类

1.服务器通信辅助类ChatTools类:这个类主要执行当某客户机登陆成功后,服务器对其发送在线用户列表,并对在线用户发送有人上线的消息。同时执行一些转发消息的功能。该类我把一个个客户机的用户名和其对应的线程装入一个码表中,每次发送消息时,我遍历这个码表找到要发送的客户机,然后严格遵守XMPP协议发送既可!具体代码如下:

/**
 * xmpp服务器:连结对象管理和消息转发工具类
 */
public class ChatTools {
	// key为处理对象代表的用户名,vlaue为对应的处理线程对象
	private static Map<String, ProcessThread> pts = new HashMap();

	/**
	 * 当一个处理线程对象启动后,要将其加入到服务器的队列中
	 * 
	 * @param userName
	 *            :客户名字
	 * @param pt
	 *            :一个处理线程对象
	 */
	public static void addPT(String userName, ProcessThread pt) {
		pts.put(userName, pt);// 将处理线程对象放入服务器中
		Set set = pts.keySet();// 1.取得在线用户列表
		Iterator<String> it = set.iterator();

		String names = "";
		String onlineMsg = "<m><type>onLine</type>";
		String onLineContent = onlineMsg + "<user>" + userName + "</user></m>";
		// String
		// onLineContent="<m><type>online</type><user>"+userName+"</user></m>";
		while (it.hasNext()) {
			String nextName = it.next();
			if (!nextName.equals(userName)) {
				// 2.给其它用户发送已上线的消息
				pts.get(nextName).sendMsg(onLineContent);
				// 拼成"用户名,用户名,..."格式串
				names += "," + nextName;
			}
		}
		String head = "<m><type>budyList</type>";
		String content = "<user>" + names + "</user>";
		String buddyListMsg = head + content + "</m>";
		// String buddyListMsg ="<m><type>budyList</type><user>"+names+"</user></m>";
		// 给当前用户发送在线好友列表
		pt.sendMsg(buddyListMsg);
	}

	/**
	 * 将一条消息发给某个用户对象
	 * 
	 * @param userName
	 *            :发送者名字
	 * @param msg
	 *            :消息内容
	 * @param destUserName
	 *            :接收用户名字
	 */
	public static void castMsg(String userName, String msg, String destUserName) {
		for (int i = 0; i < pts.size(); i++) {
			ProcessThread pt = pts.get(destUserName);// 取出队列中的一个线程对象
			try {// 将消息发给接受者名字所代表的处理对象
				if (null != pt && pt.getUserName().equals(destUserName)) {
					pt.sendMsg(msg);
					break;
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}

	// 当一个用户下线时,从队列中移除其所对应的处理对象
	public static void removeUser(String userName) {
		ProcessThread pt = pts.remove(userName);
		Set set = pts.keySet();// 取得在线用户列表
		Iterator<String> it = set.iterator();
		String names = "";
		String offLineMsg = "<m><type>offLine</type><user>" + userName
				+ "</user></m>";
		while (it.hasNext()) {
			String nextName = it.next();
			if (!nextName.equals(userName)) {
				pts.get(nextName).sendMsg(offLineMsg);
				names += "," + nextName;
			}
		}
	}
}

 

2.服务器线程ServerThread类:这个类中主要解析客户端发来的消息类型和消息内容,首先读取发送的第一条消息,只能是登陆/注意请求消息,登录成功后再调用ChatTools中的方法按照XMPP风格的协议执行转发给客户端等功能。具体代码如下:

/**
 * 每一个对象对应于一个客户端
 * 
 * @author Administrator
 * 
 */
public class ProcessThread extends Thread{
	private java.net.Socket client;
	private java.io.OutputStream ous; // 输出流对象
	private java.io.InputStream ins; // 输入流对象
	private String userName; // 客户的用户名
	private boolean connOK = false; // 是否连接成功

	public ProcessThread(Socket client) throws Exception {
		this.client = client;
		ous = client.getOutputStream();
		ins = client.getInputStream();
		connOK = true;
	}

	public void run() {
		try {
			processClient(this.client);
		} catch (Exception e) {
			e.printStackTrace();
			// 从队列中移除这个用户的处理对象,退出聊天
			ChatTools.removeUser(this.userName);
		}
	}

	public String getUserName() {
		return this.userName;
	}

	/**
	 * 给当前的客户机发送一条消息
	 * 
	 * @param msg
	 *            :要发送的消息对象
	 */
	public void sendMsg(String msg) {
		try {
			ous.write(msg.getBytes());
		} catch (Exception e) {
			connOK = false;
		}
	}

	private void processClient(Socket client) throws Exception {
		if(readFirstMsg()){//登录成功
			ChatTools.addPT(this.userName,this);
			while(connOK){
				String msg=readString();
				String type=getXMLValue("type",msg);//解析出消息类型
				if(type.equals("chat")){
					String destUserName=getXMLValue("reciever",msg);
					
					ChatTools.castMsg(this.userName, msg,destUserName);
				}
				else{//其它消息,暂不处理
					System.out.println("unknow Msg type"+type);
				}
			}
		}
	}
	
	//读取客户机发来的第一条消息 注册或者登录
	//如果登陆成功,返回true
	private boolean readFirstMsg()throws Exception{
		//读取客户机发来的第一条消息,登录或注册
		String msg = readString();//1是注册,2是登录
		String type=getXMLValue("type",msg);
		if(type.equals("reg")){//注册请求
			userName = getXMLValue("name",msg);//解析用户名
			String pwd = getXMLValue("pwd",msg);//解析密码
			int state = -1; //预设注册状态
			if(ServerDao.saveUser(userName, pwd)){
				//调用存储模块保存用户名密码,如果保存成功,则认为注册成功
				state=0;
			}
			//给客户机发送注册应答消息
			String resp="<m><type>regResp</type><state>"+state+"</state></m>";
			sendMsg(resp);
			this.client.close();
		}
		if(type.equals("login")){//登录请求
			userName = getXMLValue("name",msg); //解析用户名
			String pwd = getXMLValue("pwd",msg);//解析密码
			int state=-1;
			if(ServerDao.hasUser(userName, pwd)){
				state=0;
			}
			String resp="<m><type>loginResp</type><state>"+state+"</state></m>";
			sendMsg(resp);
			if(state==-1){//如果登录失败
				this.client.close();
			}else{
				return true;
			}
		}
		return false;
	}
	/**
	 * 从一条xmlMsg消息串中提取flagName标记的值,
	 * @param flagName:要提取的标记的名字
	 * @param xmlMsg:要解析的xml消息字符串
	 * @return:提取到flagName标记对应的值
	 * @throws:如果解析失败,则是xml消息格式不符协议规范,抛出异常
	 */
	private String getXMLValue(String flagName, String xmlMsg) throws Exception {
		try{
			//1.标记头出现的位置
			int start = xmlMsg.indexOf("<"+flagName+">");
			start+=flagName.length()+2;
			//2.结束符出现的位置
			int end = xmlMsg.indexOf("</"+flagName+">");
			//3.截取标记所代表的消息的值
			String value = xmlMsg.substring(start,end).trim();
			return value;
		}catch(Exception e){
			throw new Exception("解析"+flagName+"失败:"+xmlMsg);
		}
	}
	
	/**
	* 从输入流中读取一条xml消息,以</msg>结尾即是
	* @return:从流中读取的一条xml消息
	*/
	private String readString() throws Exception{
		String msg="";
		int i = ins.read();//从输入流中读取对象
		StringBuffer stb = new StringBuffer();//创建字符串缓冲区
		boolean end = false;
		while(!end){
			char c = (char)i;
			stb.append(c);
			msg=stb.toString().trim(); //去除消息尾的空格
			if(msg.endsWith("</m>")){
				break;
			}
			i=ins.read();//继续读字节
		}
		msg = new String(msg.getBytes("ISO-8859-1"),"GBK").trim();
		return msg;
	}
}

 

然后在建立一个服务器,等待客户端来连接,这个类已写过好多次这里就不再重复了~如此一来一个基于XMPP风格的服务器就建成了!

 

3.客户端收发消息的ClientConn类:这个类负责验证登录注册信息,同时发送XML风格的消息给服务器,并解析服务器发来的XML串!其实也就是在原来的基础上把读取一行和一个换行符改为了读取一条XML串并解析它而已。这个类要实现多台客户机同时交互,所以要继承线程的方法!具体代码如下:

/**
* xmpp客户机通信模块:接收消息,提供发送接口
* @author 蓝杰 www.NetJava.cn
*/
public class ClientConn extends Thread{
	private javax.swing.JTextArea jta_recive;  //从界面传来显示接受消息的组件
	private JComboBox jcb_users; //界面上JCombox用到的用户名列表
	private OutputStream ous;
	private InputStream ins;
	private boolean connOK=false;
	/**
	* 给接收对象传入一个TextArea对象,以在界面上显示消息
	* @param jta_recive:界面上显示消息组件
	* @param jcb_users:界面上的用户名列表
	*/
	public void setDisplay(JTextArea jta_recive,JComboBox jcb_users){
		this.jta_recive=jta_recive;
		this.jcb_users=jcb_users;
	}
	
	/**
	* 与指定IP指定端口的服务器建立连结
	* @param serverIP:服务器的IP地址
	* @param port:服务器的端口号
	* @return:是否连结成功
	*/
	public boolean conn2Server(String serverIP,int port){
		try{
			Socket sc = new Socket(serverIP,port);//连接上服务器
			ins=sc.getInputStream();
			ous=sc.getOutputStream();
			return connOK=true;
		}catch(Exception e){return connOK=false;}
	}
	
	/**
	* 客户机发送登陆请求消息的调用
	* @param name:用户名
	* @param pwd:密码
	* @return:是否登陆成功
	* @throws Exception
	*/
	public boolean login(String name,String pwd){
		try{
			String login="<m><type>login</type><name>"+name+"</name><pwd>"+pwd+"</pwd></m>";//1.拼接登陆消息xml串
			ous.write(login.getBytes());//发送登陆请求xml消息
			String xml = readString();//读取登录应答
			String state = getXMLValue("state",xml);
			return state.equals("0");
		}catch(Exception e){
			return false;
		}
	}
	
	/**
	* 客户机发送注册请求消息的调用
	* @param name:用户名
	* @param pwd:密码
	* @return:是否注册成功
	* @throws Exception
	*/
	public boolean reg(String name,String pwd){
		try{
			String reg="<m><type>reg</type><name>"+name+"</name><pwd>"+pwd+"</pwd></m>";
			ous.write(reg.getBytes());//发送登陆请求xml消息
			String xml = readString();//读取登录应答
			String state = getXMLValue("state",xml);
			return state.equals("0");
		}catch(Exception e){return false;}
	}
	
	/**
	 * 向服务器发送对某一用户的文本聊天消息
	 * @param sender
	 * @param reciver
	 * @param msg
	 */
	public void sendTextChat(String sender,String reciver,String msg){
		try{
			String textChatXML = "<m><type>chat</type><sender>" +sender+
				"</sender><reciever>" + reciver+
				"</reciever><msg>" +msg+
				"</msg></m>";
		ous.write(textChatXML.getBytes());
		}catch(Exception e){e.printStackTrace();}
	}
	
	//线程运行后,连接服务器,接收服务器发来的消息
	public void run(){
		try{
			while(connOK){
				String xmlMsg = readString(); //接收一条消息
				String type = getXMLValue("type",xmlMsg);
				//登录成功后,接收到的消息类型可能有 聊天。用户表,上线,下线
				if(type.equals("chat")){//如果服务器发来的是聊天消息
					String sender = getXMLValue("sender",xmlMsg);  //取得发送者名字
					String msg = getXMLValue("msg",xmlMsg);  //取得消息内容
					jta_recive.append(sender+"说"+msg+"\r\n");  //显示到界面
				}
				if(type.equals("budyList")){//服务器发来的在线用户表信息
					//取得在线用户表,多个在线用户名,用,分开
					String users=getXMLValue("user",xmlMsg);
					//解析以,分割的在线用户名,加入到界面列表中
					java.util.StringTokenizer stk = new StringTokenizer(users,",");
					while(stk.hasMoreTokens()){
						String uName=stk.nextToken();
						jcb_users.addItem(uName+" ");//加到界面的用户列表中去
					}
					
				}
				if(type.equals("onLine")){//服务器发来的用户上线消息
					String uName=getXMLValue("user",xmlMsg);
					jta_recive.append(uName+"上线啦\r\n");
					jcb_users.addItem(uName);//加到界面的用户列表中
				}
				if(type.equals("offLine")){
					String uName = getXMLValue("user",xmlMsg);
					jta_recive.append(uName+"下线啦!\r\n");
					
					int count = jcb_users.getItemCount();
					for(int i=0;i<count;i++){
						String it = (String)jcb_users.getItemAt(i);
						it=it.trim();
						if(it.equals(uName)){
							jcb_users.removeItemAt(i);
							break;
						}
					}
				}
			}
		}catch(Exception e){connOK=false;}
		
	}
	
	
	
	/**
	 * 从一条xmlMsg消息串中提取flagName标记的值,
	 * @param flagName:要提取的标记的名字
	 * @param xmlMsg:要解析的xml消息字符串
	 * @return:提取到flagName标记对应的值
	 * @throws:如果解析失败,则是xml消息格式不符协议规范,抛出异常
	 */
	private String getXMLValue(String flagName, String xmlMsg) throws Exception {
		try{
			//1.标记头出现的位置
			int start = xmlMsg.indexOf("<"+flagName+">");
			start+=flagName.length()+2;
			//2.结束符出现的位置
			int end = xmlMsg.indexOf("</"+flagName+">");
			//3.截取标记所代表的消息的值
			String value = xmlMsg.substring(start,end).trim();
			return value;
		}catch(Exception e){
			throw new Exception("解析"+flagName+"失败:"+xmlMsg);
		}
	}
	
	/**
	* 从输入流中读取一条xml消息,以</msg>结尾即是
	* @return:从流中读取的一条xml消息
	*/
	private String readString() throws Exception{
		String msg="";
		int i = ins.read();//从输入流中读取对象
		StringBuffer stb = new StringBuffer();//创建字符串缓冲区
		boolean end = false;
		while(!end){
			char c = (char)i;
			stb.append(c);
			msg=stb.toString().trim(); //去除消息尾的空格
			if(msg.endsWith("</m>")){
				break;
			}
			i=ins.read();//继续读字节
		}
		msg = new String(msg.getBytes("ISO-8859-1"),"GBK").trim();
		return msg;
	}
}

 

以上一个客户机的主要实现流程也就做好了!

 

      XMPP协议风格通信的最大好处就是减少了很多死板的通信流程,不用严格按照一定的顺序收发消息,而可以按照消息的类型收发消息。这大大增加了程序的灵活性!同时一个XMPP协议的串中可以包含多层信息!也大大减少了代码量方便我们更好的扩展!不过上述代码在很多地方还有漏洞,比如当一个客户端下线时,程序会报错,执行了一个SOCKET的异常!这就需要我在以后的代码中继续改进才是!

分享到:
评论

相关推荐

    安卓Android源码——基于XMPP协议的多帐号聊天.zip

    《安卓Android源码——基于XMPP协议的多帐号聊天》 在移动通信领域,实时通讯功能已经成为各种应用程序不可或缺的一部分。Android平台提供了丰富的API和工具,使得开发者能够构建复杂且高效的应用,其中包括聊天...

    基于Xmpp协议的Android端即时通讯软件,Beem的源码

    **基于Xmpp协议的Android端即时通讯软件——Beem源码解析** Xmpp(Extensible Messaging and Presence Protocol)是一种开放的、基于XML的即时通讯协议,广泛应用于各种实时通信系统,包括聊天应用、协作工具等。...

    基于Jabber/XMPP 的即时通讯系统的设计与实现

    #### Jabber/XMPP协议详解 Jabber与XMPP(可扩展消息处理协议)是两种密切相关的即时通讯技术。最初,Jabber被设计为一种开放式的即时通讯协议,旨在打破当时市场上的封闭式通讯系统壁垒,实现跨平台、跨网络的即时...

    WeiXin:Swift练手项目——基于XMPP的微信

    "WeiXin:Swift练手项目——基于XMPP的微信" 这个标题揭示了一个软件开发项目,旨在使用Swift编程语言来模仿微信的功能。它特别指出项目是基于XMPP(Extensible Messaging and Presence Protocol)协议,这是一种开放...

    安卓Andriod源码——【仿微信即时聊天】xmpp4第一期.zip

    【安卓Andriod源码——【仿微信即时聊天】xmpp4第一期.zip】这个压缩包文件主要包含了一个安卓应用的源代码,该应用模仿了微信的即时聊天功能,使用了XMPP协议进行通信。XMPP(Extensible Messaging and Presence ...

    xmpp——Smack的IM实现

    XMPP(Extensible Messaging and Presence Protocol)是一种基于XML的开放标准通信协议,主要用于即时通讯(IM)和在线状态管理。它的设计目标是提供一个灵活、可扩展的框架,支持多种应用,如聊天、文件传输、语音...

    安卓Android源码——【仿微信即时聊天】xmpp4安卓Android 第一期.rar

    【安卓Android源码——【仿微信即时聊天】xmpp4安卓Android 第一期.rar】这个压缩包文件主要包含了一个安卓应用的源代码,该应用模仿了微信的即时...同时,对XMPP协议的理解将有助于扩展到其他基于此协议的通信系统。

    XMPP基本概念-节点(二)

    XMPP协议的灵活性使得它能适应多种应用场景,如企业级即时通讯、物联网通信、游戏中的实时交互等。开发者可以通过开源库,如Smack、Java Jabber Library(JJlib)等来实现XMPP功能。 为了更好地理解和应用XMPP,...

    安卓Android源码——AdXmpp(Openfire+asmack+spark)即时通信.zip

    首先,AdXmpp是一个针对Android平台设计的即时通信解决方案,它整合了Openfire、Asmack和Spark这三款开源工具,以实现高效、稳定且安全的XMPP(Extensible Messaging and Presence Protocol)协议通信。 1. **...

    安卓Android源码——AdXmpp(Openfire+asmack+spark).zip

    这个项目的源码对于学习XMPP协议、Android网络编程以及如何在Android上实现RTC功能具有极高的参考价值。通过深入研究AdXmpp的源码,开发者可以掌握如何在Android应用中集成实时通信功能,并理解背后的工作原理,从而...

    WebRTC零基础开发者教程(纯净版)

    6.1.2 XMPP 协议网络架构 6.1.3 XMPP 协议的组成 6.1.4 Xmpp介绍 6.1.5 协议内容 6.2 Stun协议 6.2.1 P2P实现的原理 6.2.2 P2P的常用实现 6.2.3 Stun URI 6.2.4 内容 6.2.5 中文内容 6.2.6 开源服务器 6.2.7 公开的...

    IOS源码之iChabber-Simple gtalk and jabber client for the iphone

    4. **多线程**:为了保证用户体验,iChabber通常在后台线程处理网络通信,避免阻塞主线程。学习多线程处理可以提高应用性能和响应性。 5. **XML解析**:XMPP协议使用XML进行数据交换,因此,理解XML的解析和生成是...

    android应用源码(精)基于asmack开发的Android开源IM客户端.zip

    4. **XMPP协议**:理解XMPP的基本原理,包括XML流、实体、资源、会话、消息类型等,以及如何使用XMPP进行即时通讯。 5. **Android UI设计**:分析应用的用户界面设计,包括Activity和Fragment的使用,以及自定义...

    网络聊天程序网络聊天程序网络聊天程序

    首先,我们需要理解网络通信的基础——TCP/IP协议栈。TCP(传输控制协议)负责数据的可靠传输,而IP(因特网协议)则处理数据在网络中的路由。网络聊天程序通常基于TCP或UDP协议来实现数据的发送和接收,前者保证了...

    wildfire_src_3_1_1.zip_ 即时消息_install4j_jabber server_wildfire_wi

    Wildfire,现在通常被称为Ignite Realtime,是一个开源项目,它基于Jabber/XMPP协议,允许用户进行实时通信,包括个人消息、状态更新和群组聊天。 Jabber协议,现被称为XMPP(可扩展消息处理和Presence Protocol)...

    Android AdXmpp(Openfire+asmack+spark)即时通信.rar

    Openfire是基于XMPP协议的服务器软件,它提供了一个可扩展的、高性能的即时通信平台。在我们的即时通信系统中,Openfire作为后台服务器,处理用户的注册、登录、消息传输等核心功能。安装并配置Openfire后,开发者...

    WebRTC零基础开发者教程(pdf)

    在协议方面,教程涵盖了XMPP协议、STUN和TURN协议等WebRTC相关的通信协议。XMPP(Extensible Messaging and Presence Protocol)协议用于支持实时通信,例如即时消息、状态信息以及互联网中继聊天(IRC)。STUN...

    企业即时通信客户端的开发和性能测试.doc

    总之,企业即时通信客户端的开发涉及到了XMPP协议的应用、跨平台开发、系统性能优化等多个关键点。通过合理的设计和测试,可以创建出满足企业需求、性能优异的通信工具,进一步提升企业的运营效率和竞争力。在实际...

Global site tag (gtag.js) - Google Analytics