2012年4月1日星期日

2012.04.02

2012.04.02



《微博是这样炼成的:从聊天室到Twitter的技术实现》

第一章 JavaKe起步:聊天室的实现

1.1.3 服务器读写消息实现

1.设定服务器顺循环等待:

服务器不能只连接一个客户机就退出——你可以将ServerSocket的accept()方法放在一个循环中调用:进行一个客户机,当服务器与这个客户机通信完毕后,服务器就再次进入循环中,重新调用accept()方法等待下一客户机连接进入,代码示例如下:

while(true){Socket client=server.accept();//让服务器在while中等待:阻塞状态//从连接对象上得到输入输出流对象. . .

当然,如果你要控制服务器的启动和停止,硬编码while的条件为true可不是个好主意。

2.读取客户机字符串分析

至此,我们知道了服务器创建的基本流程,但有一件事还没弄明白,一个字符串是如何通过网络被发送到客户机的?如何读取客户机发来的消息?

发送字符串分析:

请注意,发送字符串时,首先调用字符串的getBytes()方法,得到组成这个字符串的字节数组,发送出的实际上是这个字节数组。请测试如下代码,示例了在字符串和和字节数组之间的转换:

String s="Hello!";byte[] data=s.getBytes();for(int i=0;i<data.length;i++){	System.out.println(i+"个字节是"+data[i]	+" 二进制是:"+Integer.toBinaryString(data[i]));	}	//再将字节组转为字符串	String src=new String(data);	System.out.println("生成的字符串是: "+src);

运行后,输出的结果如下:

0个字节是72 二进制是:10010001个字节是101 二进制是:11001012个字节是108 二进制是:11011003个字节是108 二进制是:11011004个字节是111 二进制是:11011115个字节是33 二进制是:100001生成的字符串是: Hello!

我的目地只是要说明:字符串是由字节组成的,字节是二进制位组成的——在网络上发送的,其实是一个个字节(byte),或者说是组成每一个字节的一个二进制位(bit)。从网络中读取信息时,情况也是一样的。从InputStream对象中,一次只能读到一个字节,然后,再将这些字节组装为一个String对象——前提是对方发送的是一串文本,而不是一张图片,如下代码:

public void setUpServer(int port) {		try {			// 建立绑定在指定端口上的服务器对象			ServerSocket server = new ServerSocket(port);			System.out.println("服务器创建成功!" + port);			while (true) { // 让服务器循环等待				Socket client = server.accept();				// 从连接对象上得到输入输出流对象				OutputStream out = client.getOutputStream();				InputStream ins = client.getInputStream();				String s = "你好,欢迎来javaKe.com\r\n";				byte[] data = s.getBytes();// 取得组成这个字符串的字节				out.write(data); // 用输出对象发送数据				out.flush();// 强制输出				int in = 0; // 一个字节一个字节的读取客户机的输入				while (in != 13) {// 如果读到不是13,即回车字符					in = ins.read();					System.out.println("读到的一个是: " + in);				}				System.out.println("客户机按了回车,退出:" + in);				client.close();// 半闭与这个客户机的连接			}		} catch (Exception ef) {			ef.printStackTrace();		}	}

运行如上程序,在命令行通过telnet连接后,输入一些字符,如图1.13所示。

可以看到,在telnet输入界面中,每输入一个字符(串),马上会被发送给服务器。在以上代码示例中,我们还改进了让服务能循环等待客户机连接的功能。

3.读取字符串实现

要让服务器能读取客户机发来的一段字符串,必须设定一个基本规则:客户机可以连续输入字符给服务器,服务器循环读取,如果收到的字符串有一个回车符,则认为是一条字符串——一条消息。如果收到的字符串是“bye”,则认为客户机要结束通信,就断开客户机。

实现代码如下:

/** *  * 简单Echo服务器实现 *  * @author www.NetJava.cn */public class ChatServer {	/**	 * 	 * 在指定端口上启动一个服务器	 * 	 * @param port	 *            :服务器所以的端口	 */	public void setUpServer(int port) {		try {			// 建立绑定在指定端口上的服务器对象			java.net.ServerSocket server = new java.net.ServerSocket(port);			System.out.println("服务器创建成功!" + port);			// 当有客户机连接上时,等待方法就会返回,返回一个代表与客户机连接的对象			while (true) { // 让服务器进入循环等待状态				java.net.Socket client = server.accept();				System.out.println("Incoming clieng: "						+ client.getRemoteSocketAddress());				// 调用处理连接对象的方法去处理连接对象				processChat(client);			}		} catch (Exception ef) {			ef.printStackTrace();		}	}	/**	 * 	 * 处理连接对象:读取客户机发来的字符串,回送给客户机	 * 	 * @param client	 *            :与客户机的连接对象	 */	private void processChat(java.net.Socket client) throws Exception {		OutputStream out = client.getOutputStream();		InputStream ins = client.getInputStream();		String s = "你好,欢迎来到服务器!\r\n";		byte[] data = s.getBytes();// 取得组成这个字符串的字节		out.write(data); // 用输出对象发送!		out.flush();// 强制输出		// 调用读取字符串的方法,从输入流中读取一个字符串		String inputS = readString(ins);		while (!inputS.equals("bye")) {			System.out.println("客户机说: " + inputS);			s = "服务器收到:" + inputS + "\r\n";			data = s.getBytes();// 取得组成这个字符串的字节数组			out.write(data); // 用输出流对象发送!			out.flush();// 强制输出			inputS = readString(ins); // 读取客户机的下一次输入		}		s = "你好,欢迎再来!\r\n";		data = s.getBytes();		out.write(data);		out.flush();		client.close();// 半闭与客户机的连接	}	/**	 * 	 * 从输入流对象中读取字节,拼成一个字符串返加	 * 	 * 如果读到一个字节值为13,则认为以前的是一个字符串	 * 	 * @param ins	 *            :输入流对象	 * 	 * @return :从流上(客户机发来的)读到的字符串	 */	private String readString(InputStream ins) throws Exception {		// 创建一个字符串缓冲区		StringBuffer stb = new StringBuffer();		char c = 0;		while (c != 13) {			// 遇到一个换行,就是一句话			int i = ins.read();// 读取客户机发来的一个字节			c = (char) i;// 将输入的字节转换为一个Char			stb.append(c);// 将读到的一个字符加到字符串缓冲区中		}		// 将读到的字节组转为字符串,并调用trim去掉尾部的空格		String inputS = stb.toString().trim();		return inputS;	}	// 程序入口	public static void main(String[] args) {		ChatServer cs = new ChatServer();		cs.setUpServer(9090);	}}

运行如上程序,现在,服务器可以和客户机对话了吗?如图1.14所示。

4.网络数据传送总结。

在程序中,字符串最终被以“字节”为单位通过网络发送(接收),再根据双方约定的协议 (如本例中,以回车为一条消息的结束)在程序中进行解析。这个过程如图1.15所示。

图1.15通信时,数据在网络上传送格式的分层示意图

但还有许多问题没有介绍清楚:这些字节是如何被发送到对方IP所在的电脑上的?如果中途丢了字节怎么办?这些字节又是如何转化为为电流信息的……建议大家在完成本节练习后,找一本TCP/IP分析方面的书做深入研读。

在以上代码的基础上,你可以添加上当客户端连接上服务器后,服务项器提示用户输入用户名、输入密码的功能,然后实现服务器与客户机的聊天功能。

现在最大的缺陷是:这个服务器同时只能与一个客户端通信,测试效果如图1.16所示。

当用两个telnet客户端同时连接服务器时,只有一个可以通信,前一个退出后,后一个才能通信。这就是我们下一步的任务:多线程服务器实现。



TAG: