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: