网络通信中的常见错误及解决方案

网络通信常见错误产生原因及解决方案

 

          如今,很多软件为实现某些功能,都会涉及到网络通信的应用(像是这次的远程桌面控制),而在通信的过程中,是会出现各种各样错误的。这些错误虽然形式不同,但其产生原因大都涵盖在以下十种中。

 

        一、IP或者端口错误

        java.net.SocketException: Network is unreachable: connect

      产生原因:建立连接时使用了错误的IP地址或者端口。

        解决方案:在建立连接对象时,明确服务器的IP地址以及通信软件占用的端口。这种属于人为的错误。

 

        二、保持连接时,不同的循环方式的使用

      代码实例:服务器接受客户端连接.

public void SetupServer(int port) {

       try {

           //创建服务器

           java.net.ServerSocket server = new java.net.ServerSocket(port);

           System.out.println("服务器创建成功!!");

 

           while (true) {

              java.net.Socket client = server.accept();

              clientAddr = client.getRemoteSocketAddress().toString();

              System.out.println("有客户机连接"+clientAddr);

              ServerThread st=new ServerThread(client);

              st.start();

           }

 

       } catch (Exception ef) {

           ef.printStackTrace();

           System.out.println("服务器创建失败!!");

       }

    }

以上是一段正确的代码,再看下面这段错误代码。

public void SetupServer(int port) {

       try {

           //创建服务器

           java.net.ServerSocket server = new java.net.ServerSocket(port);

           System.out.println("服务器创建成功!!");

           java.net.Socket client = server.accept();

           clientAddr = client.getRemoteSocketAddress().toString();

           System.out.println("有客户机连接"+clientAddr);

 

           while (true) {

              ServerThread st=new ServerThread(client);

              st.start();

           }

 

       } catch (Exception ef) {

           ef.printStackTrace();

           System.out.println("服务器创建失败!!");

       }

    }

我们发现这两段代码的区别在于while(true){}循环体的不同。两者不同的循环方式导致了不同的结果。

第一段代码能理想的运行、能允许多个客户端连接;第二段代码会导致系统堆溢出、而且只允许一个客户端连接进入。

产生原因:对连接流程理解的偏差。

解决方案:弄清楚服务器端在创建连接对象时的正确流程。按我们的设想,是在每一个客户端连接进入后,都要创建一个连接对象,并启动一个线程去处理这个连接对象。所以,对一段代码就是正确的。

 

三、协议不相符

这种错误可以说是最常见的错误了,而这种错误一旦出现,将会使整个通信过程变得毫无意义。

简单的说,网络通信就是服务器、客户端对 “字节流”的处理操作,“字节流”是在输入输出流对象中读取或写入的。而网络通信的关键就是通信协议的制定,服务器、客户端对“字节流”处理的规则就是通信协议。

可以想象,如果服务器或客户端没有按照协议“办事”(如:少读取了几个字节),那在下次读取的时候,必然会再次犯错,然后就这样一直犯错下去。也就造成了“失之毫厘谬以千里”的后果,整个通信也就没有意义了。

代码实例:

服务器发送一张截屏图片的代码:

if (null != imageData) {

                  // 1 代表这条消息是一张图片

                  dous.writeByte(1);

                  // 图片字节的长度

                  dous.writeInt(imageData.length);

                  // 发送图片数据

                  dous.write(imageData);

                  dous.flush();

                 

                  count++;

                  System.out.println(" "+count+" 张图片发送完毕!!");

客户端读取一张截屏图片的代码:

while (true) {

              byte type = dins.readByte();// 得到消息类型

              switch (type) {

              case 1:// 接收到图片信息

              {

                  int len = dins.readInt();// 图片数据的长度

                  byte[] data = new byte[len];

 

                  // 读取字节数组

                  for (int i = 0; i < len; i++) {

                     data[i] = dins.readByte();

                  }

 

                  count++;

                  System.out.println("第 " + count + " 张图片读取完毕,\r\n长度是: "

                         + len);

         可以看出这个流程是:服务器先发送一个byte代表发送消息的类型(1代表是截屏图片),再发送一个int代表截屏图片的长度,最后发送截屏图片的字节数组。  而在客户端也是先读取一个byte的得到消息的类型(1代表是截屏图片),再读取一个int得到截屏图片的长度,最后再循环读取截屏图片的字节数组。这就是按照协议正确的处理操作。

      发生场景:如果在客户端读到一个byte后,直接就循环读取截屏图片的字节数组,那么就会造成4个字节的偏差。而遗漏的4个字节又会在下次读取时被读到,这样显然得不到正确的结果。

产生原因:服务器或客户端违背了通信协议。

解决方案:要明确的制定好通信协议,并保证服务器、客户端能严格遵守。

 

四、read(byte a[])readFully(byte a[])的区别

因为网络在传送数据时会做一些处理,使服务器发出的消息不是作为一个整体被发送出去的。而客户端在读取时,若采用这两种不同的读取方法,就会有不同的结果。

产生原因:用read(byte a[])读取,可能会造成实际读取到的字节数并没有a.length那么多,从而造成错误。

解决方案:1.可以用循环读取a.length个字节。2.readFully(byte a[])

 

五、异常内存错误

OutOfMemoryError

代码实例:服务器接受客户端连接.

public void SetupServer(int port) {

       try {

           //创建服务器

           java.net.ServerSocket server = new java.net.ServerSocket(port);

           System.out.println("服务器创建成功!!");

           java.net.Socket client = server.accept();

           clientAddr = client.getRemoteSocketAddress().toString();

           System.out.println("有客户机连接"+clientAddr);

 

           while (true) {

              ServerThread st=new ServerThread(client);

              st.start();

           }

 

       } catch (Exception ef) {

           ef.printStackTrace();

           System.out.println("服务器创建失败!!");

       }

    }

产生原因:死循环、或者在不停地创建对象。

解决方案:在while(true){}中,谨慎的创建对象。再者,尽量避免“死循环”。

 

六、EOFException

产生原因:要读取n个字节,输入流中传来了m个字节,n>m。产生这种异常,主要还是在协议的制定上出了问题,发送的字节数和要接受的字节数不等。

解决方案:弄清楚通信流程,制定好协议。

 

七、断开

客户端和服务器的连接终归是要断开的,而断开分为意料中的和意料之外的(像是网线断了)

产生原因:客户端断开,服务器还在发送或等待接收消息。

解决方案:意料中的比较容易避免,可以在客户端准备退出时,先发一条消息通知服务器,让服务器断开连接,停止工作。而意料之外的就比较麻烦了,因为不清楚连接是如何断开的。

 

八、try-catch 放的位置不同

throws异常  还是用try-catch处理异常。

产生原因:try-catch放的位置不同,得到的是不同的异常,如果放错了地方,就会产生错误。

解决方案:分析清楚特定代码会抛出什么异常,在catch里正确的打印出来。

 

九、性能问题(不必要的对象的创建,初始化)

代码实例:服务器端截取屏幕图片的方法

public static byte[] printScreen(){

       try{

           java.awt.Robot robot=new java.awt.Robot();

           Rectangle rt=new Rectangle(0,0,1440,900);

           //取得截屏图片

           BufferedImage image= robot.createScreenCapture(rt);

          

           //改变全屏截图的大小

           AffineTransformOp op = new AffineTransformOp(AffineTransform

                  .getScaleInstance(1.0/2.0, 1.0/2.0), null);

           image = op.filter(image, null);

 

           //转为字节数组

           byte[] imageGet=imageToBytes(image);

           return imageGet;   

       }catch(Exception ef){

           ef.printStackTrace();

           System.out.println("截取屏幕图片失败!!");

       }

       return null;

    }

因为这个方法会被频繁的调用,而每次调用都要创建一个RobotRectangle对象,毫无疑问,这会降低程序的运行效率。

产生原因:创建了很多不必要的对象,或是不必要的初始化,降低了程序的运行效率。

解决方案:尽量少的创建对象和初始化对象。

 

十、对程序的一点改进

代码示例:

//将截屏图片转换成字节数组的方法

    private static byte[] imageToBytes(BufferedImage image) {  

//        BufferedImage bImage = new BufferedImage(image.getWidth(null), image  

//                .getHeight(null), BufferedImage.TYPE_INT_ARGB);  

        Graphics bg = image.getGraphics();  

        bg.drawImage(image, 0, 0, null);  

        bg.dispose();  

 

        ByteArrayOutputStream out = new ByteArrayOutputStream();  

        try {  

            ImageIO.write(image, "jpeg", out);  

        } catch (IOException e) {  

            e.printStackTrace();  

        }  

        return out.toByteArray();  

    }  

这个方法是在服务器发送截屏图片时将截屏图片转换成字节数组,也会被频繁的调用。而之前传入的是一个Image对象,之后还要创建一个BufferedImage对象,这会降低程序的运行效率。

因为BufferedImage继承了Image,所以可以传入一个BufferedImage对象来解决上述问题。

 

 

     

 

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值