【Java】-- 网络编程のNo.2

网络通讯的方式除了TCP方式以外,还有一种实现的方式就是UDP方式

UDP(User Datagram Protocol),中文意思是用户数据报协议,方式类似于发短信息,是一种物美价廉的通讯方式,使用该种方式无需建立专用的虚拟连接,由于无需建立专用的连接,所以对于服务器的压力要比TCP小很多,所以也是一种常见的网络编程方式。

但是使用该种方式最大的不足是传输不可靠,当然也不是说经常丢失,就像大家发短信息一样,理论上存在收不到的可能,这种可能性可能是1%,反正比较小,但是由于这种可能的存在,所以平时我们都觉得重要的事情还是打个电话吧(类似TCP方式),一般的事情才发短信息(类似UDP方式)

So 网络编程中也是这样,必须要求可靠传输的信息一般使用TCP方式实现,一般的数据才使用UDP方式实现。

 UDP方式的网络编程也在Java语言中获得了良好的支持,由于其在传输数据的过程中不需要建立专用的连接等特点,所以在Java API中设计的实现结构和TCP方式不太一样。当然,需要使用的类还是包含在java.net包中。


Java API中,实现UDP方式的编程,包含客户端网络编程和服务器端网络编程,主要由两个类实现,分别是:

  • DatagramSocket
     DatagramSocket类实现网络连接,包括客户端网络连接和服务器端网络连接。虽UDP方式的网络通讯不需要建立专用的网络连接,但是毕竟还是需要发送和接收数据,DatagramSocket实现的就是发送数据时的发射器,以及接收数据时的监听器的角色。类比于TCP中的网络连接,该类既可以用于实现客户端连接,也可以用于实现服务器端连接。
  • DatagramPacket

     DatagramPacket类实现对于网络中传输的数据封装,也就是说,该类的对象代表网络中交换的数据。UDP方式的网络编程中,无论是需要发送的数据还是需要接收的数据,都必须被处理成DatagramPacket类型的对象,该对象中包含发送到的地址、发送到的端口号以及发送的内容等。

    TCP方式的网络传输相比,IO编程在UDP方式的网络编程中变得不是必须的内容,结构也要比TCP方式的网络编程简单一些。

--------------

下面介绍一下UDP方式的网络编程中,客户端和服务器端的实现步骤,以及通过基础的示例演示UDP方式的网络编程在Java语言中的实现方式。

UDP方式的网络编程,编程的步骤和TCP方式类似,只是使用的类和方法存在比较大的区别,下面首先介绍一下UDP方式的网络编程客户端实现过程。

UDP客户端编程涉及的步骤也是4个部分:建立连接、发送数据、接收数据和关闭连接。

  1. // 建立了一个客户端连接, 
  2. // 该客户端连接使用系统随机分配的一个本地计算机的未用端口号 
  3. DatagramSocket ds = new DatagramSocket(); 

在该连接中,不指定服务器端的IP和端口,所以UDP方式的网络连接更像一个发射器,而不是一个具体的连接。

可以通过制定连接使用的端口号来创建客户端连接。

  1. // 使用本地计算机的5000号端口建立了一个连接。 
  2. // 一般在建立客户端连接时没有必要指定端口号码。 
  3. DatagramSocket ds = new DatagramSocket(5000); 

----------------

UDP客户端编程中发送数据的实现。UDP方式的网络编程中,IO技术不是必须的,在发送数据时,需要将需要发送的数据内容首先转换为byte数组,然后将数据内容、服务器IP和服务器端口号一起构造成一个DatagramPacket类型的对象,这样数据的准备就完成了,发送时调用网络连接对象中的send方法发送该对象即可。

接下来查看一个例子,将数据打包并通过udp方式发送

  1. String msg = "Message"
  2. String ip = "localhost"
  3. int port = 5000
  4.  
  5. DatagramSocket ds = new DatagramSocket(); 
  6.  
  7. // 将发送内容转换为byte数组 
  8. byte[] b = msg.getBytes(); 
  9.  
  10. // 将ip转换为Inetaddress对象 
  11. InetAddress server = InetAddress.getByName(ip); 
  12.  
  13. //构造发送的数据包对象 
  14. DatagramPacket datagramPacket = new DatagramPacket(b, b.length,server, port); 
  15.  
  16. //发送数据 
  17. ds.send(datagramPacket); 

     在该示例代码中,不管发送的数据内容是什么,都需要转换为byte数组,然后将服务器端的IP地址构造成InetAddress类型的对象,在准备完成以后,将这些信息构造成一个DatagramPacket类型的对象,在UDP编程中,发送的数据内容、服务器端的IP和端口号,都包含在DatagramPacket对象中。在准备完成以后,调用连接对象dssend方法把DatagramPacket对象发送出去即可。   

  按照UDP协议的约定,在进行数据传输时,系统只是尽全力传输数据,但是并不保证数据一定被正确传输,如果数据在传输过程中丢失,那就丢失了。

  UDP方式在进行网络通讯时,也遵循“请求-响应”模型,在发送数据完成以后,就可以接收服务器端的反馈数据了。

--------------------

  下面介绍一下UDP客户端编程中接收数据的实现。当数据发送出去以后,就可以接收服务器端的反馈信息了。

  接收数据在Java语言中的实现是这样的:首先构造一个数据缓冲数组,该数组用于存储接收的服务器端反馈数据,该数组的长度必须大于或等于服务器端反馈的实际有效数据的长度。然后以该缓冲数组为基础构造一个DatagramPacket数据包对象,最后调用连接对象的receive方法接收数据即可。接收到的服务器端反馈数据存储在DatagramPacket类型的对象内部。实现接收数据以及显示服务器端反馈内容的示例代码如下:

  1. DatagramSocket ds = null
  2.         try { 
  3.             //建立一个链接监听9898端口 
  4.             ds = new DatagramSocket(9898); 
  5.  
  6.             // 构造缓冲器 
  7.             byte buf[] = new byte[1024]; 
  8.             // 构造数据包对象 
  9.             DatagramPacket datagramPacket = new DatagramPacket(buf, buf.length); 
  10.  
  11.             // 接收数据 
  12.             ds.receive(datagramPacket); 
  13.             // 输出数据内容 
  14.             byte b[] = datagramPacket.getData(); 
  15.             int n = datagramPacket.getLength(); 
  16.             String s = new String(b, 0, n); 
  17.             System.out.println(s); 
  18.         } catch (SocketException e) { 
  19.             // TODO Auto-generated catch block 
  20.             e.printStackTrace(); 
  21.         } catch (IOException e) { 
  22.             // TODO Auto-generated catch block 
  23.             e.printStackTrace(); 
  24.         } finally { 
  25.             ds.close(); 
  26.         } 

 UDP方式客户端网络编程的最后一个步骤就是关闭连接。虽然UDP方式不建立专用的虚拟连接,但是连接对象还是需要占用系统资源,所以在使用完成以后必须关闭连接。关闭连接使用连接对象中的close方法即可

----------------------

介绍完了UDP方式客户端网络编程的基础知识以后,下面再来介绍一下UDP方式服务器端网络编程的基础知识。

UDP方式网络编程的服务器端实现和TCP方式的服务器端实现类似,也是服务器端监听某个端口,然后获得数据包,进行逻辑处理以后将处理以后的结果反馈给客户端,最后关闭网络连接

首先UDP方式服务器端网络编程需要建立一个连接,该连接监听某个端口

  1. DatagramSocket ds = new DatagramSocket(9898); 

由于服务器端的端口需要固定,所以一般在建立服务器端连接时,都指定端口号

接着服务器端就开始接收客户端发送过来的数据,其接收的方法和客户端接收的方法一直,其中receive方法的作用类似于TCP方式中accept方法的作用,该方法也是一个阻塞方法,其作用是接收数据。

接收到客户端发送过来的数据以后,服务器端对该数据进行逻辑处理,然后将处理以后的结果再发送给客户端,在这里发送时就比客户端要麻烦一些,因为服务器端需要获得客户端的IP和客户端使用的端口号,这个都可以从接收到的数据包中获得。

  1. //获得客户端的IP 
  2. InetAddress clientIP = receiveDp.getAddress(); 
  3. //获得客户端的端口号 
  4. Int clientPort = receiveDp.getPort(); 

使用以上代码,就可以从接收到的数据包对象receiveDp中获得客户端的IP地址和客户端的端口号,这样就可以在服务器端中将处理以后的数据构造成数据包对象,然后将处理以后的数据内容反馈给客户端了。

最后,当服务器端实现完成以后,关闭服务器端连接,实现的方式为调用连接对象的close方法

-------------------------

 介绍完了UDP方式下的客户端编程和服务器端编程的基础知识以后,下面通过一个简单的示例演示UDP网络编程的基本使用。该示例的功能是实现将客户端程序的系统时间发送给服务器端,服务器端接收到时间以后,向客户端反馈客户端发送的字符串 

  1. public static void main(String[] args) { 
  2.         DatagramSocket ds = null
  3.         try { 
  4.  
  5.             String ip = "localhost"
  6.             int port = 5000
  7.  
  8.             DatagramPacket senddp = null
  9.             DatagramPacket receivedp = null
  10.  
  11.             ds = new DatagramSocket(); 
  12.  
  13.             Date date = new Date(); 
  14.  
  15.             // 将发送内容转换为byte数组 
  16.             byte[] b = date.toString().getBytes(); 
  17.  
  18.             // 将ip转换为Inetaddress对象 
  19.             InetAddress server = InetAddress.getByName(ip); 
  20.  
  21.             // 构造发送的数据包对象 
  22.             senddp = new DatagramPacket(b, b.length, server, port); 
  23.  
  24.             // 发送数据 
  25.             ds.send(senddp); 
  26.  
  27.              
  28.             //接收服务器数据 
  29.             byte buf[] = new byte[1024]; 
  30.             receivedp = new DatagramPacket(buf, buf.length); 
  31.             ds.receive(receivedp); 
  32.             byte msg[] = receivedp.getData(); 
  33.             int n = receivedp.getLength(); 
  34.             System.out.println("服务器返回的数据:" + new String(msg, 0, n)); 
  35.  
  36.         } catch (SocketException e) { 
  37.             // TODO Auto-generated catch block 
  38.             e.printStackTrace(); 
  39.         } catch (UnknownHostException e) { 
  40.             // TODO Auto-generated catch block 
  41.             e.printStackTrace(); 
  42.         } catch (IOException e) { 
  43.             // TODO Auto-generated catch block 
  44.             e.printStackTrace(); 
  45.         } finally { 
  46.             ds.close(); 
  47.         } 
  48.     } 

在该示例代码中,首先建立UDP方式的网络连接,然后获得当前系统时间,这里获得的系统时间是客户端程序运行的本地计算机的时间,然后将时间字符串以及服务器端的IP和端口,构造成发送数据包对象,调用连接对象dssend方法发送出去。 

在数据发送出去以后,构造接收数据的数据包对象,调用连接对象dsreceive方法接收服务器端的反馈,并输出在控制台。最后在finally语句块中关闭客户端网络连接。

下面是该示例程序的服务器端代码实现:

  1. public static void main(String[] args) { 
  2.  
  3.         DatagramSocket ds = null
  4.         try { 
  5.             ds = new DatagramSocket(5000); 
  6.  
  7.             // 接收客户端的数据 
  8.             byte buf[] = new byte[1024]; 
  9.             DatagramPacket receivedp = new DatagramPacket(buf, buf.length); 
  10.  
  11.             ds.receive(receivedp); 
  12.             byte b[] = receivedp.getData(); 
  13.             InetAddress ip = receivedp.getAddress(); 
  14.             int port = receivedp.getPort(); 
  15.  
  16.             String msg = new String(b, 0, b.length); 
  17.             // System.out.println("客户端发送的内容:"+msg); 
  18.  
  19.             DatagramPacket senddp = new DatagramPacket(msg.getBytes(), 
  20.                     msg.getBytes().length, ip, port); 
  21.             ds.send(senddp); 
  22.  
  23.         } catch (SocketException e) { 
  24.             // TODO Auto-generated catch block 
  25.             e.printStackTrace(); 
  26.         } catch (IOException e) { 
  27.             // TODO Auto-generated catch block 
  28.             e.printStackTrace(); 
  29.         } finally { 
  30.             ds.close(); 
  31.         } 
  32.     } 

在该服务器端实现中,首先监听5000号端口,和TCP方式的网络编程类似,服务器端的receive方法是阻塞方法,如果客户端不发送数据,则程序会在该方法处阻塞。当客户端发送数据到达服务器端时,则接收客户端发送过来的数据,然后将客户端发送的数据内容读取出来,并在服务器端程序中打印客户端的相关信息,从客户端发送过来的数据包中可以读取出客户端的IP以及客户端端口号,将反馈数据字符串发送给客户端,最后关闭服务器端连接,释放占用的系统资源,完成程序功能示例。

 UDP方式的网络编程由于不建立虚拟的连接,所以在实际使用时和TCP方式存在很多的不同,最大的一个不同就是“无状态”。该特点指每次服务器端都收到信息,但是这些信息和连接无关,换句话说,也就是服务器端只是从信息是无法识别出是谁发送的,这样就要求发送信息时的内容需要多一些。

下面是实现客户端多次发送以及服务器端支持多个数据包同时处理的程序结构,实现的原理和TCP方式类似,在客户端将数据的发送和接收放入循环中,而服务器端则将接收到的每个数据包启动一个专门的线程进行处理。

客户端代码:循环30次,将时间发送到服务器

  1. public class Client { 
  2.     public static void main(String[] args) { 
  3.         DatagramSocket ds = null
  4.         // 发送数据包 
  5.         DatagramPacket sendPackage; 
  6.         // ip地址,犹豫服务器在本地 所以用localhost 
  7.         String ip = "localhost"
  8.         // 端口 
  9.         int port = 15022
  10.         try { 
  11.  
  12.             // 建立连接 
  13.             ds = new DatagramSocket(); 
  14.  
  15.             InetAddress addressIP = InetAddress.getByName(ip); 
  16.  
  17.             for (int i = 0; i < 30; i++) { 
  18.                 //发送 
  19.                 Date date = new Date(); 
  20.                 byte dt[] = date.toString().getBytes(); 
  21.                 sendPackage = new DatagramPacket(dt, dt.length, addressIP, port); 
  22.                 ds.send(sendPackage); 
  23.  
  24.                 // 延迟 
  25.                 Thread.sleep(10); 
  26.             } 
  27.         } catch (Exception e) { 
  28.             // TODO: handle exception 
  29.             e.printStackTrace(); 
  30.         } 
  31.     } 

服务端代码:接收数据包,开启线程,在线程中处理接收到的内容,并回传给客户端

  1. public class Server { 
  2.  
  3.     public static void main(String[] args) { 
  4.          
  5.         DatagramSocket ds = null
  6.         DatagramPacket receivePackage; 
  7.         int port = 15022
  8.         byte buf[] = new byte[1024]; 
  9.         receivePackage = new DatagramPacket(buf, buf.length); 
  10.         try { 
  11.             // 建立连接 
  12.             ds = new DatagramSocket(port); 
  13.  
  14.             while (true) { 
  15.                 ds.receive(receivePackage); 
  16.                 Server server= new Server(); 
  17.                 server.new LogicThread(ds, receivePackage); 
  18.  
  19.             } 
  20.  
  21.         } catch (Exception e) { 
  22.             // TODO: handle exception 
  23.             e.printStackTrace(); 
  24.         } 
  25.     } 
  26.  
  27.     class LogicThread extends Thread { 
  28.         DatagramSocket ds = null
  29.         DatagramPacket receivePackage; 
  30.  
  31.         public LogicThread(DatagramSocket ds, DatagramPacket receivePackage) { 
  32.             this.ds = ds; 
  33.             this.receivePackage = receivePackage; 
  34.             start(); 
  35.         } 
  36.  
  37.         @Override 
  38.         public void run() { 
  39.             try { 
  40.  
  41.                 byte b[] = receivePackage.getData(); 
  42.                 int n = receivePackage.getLength(); 
  43.                 String s = new String(b, 0, n); 
  44.                 System.out.println(s); 
  45.  
  46.                 InetAddress addressIP = receivePackage.getAddress(); 
  47.                 int port = receivePackage.getPort(); 
  48.  
  49.                 DatagramPacket sendPackage = new DatagramPacket(b, b.length, 
  50.                         addressIP, port); 
  51.                 ds.send(sendPackage); 
  52.             } catch (Exception e) { 
  53.                 // TODO: handle exception 
  54.                 e.printStackTrace(); 
  55.             } 
  56.         } 
  57.     } 

上面的服务器端可以用来接收多个客户端发来的数据,但是客户端没有写接收的代码  所以在客户端不能打印数据

接下来改造下客户端的代码  让它实现接收来自服务器端的数据

  1. public class Client { 
  2.     public static void main(String[] args) { 
  3.         DatagramSocket ds = null
  4.         // 发送数据包 
  5.         DatagramPacket sendPackage; 
  6.         // 接收数据包 
  7.         DatagramPacket receivePackage; 
  8.         // ip地址,犹豫服务器在本地 所以用localhost 
  9.         String ip = "localhost"
  10.         // 端口 
  11.         int port = 15022
  12.         try { 
  13.  
  14.             // 建立连接 
  15.             ds = new DatagramSocket(); 
  16.  
  17.             InetAddress addressIP = InetAddress.getByName(ip); 
  18.  
  19.             byte[] buf = new byte[1024]; 
  20.             receivePackage = new DatagramPacket(buf, buf.length); 
  21.  
  22.             for (int i = 0; i < 30; i++) { 
  23.                 //发送 
  24.                 Date date = new Date(); 
  25.                 byte dt[] = date.toString().getBytes(); 
  26.                 sendPackage = new DatagramPacket(dt, dt.length, addressIP, port); 
  27.                 ds.send(sendPackage); 
  28.  
  29.             } 
  30.             while(true){ 
  31.                 //接收 
  32.                 ds.receive(receivePackage); 
  33.                 new Client().new LogicThread(receivePackage); 
  34.             } 
  35.         } catch (Exception e) { 
  36.             // TODO: handle exception 
  37.             e.printStackTrace(); 
  38.         } 
  39.     } 
  40.  
  41.     class LogicThread extends Thread { 
  42.         DatagramPacket receivePackage; 
  43.  
  44.         public LogicThread(DatagramPacket receivePackage) { 
  45.             this.receivePackage = receivePackage; 
  46.             start(); 
  47.         } 
  48.  
  49.         @Override 
  50.         public void run() { 
  51.             byte reb[] = receivePackage.getData(); 
  52.             int n = receivePackage.getLength(); 
  53.             String s = new String(reb, 0, n); 
  54.  
  55.             System.out.println("服务器返回:" + s); 
  56.         } 
  57.     } 

 

 这样子就可以通过客户端发送数据到服务器端 ,然后返回到客户端输出

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章