服务器不能只连接一个客户机就退出——你可以将ServerSocket的accept()方法放在一个循环中调用:进行一个客户机,当服务器与这个客户机通信完毕后,服务器就再次进入循环中,重新调用accept()方法等待下一客户机连接进入,代码示例如下:
while(true){ Socket client=server.accept();//让服务器在while中等待:阻塞状态 //从连接对象上得到输入输出流对象. . . |
当然,如果你要控制服务器的启动和停止,硬编码while的条件为true可不是个好主意。
至此,我们知道了服务器创建的基本流程,但有一件事还没弄明白,一个字符串是如何通过网络被发送到客户机的?如何读取客户机发来的消息?
发送字符串分析:
请注意,发送字符串时,首先调用字符串的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 二进制是:1001000 1个字节是101 二进制是:1100101 2个字节是108 二进制是:1101100 3个字节是108 二进制是:1101100 4个字节是111 二进制是:1101111 5个字节是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输入界面中,每输入一个字符(串),马上会被发送给服务器。在以上代码示例中,我们还改进了让服务能循环等待客户机连接的功能。
要让服务器能读取客户机发来的一段字符串,必须设定一个基本规则:客户机可以连续输入字符给服务器,服务器循环读取,如果收到的字符串有一个回车符,则认为是一条字符串——一条消息。如果收到的字符串是“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所示。
在程序中,字符串最终被以“字节”为单位通过网络发送(接收),再根据双方约定的协议 (如本例中,以回车为一条消息的结束)在程序中进行解析。这个过程如图1.15所示。
图1.15通信时,数据在网络上传送格式的分层示意图
但还有许多问题没有介绍清楚:这些字节是如何被发送到对方IP所在的电脑上的?如果中途丢了字节怎么办?这些字节又是如何转化为为电流信息的……建议大家在完成本节练习后,找一本TCP/IP分析方面的书做深入研读。
在以上代码的基础上,你可以添加上当客户端连接上服务器后,服务项器提示用户输入用户名、输入密码的功能,然后实现服务器与客户机的聊天功能。
现在最大的缺陷是:这个服务器同时只能与一个客户端通信,测试效果如图1.16所示。
当用两个telnet客户端同时连接服务器时,只有一个可以通信,前一个退出后,后一个才能通信。这就是我们下一步的任务:多线程服务器实现。