Zookeeper客户端编程趣味小例子——网络聊天

Zookeeper聊天室客户端
本文介绍了一个基于Zookeeper实现的简单聊天室客户端程序。该程序能够显示在线用户、接收消息并监听用户登录登出事件。通过创建临时节点来标识在线状态,并利用Zookeeper的数据变更监听功能实现消息传递。

共享一份闲暇之余编写的Zookeeper客户端使用的一些最基础的代码,下面实现的是一个网络聊天的小程序

需要将Zookeeper的服务器启动好了之后,修改其中的IP地址就可,可配置多个服务器IP

package yz.web;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;

import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.KeeperException.NodeExistsException;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.Watcher.Event.EventType;
import org.apache.zookeeper.ZooDefs.Ids;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.data.Stat;

public class AppClient implements Watcher, Runnable{  
    private String clientListNode = "/chat";  
    private ZooKeeper zk;  
    private Stat stat = new Stat();  
    private volatile List<String> onlineUsers = new ArrayList<String>();  
    private String myself = "";
    private CountDownLatch connectedSignal = new CountDownLatch(1);
  
    public void connectZookeeper() throws Exception {  
        zk = new ZooKeeper("192.168.20.97:2181,192.168.20.97:2182,192.168.20.97:2183,192.168.20.97:2184,192.168.20.97:2185", 5000, this);
        connectedSignal.await(); // 等待连接完成
        userLogin();
    }  

	private void userLogin() throws IOException, KeeperException, InterruptedException {
		showOnlineUsers();
		myself = inputName();
    	createNamePath(myself);
    	zk.getData(clientListNode, true, stat); // 注册NodeDataChanged事件的watcher
	}
	
	private void showOnlineUsers() throws KeeperException, InterruptedException {
		List<String> onlineUsers = zk.getChildren(clientListNode, true);
		if (onlineUsers.size() > 0) {
			System.out.println("The online users is shown as below:");
			for (String username : onlineUsers) {
				System.out.println(username);
				this.onlineUsers.add(username);
			}
		} else {
			System.out.println("No one is online!");
		}
	}

	private void createNamePath(String name) throws KeeperException, InterruptedException, IOException {
		try {
        	zk.create(clientListNode + "/" + name, null, Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
        } catch (NodeExistsException e) {
        	System.out.println("The name is duplicated...");
        	userLogin();
        } catch (Exception e) {
        	System.out.println("Occur unexpected exception...");
        	userLogin();
        }
	}
	
	private String inputName() throws IOException {
		System.out.println("Please input your name:");
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    	String name = br.readLine();
		return name;
	}
  
	private String getLastPathName(String path) {
    	return path.substring(path.lastIndexOf("/") + 1, path.length());
	}
  
    public void handleChat() throws InterruptedException {  
        run();
        Thread.sleep(Long.MAX_VALUE);
    }  
  
    public void run() {
    	while (true) {
	    	try {
	    		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
				String chatContent = br.readLine();
				if (chatContent == null || "".equals(chatContent)) {
					continue;
				}
				zk.setData(clientListNode, (myself + " says: " + chatContent).getBytes("utf-8"), -1);
			} catch (Exception e) {
				e.printStackTrace();
			}
    	}
	}

	public void process(WatchedEvent event) {
        String path = event.getPath();
        if (event.getType() == Event.EventType.None) {
            // We are are being told that the state of the
            // connection has changed
            switch (event.getState()) {
            case SyncConnected:
                // In this particular example we don't need to do anything
                // here - watches are automatically re-registered with 
                // server and any watches triggered while the client was 
                // disconnected will be delivered (in order of course)
            	connectedSignal.countDown();// 倒数-1
                break;
            case Expired:
                // It's all over
                break;
			default:
				break;
            }
        } else {
        	if (event.getType() == EventType.NodeChildrenChanged && path.equals(clientListNode)) {
            	try {
    				checkUsers();
    			} catch (KeeperException e) {
    				e.printStackTrace();
    			} catch (InterruptedException e) {
    				e.printStackTrace();
    			}
            } else if (event.getType() == EventType.NodeDataChanged) {
            	try {
					String chatContent = new String(zk.getData(clientListNode, true, stat), "utf-8");
					System.out.println(chatContent);
				} catch (KeeperException e) {
					e.printStackTrace();
				} catch (InterruptedException e) {
					e.printStackTrace();
				} catch (UnsupportedEncodingException e) {
					e.printStackTrace();
				}
            }
        }
	}
	
	protected void checkUsers() throws KeeperException, InterruptedException {
		List<String> onlineUsers = zk.getChildren(clientListNode, true);
		int preUserNum = this.onlineUsers.size();
		int nowUserNum = onlineUsers.size();
		List<String> tempList = new ArrayList<String>(Arrays.asList(new String[this.onlineUsers.size()]));
		Collections.copy(tempList, this.onlineUsers);
		
		if (preUserNum == nowUserNum) {
			return;
		} else if (preUserNum > nowUserNum) {
			tempList.removeAll(onlineUsers);
			someoneLogout(tempList);
		} else if (preUserNum < nowUserNum) {
			onlineUsers.removeAll(tempList);
			someoneLogin(onlineUsers);
		}
	}
    
	protected void someoneLogout(List<String> paths) {
		for (String path : paths) {
			String username = getLastPathName(path);
			if (onlineUsers.contains(username)) {
				System.out.println(username + " is logout...");
				onlineUsers.remove(username);
			}
		}
	}

	private void someoneLogin(List<String> paths) {
		for (String path : paths) {
			String username = getLastPathName(path);
			if (!onlineUsers.contains(username)) {
				System.out.println(username + " is login...");
				onlineUsers.add(username);
			}
		}
	}
	
    public static void main(String[] args) {  
    	try {
	        AppClient ac = new AppClient();  
	        ac.connectZookeeper();  
	        ac.handleChat();  
    	} catch(Exception e){
    		e.printStackTrace();
    	}
    }
}  


  	<dependency>
	    <groupId>org.apache.zookeeper</groupId>
	    <artifactId>zookeeper</artifactId>
	    <version>3.4.6</version>
	</dependency>



zkfire = zookeeper openfire(3.8.1)     Openfire 采用Java开发,开源的实时协作(RTC)服务器基于XMPP(Jabber)协议,您可以使用它轻易的构建高效率的即时通信服务器.    根据对xmpp与openfire的理解,我在openfire中相应的地方植入少量的代码,并把zookeeper包也一并打包到zkfire中。使用zookeeper(http://zookeeper.apache.org/)管理集群中的节点。   客户登陆集群中的不同服务器进行通信就如登陆同一台服务器一样。   openfire自身也有一套集群的实现,使用了oracle 的coherence的中间件,使用时要自己加入相应的jar包与集群插件。   之所以又自己开发了一套集群实现,一个是给集群提供多一些选择,一个是兴趣^_^,让openfire天然就支持集群      zkfire使用的场景:   zkfire中有zookeeper的服务器监听与客户端连接程序,但可以不依赖自身的zookeeper服务,可以在openfire之外另外开启其他zookeeper服务,此时只需指定   cluster.xml配置文件中zClient节点的连接地址即可。   如果只是zookeeper单机服务,那么所有openfire服务器只需要连到同一个zookeeper服务器就可以完成openfire的集群   如果是zookeeper集群,根据zookeeper的集群特点,集群中节点不应该少于3台。如果超过一半的zk节点宕机,那么整个集群境将不能正常的工作。      使用方法:   将zkfire.jar包替换lib下的openfire.jar,之所以命名zkfire.jar只是为了易于区分,名字可以随意取。并将cluster.xml放到bin目录下。   zkfire基于单openfire的实现,所以如果使用的话建议不要开启openfire自身的集群功能。   在安装的openfire目前bin下,放入cluster.xml文件。   示例内容如下:    <?xml version="1.0" encoding="UTF-8"?>    <jive>         <!-- 该节点用于openfire服务器之间通讯。IP为本机IP地址,需其他服务器能访问到 --> <notice>10.10.152.180:3004</notice>          <!-- zoo节点用于配置zkfire的zookeeper服务。如果用其他zk服务器,那么这个节点可以去掉。-->    <zoo>               <tickTime>2000</tickTime>              <initLimit>10</initLimit>              <syncLimit>5</syncLimit>              <dataDir>E:/zoo/data</dataDir>              <clientPort>3181</clientPort>               <server name="server.1">10.10.152.180:2888:3888</server>               <server name="server.2">10.10.152.185:2888:3888</server>               <server name="server.3">10.10.152.189:2888:3888</server>               <myid>1</myid>    </zoo>                <!-- 该节点用于连接zk服务器,如果连接zkfire自身的zk服务器,那么该节点可以去掉 -->      <zClient>127.0.0.1:3181</zClient>    </jive>  zoo中的节点server用于配置zookeeper的集群,myid指定本身zookeeper服务器的myid值,server.X 这个数字就是对应myid中的数字,集群中不同zk服务器的myid值不同。    zoo中其他节点的内容皆对应zk配制文件的键值内容。这里不再详述,可以参考 http://rdc.taobao.com/team/jm/archives/665,但dataDir与clientPort是必须配置,用于指定zookeeper数据文件地址与监听端口。    有任何问题请随时email给我donnie4w@gmail.com 标签:zkfire
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值