在这一段,我们的目地是创建一个简单服务器,能将客户机发来的字符串显示出来,并且再回送给客户机——有必要解释一下:在这里,服务器指的是等待别人来连接的机器;客户机,当然就指的是主动去连接别人的机器了,这就像打电话过程中的主叫与被叫的区分一样,一旦连结成功,就不存在这样谁是客户机谁是服务器的区分了。
Java中编写网络通信程序,必须用到java.net包下面的API。创建一个服务器,相当简单。
第一步:在指定端口上创建一个java.net.ServerSocket对象,如下代码:
ServerSocket server=new ServerSocket(9090); System.out.println("服务器创建成功!"+port); |
第二步:服务器创建成功后,就像手机开机后,进入待机状态一样,你也要让服务器进入等待状态,当然是等待其他的客户机来连接:
//在等待客户机连接进入,进入后,生成一个Socket对象 java.net.Socket client=server.accept(); System.out.println("Incoming "+client.getRemoteSocketAddress()); |
注意:调用服务器对象server.accept()方法时,程序就会“阻塞”在这个调用上,或者说“卡”到这里——直到有一个客户机连接上来,这个方法才会返回,返回一个Socket类对象——这个对象就代表了服务器与客户机之间的连接。可以这样理解:当你拔通我的手机时,可以理解为我的机手机中存在着一个“通话对象”,就相当于此的client对象;以后(服务器)与客户机的通信,就在这个Socket类型的对象client上进行。
第三步:从Socket连接对象上调用方法得到输入输出流:
java.net.Socket client=server.accept(); //从连接对象上得到输入流和输出流对象 OutputStream out=client.getOutputStream(); InputStream ins=client.getInputStream(); |
如果这段代码难以理解,请看下图1.10所示和解说。
图1.10 网络通信连接示意图
图1.10所示网络通信过程如下:当你在指定端口创建了一个ServerSocket对象后,就相当于你买了一部手机,且申请到了一个手机号,但此时还不能通话——除排你开机进入待机状态,就如同程序中调用Server对象的accept()方法等待客户机连接进入;当客户机连接进入后,在服务器程序中,即得到一个代表它们之间通话通道的连接对象Socket。
最后从这个Socket上得到输入、输出流对象:当一个电话接入后,你手机中的喇叭就相当于此的输出流对象,输入流对象自然的对应于手机中的话筒。你向输入流中写入的数据,就被发向了客户机,如果你从输出流对象中读取数据,读到的就是客户机发来的数据。
第四步:你使用输入/输出流对象进行通信数据的读写:从输入流中读取数据,向输出流中写入数据。读到的数据,即是客户机发来的数据;写出的数据,就会发送给客户机!发送的代码如下所示:
String s="你好,欢迎来javaKe.com\r\n"; //取得组成这个字符串的字节 byte[] data=s.getBytes(); //用输出对象发送! out.write(data); out.flush();//强制输出 //半闭与客户机的连接 client.close(); |
第五步:最后,将如上步骤完整地集成起来,得到如下代码所示:
/** * 简单服务器实现 * @author www.NetJava.cn */ public class ChatServer { /** * 在指定端口上启动一个服务器 * @param port:服务器所以的端口 */ public void setUpServer(int port){ try{ //建立绑定在指定端口上的服务器对象 ServerSocket server=new ServerSocket(port); System.out.println("服务器创建成功!"+port); //让服务器进入等待状态:阻塞状态 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();//强制输出 client.close();//半闭与客户机的连接 }catch(Exception ef){ ef.printStackTrace(); } } //主函数 public static void main(String[] args) { ChatServer cs=new ChatServer(); cs.setUpServer(9090); } } |
如果以上程序运行成功,你可能会有疑问?客户机在哪里呢?我是怎么做为一个客户机连接上这段代码所实现的服务器打开的9090端口呢?这个简单,还记得上一节所述的telnet吗?物理上,一台计算机,可以在逻辑中的分成客户机和服务器两部分。现在你手前的计算机就是一台服务器,当你进入命令行,执行telnet命令后,它同时又是一台客户机。这样,本地的IP地址可以用”localhost”这个词指代,当然不包括双引号。如图1.11所示,使telnet客户端连接你开发的服务器程序。
图1.11 使用telnet命令连接自已的服务器
回车后,你的telnet客户端应接收并显示服务器发送来的消息。如图1.12所示。
图1.12 telnet客户端接收到服务器发来的消息
当然,你也可以在局域网中其他电脑的命令行中输入“telnet 你的ip 端口”命令,测试接收服务器输出的消息。
以上步骤中,如果运行服务器出错,可能会在控制台看到报错信息:
java.net.BindException: Address already in use: JVM_Bind at java.net.PlainSocketImpl.socketBind(Native Method) at java.net.PlainSocketImpl.bind(Unknown Source) at java.net.ServerSocket.bind(Unknown Source) at java.net.ServerSocket.<init>(Unknown Source) at java.net.ServerSocket.<init>(Unknown Source) at cn.netjava.chatv2.ChatServer.setUpServer(ChatServer.java:20) at cn.netjava.chatv2.ChatServer.main(ChatServer.java:42) |
该引起报错的原因,是设定给服务器的端口已经被其他程序占用了。换一个端口号试试?当然,不要以为端口号是个int型数值就可以,不能小于零或大于65535啦!
如果一切正常,最后,你还是发现了一个极大的缺陷:一个客户机连接上来后,收到服务器发出的一条消息,服务器程序就退出了。如此不稳定,怎担当服务器角色?想要解决这个问题,请进入下一节。