ネットワーク通信基礎総括4——簡易XMPPプロトコルに基づくネットワーク通信ツール


前のまとめでは、簡単なチャットツールをほぼ完成しました.しかし、そのチャットツールは堅苦しすぎて、一定の順序を厳守して、メッセージを受信しなければなりません.例えばサーバがどの順番でメッセージを書くかというと、クライアントはどの順番でメッセージを読まなければなりません.このようにして以来、通信の工事が煩雑すぎると、間違いがあります.これは私達が通信プロトコルを制定してこの通信過程の規則を規定する必要があります.これでは、読む順番を気にせず、読むタイプと書くタイプだけが気になります.同時に、協議を制定した後、一つのメッセージには複数の内容が含まれているかもしれません.これは私達がもっと多くの拡張をするのにも便利です.
     まず相互作用の流れを大体分析させていただきます. インタラクティブフローは、次のように説明されています.1.クライアントとサーバがtcp/ip接続を確立した後、送信された最初のメッセージは、ログイン/注意要求メッセージのみです.3.サーバは、クライアントから送信されたチャットメッセージを受信し、このメッセージを指定されたクライアントユーザに送信する.
4.あるユーザーがオフラインした後、すべてのクライアントにオフラインのメッセージを送信する.
    このような流れが決まったら、XMPPプロトコル、つまり通信のルールを作ります.私が書いたXMPPはXMLタグ言語に基づいて拡張されています.具体的には以下の8種類に分けられます.
1.ログイン要求:  lognユーザ名パスワード
2.ログイン応答:  loginResp登録結果 //0:成功、1:ユーザ名エラー、2:パスワードエラー   3.登録メッセージ: regユーザパスワード  4.登録応答メッセージ regResp登録結果 //0は成功を表します  5.チャットメッセージ:  chat送信者名受信者名チャット内容  6.オンラインユーザテーブル情報:  budyListユーザ1,ユーザ2,… //オンラインユーザーはカンマで分離します.  7.オンラインメッセージ:  onlineユーザー //上線者の名前  8.オフラインメッセージ:  offLineユーザ //オフラインの名前
 
      以上の流れとルールを分析してから、前に書いたテキストと改行で通信を行うグループチャットツールにこのような流れと規則を加えるだけでいいです.唯一の違いは、このとき送信されるのはXMPPスタイルのメッセージです.サーバとクライアントは、このXMPPストリングを解析することにより、送信するメッセージタイプとメッセージ内容を決定することができます.以下でいくつかの主要なコードを分析します.一つは通信補助類ChotTools類で、一つはサーバーが通信を実行するスレッド類ServerThread類で、一つはクライアントがメッセージを送受信するCientConn類です.
1.サーバ通信補助類ChotTools類:この類は主にあるクライアントがログインした後、サーバーがオンラインユーザーリストを送信し、オンラインユーザにオンライン上であるメッセージを送信する.いくつかの転送メッセージの機能を同時に実行します.このクラスは、クライアントのユーザ名と対応スレッドをコード表に入れて、メッセージを送るたびに、このコード表を通して送信するクライアントを見つけて、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クラス:このクラスでは主にクライアントから送られてきたメッセージの種類とメッセージの内容を解析し、まず送信された第一のメッセージを読み取り、ログイン/注意要求メッセージのみを読み取り、ログインが成功したらChat Toolsのメソッドを呼び出し、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.クライアントからメッセージを受信するClienntConクラス:このクラスは登録情報を検証し、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
"); // } 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
"); jcb_users.addItem(uName);// } if(type.equals("offLine")){ String uName = getXMLValue("user",xmlMsg); jta_recive.append(uName+" !\r
"); 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の異常を実行しました.これからのコードの中で改善し続ける必要があります.