3.服务器端读写消息实现

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

    服务器不能只连接一个客户机就退出——你可以将ServerSocketaccept()方法放在一个循环中调用:进行一个客户机,当服务器与这个客户机通信完毕后,服务器就再次进入循环中,重新调用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 二进制是: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所示。
3.服务器端读写消息实现_第1张图片
 

可以看到,在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所示。

 3.服务器端读写消息实现_第2张图片
 

4.网络数据传送总结。

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

 
3.服务器端读写消息实现_第3张图片
 

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

 

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

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

 

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

3.服务器端读写消息实现_第4张图片 

 

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

 

你可能感兴趣的:(多线程,.net,socket,网络协议)