简单聊天室——总述

简单聊天室——服务器

 

        什么是简单聊天室?什么又是服务器?相对应的,什么又是客户端?

        在刚接触通信时,这是我最想了解的三个问题。好吧,现在让我一一解释:

        简单聊天室,我们可以将之看成QQ中的群聊天,实现群聊,单对单聊天,还有在线好友列表,新增了上下线通知;

        服务器&客户端,服务器将之比作移动公司,而我们的手机就是客户端。我们打电话,就是向服务器申请通话请求,服务器在根据请求作出应答,即连接另一部手机,使之进行通话连接。

 

        在计算机中,我们可以根据IP和端口创建服务器:

        1. 获得IP

            在命令提示符中,输入ipconfig,从中获得IPv4即可。下列都用本地IP  localhost。

        2. 创建端口

            端口一般是从0~2^16之间,前1024个端口为常用端口,有特殊用途,比如80端口,故只用后面的一些端口。

        下列中皆选取9999端口。

        3. 创建服务器(等待客户端接入)

               /**
	 * 创建一个服务器
	 * 
	 * @param port:端口号
	 * @throws IOException
	 */
	public void setUpServer(int port) throws IOException {
		// 创建一个服务器,port为端口号
		server = new ServerSocket(port);
		System.out.println("服务器创建成功!" + port);
		runing = true;
		while (runing) {
			// 等待一个客户端
			Socket client = server.accept();
			// 创建一个客户端线程
			ClientThread ct = new ClientThread(client);
			ct.start();
		}
	}

        蓝色部分便是创建一个服务器,只传入端口号即可。

        红色部分<SPAN style="COLOR: #ff0000">,在计算机中,其实一个端口只能连接上一个进程,换句话讲,一个服务器只能连接一个客户端,这显然不是我们想要的,故将客户端设置为线程,用一个近似于死循环,等待一个个客户端的接入,将Socket对象传入到自定义的客户端线程类中即可操作该客户端,其中一个客户端对应一个客户端线程,当没有客户端接入时,服务器进入阻塞。 

         只要在主函数中调用setUpServer(port)方法,一个简单的服务器&客户端就完成了,只不过他还不能干任何事。

 

下面讲讲服务器&客户端都需要干些什么:

 

一. 服务器类:

        1. ClientThread类  对应客户端的线程,每一个客户端接入就会创建一个该线程,而将线程放入队列中,就是一个用户线程队列,其中定义了方法(参数省略):

                ⑴. getOwerUser()  取得该线程的用户对象(UserInfor类的对象)

                ⑵. closeMe()          关闭该线程

                ⑶. setMessage()    发送给客户端(用到输出流out.write())

                ⑷. readString()      自定义的对客户端发来的信息的读取(用到输入流ins.read()) 

                ⑸. dealMsg()          处理从客户端发来的信息(有关对通信协议xmpp的处理)(完成了登陆应答,注册应答等等)

 

public void run() {
	try {
		ThreadMain();
	} catch (IOException e) {
		e.printStackTrace();
	}
}

public void ThreadMain() throws IOException {
	// 创建输入输出流
	out = client.getOutputStream();
	ins = client.getInputStream();

	// 如果收到bye则断开客户端与服务器的链接
	do {
		// 一行一行的读取客户机发来的消息
		readString(ins);
	} while (!isClose);
}

                 该类中的主要方法,红色部分<SPAN style="COLOR: #ff0000">,是一个近似的死循环,他的作用是不停的从Socket对象client中读取,如果没有则阻塞,这与客户端连接服务器相似。

 

        2. UserInfor类  用户对象的类,在简单聊天室中,只有用户名和密码两个属性,设置&获取 四个方法。

        3. ServerTools类  服务器的工具类,定义了静态的方法,将每个ClientThread的对象放入到一个队列中,即有对应的方法addThread(),remove(),clear(),getNum()(队列中元素个数)等方法,其中将所有线程对象放在一个队列中,就可以根据需求对每一个线程进行操作,实现群发信息。

        4. DaoTools类  不同于 ServerTools类 主要是储存用户的名单(大致一个小型的库),判断用户是否注册,是否已登陆,账号密码正确性,显示在线用户等。用到了哈希表key=用户名,value=密码。

        5. ServerUI类&ServerListener类  界面的类和监听器的类。

 

 

二. 客户端类

        客户端和服务器是不同的两个程序,即有两个主函数,而连接客户端与服务器的纽带就是IP和端口。

        1. ClientUI类 客户端的界面类。

        2. ClientTX类 客户端的通信类。

                ⑴. con2Server()  创建一个客户端连接上服务器。

/**
 * 1.是否连接上服务器
 */
public boolean con2Server() {
	try {
		// 创建一个到服务器端的Socket对象
		Socket client = new Socket(serverIp, port);
                                // 得到输入输出流对象
		ins = client.getInputStream();
		ous = client.getOutputStream();
		System.out.println("服务器连接成功!\n");
		return true;
	} catch (Exception ef) {
		ef.printStackTrace();
	}
	return false;
}

                红色部分<SPAN style="COLOR: #ff0000">,在客户端中穿件Socket对象不同于在服务器中创建,这里需要传入IP地址和端口,注意serverIp在这里是一个String,要输入比如"192.168.1.150"或者"localhost"本地IP;

        ⑵. loginServer(String name,String pwd)  发送登陆的用户名和密码给服务器,服务器验证后传回消息。

        ⑶. chatOnline()  主要的聊天方法。

/**
 * 线程中读取服务器发来的消息
 */
public void run() {
	chatOnline();
}
/**
 * 3.开始聊天 (1).获取信息,写在jta_main上
 */
public void chatOnline() {
	String connent;
	try {
		do {
			// 获得对方的话
			connent = readString(ins);
			jta_main.setText(jta_main.getText() + connent + "\n");
		} while (!isClose);
	} catch (IOException e) {
		e.printStackTrace();
	}
}

                红色部分<SPAN style="COLOR: #ff0000">,不断读取流中的信息,然后反映到JTextArea的对象上。

        ⑷. readString()  自定义的对客户端发来的信息的读取(用到输入流ins.read()) 

        ⑸. dealMsg()   处理从服务器发来的信息(有关对通信协议xmpp的处理)(完成了登陆结果,注册结果等等)

 

 

三. xmpp

        根据xmpp自定义了一套传输的协议。

1. 登陆请求 "<login><name>" + name + "</name><pwd>" + pwd + "</pwd></login><end>"

2. 登陆应答 "<loginResp>No Name</loginResp><end>"

3. 注册请求 "<reg><name>" + yhm + "</name><pwd>" + mm + "</pwd></reg><end>"

4. 注册应答 "<regResp>" + isRegister + "</regResp><end>"(isRegister是DaoTools中的一个静态方法

                  isRegister()的返回值,boolean)

5. 信息发送 "<msg><sender>" + uName  + "</sender><reciver>" + " 大家 " + "</reciver><body>" +

                  content_next + "</body></msg><end>"

6. 下线请求 "<offline>" + uName + "</offline><end>"

 

7. readString(InputStream ins)

/**
 * 自定义的读取字符串的方法 从输入流上读取字节,转为一个字符串,以<end>分割
 * 
 * @param ins:输入流对象
 * @return:读取到的字符串
 * @throws IOException
 */
private void readString(InputStream ins) throws IOException {
	StringBuffer sb = new StringBuffer();
	// 将字节转化成一句话
	while (!sb.toString().endsWith("<end>")) {
		int in = ins.read();
		if (in == 8)
			// 退格键
			sb.deleteCharAt(sb.length() - 1);
		else
			// 将字符转化为字符串
			sb.append((char) in);
	}
	// 中文解析
	input2 = new String(sb.toString().getBytes("ISO-8859-1"), "GBK").trim();
	dealMsg(input2);
}

8. dealMsg(String msg)——服务器

/**
 * 处理信息
 * 
 * @param msg:传入的内容
 * @throws IOException
 */
public void dealMsg(String msg) throws IOException {
	// 登陆应答
	if (msg.startsWith("<login>")) {
		start = msg.indexOf("<name>") + 6;
		end = msg.indexOf("</name>");
		username = msg.substring(start, end);

		start = msg.indexOf("<pwd>") + 5;
		end = msg.indexOf("</pwd>");
		userpwd = msg.substring(start, end);
		user = new UserInfor(username, userpwd);

		// 看用户是否已登录
		boolean isLogin = DaoTools.isLogin(user);

		// 调用数据库模块,验证用户是否存在
		boolean loginState = DaoTools.checkLogin(user);

		if (!loginState) {
			// 不存在这个用户帐号则重新输入
			setMessage("<loginResp>No Name</loginResp><end>");
		} else if (isLogin) {
			// 已登陆,则
			setMessage("<loginResp>Logining</loginResp><end>");
		} else {
			setMessage("<loginResp>OK</loginResp><end>");
		}
		// 添加线程
		if (!isLogin && loginState) {
			// 将线程添加到队列中
			ServerTools.addThread(this); // 认证成功:将这个对象加入服务器队列
			ServerUI.frash();
			ServerTools.castMsg(user.getName(), " 大家 ", "我悄悄地来了.");
		}
	}
	// 消息内容
	else if (msg.startsWith("<msg>")) {
		// 发信人的名字
		start = msg.indexOf("<sender>") + 8;
		end = msg.indexOf("</sender>");
		sender = msg.substring(start, end);
		// 收件人
		start = msg.indexOf("<reciver>") + 9;
		end = msg.indexOf("</reciver>");
		reciver = msg.substring(start, end);
		// 内容
		start = msg.indexOf("<body>") + 6;
		end = msg.indexOf("</body>");
		String output = msg.substring(start, end);
		ServerTools.castMsg(sender, reciver, output);
	}
	// 注册应答
	else if (msg.startsWith("<reg>")) {
		start = msg.indexOf("<name>") + 6;
		end = msg.indexOf("</name>");
		username = msg.substring(start, end);

		start = msg.indexOf("<pwd>") + 5;
		end = msg.indexOf("</pwd>");
		userpwd = msg.substring(start, end);
		boolean isRegister = DaoTools.isRegister(username, userpwd);
		setMessage("<regResp>" + isRegister + "</regResp><end>");
	}
	// 下线应答
	else if (msg.startsWith("<offline>")) {
		start = msg.indexOf("<offline>") + 9;
		end = msg.indexOf("</offline>");
		username = msg.substring(start, end);
		// 清空队列
		isClose = true;
		ServerTools.castMsg(username, " 大家 ", "我悄悄地走了.");
		client.close();
		ServerTools.remove(this);
		ServerUI.frash();
	}
}

 

9. dealMsg(String msg)——客户端

/**
 * 处理信息
 *
 * @param msg:传入的内容
 */
private String dealMsg(String msg) {
	String output = null;
	// 登陆结果
	if (msg.startsWith("<loginResp>")) {
		start = 11;
		end = msg.indexOf("</loginResp>");
		output = msg.substring(start, end);
	}
	// 消息内容
	else if (msg.startsWith("<msg>")) {
		// 发信人的名字
		start = msg.indexOf("<sender>") + 8;
		end = msg.indexOf("</sender>");
		sender = msg.substring(start, end);

		// 收件人的名字
		start = msg.indexOf("<reciver>") + 9;
		end = msg.indexOf("</reciver>");
		reciver = msg.substring(start, end);

		// 内容
		start = msg.indexOf("<body>") + 6;
		end = msg.indexOf("</body>");
		output = sender + "对" + reciver + "说: " + msg.substring(start, end);
	}
	// 注册信息
	else if (msg.startsWith("<regResp>")) {
		start = msg.indexOf("<regResp>") + 9;
		end = msg.indexOf("</regResp>");
		content = msg.substring(start, end);
		if (content.equals("true"))
			javax.swing.JOptionPane.showMessageDialog(null, "注册成功!");
		else
			javax.swing.JOptionPane.showMessageDialog(null, "用户名已注册!");
	}
	return output;
}

 

 

 

 四. 问题总结

 1. 对于 客户端接入 与 注册登陆 先后顺序问题。在登陆界面中,我们需要将登陆信息发送的服务器,在注册界面中,我们也需要发送注册信息。注册 和 登陆 在操作时,不分先后,这就要求我们不能将 客户端的接入只写在登陆界面或注册界面,当然也不能两个都写,可以写在界面生成之前,如果服务器未开启,则不显示登陆界面。否则 会报空指针异常(Socket对象client为空,ins,out输入输出流对象也为空)。

 

2. 在 服务器界面 上显示在线用户,要用到JTable,JTable要时时更新。但是当客户端非正常关闭时,会导致内存泄露,则会出现用户下线,但服务器界面上用户还在线。解决这一问题的方法:可以用windowListener()中的windowClosing()方法重写关闭方法。

 

3. 关于 用户列表 的读取写入问题。在 注册时 我们需要将用户的信息写入到一个文件中,用户名可以是中文,在打开服务器时,要读取文件中的信息,这样用户在注册一次后就可以一直用这个账号登陆,而不用每次开启服务器就要注册一遍。

我在写入时用的是writeUTF()的方法,读取时用readUTF()的方法,如果用write(byte[] b)或者writeByte(byte[] b),read(byte[] b)或者readFully(byte[] b)等方法,会报空指针,原因不懂,希望有人能帮我解决一下。

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值