网络编程-概念及UDP、TCP连接


        在掌握了基本的程序编写技巧后,我们来看一下Java网络编程。在这部分总结之前,我们首先需要掌握一些计算机网络的学科知识,这里给出了部分主要内容的概述:

        端口:为进程间通信提供必要标识的数据标识,我们称之为端口。它是计算机程序为了区分不同进程而创立的区别方式。因此,端口号只具有本地意义,对应主机间通讯,在某种情况下必须指明对应接受传输数据端口号。

        端口号分为两种:服务端使用端口号、客户端使用端口号。

        服务端使用端口号又分为:熟知端口号、登记端口号。

        熟知端口号:0~1023,IANA,即互联网地址指派机构,将此类端口派给了TCP/IP协议的一些重要程序。

        登记端口号:1024~49151,此类端口号为一些没有熟知端口号的应用程序使用。想要使用登记端口号,则必须在IANA登记才可使用。

客户端使用端口号:又被称为短暂端口号,因为此类端口号只在客户进程运行时才会动态的选择调用。当用户进程结束时,此类端口则会自动释放。便于其他进程使用。此类端口号并没有具体使用要求,一般在程序运行时,都会由系统分配,并回收。

        端口的存在,另一点就是为直达进程的网际进程间通信,打下了条件。

        除了端口之外,在网际数据传输上,我们根据实际情况将计算机网络体系结构分为一下两种模型。

网络编程-概念及UDP、TCP连接

图8.计算机网络体系结构图

 

        本质上,这两种模型都是对同一种事物,即网络,的不同描述。我们这里的Java程序,主要运用到的是TCP/IP中传输层、网际层,这两层的协议。如果是Java-Web开发,则会涉及应用层的协议。具体协议划分、数据封装,以及报文、报文段、分组、帧、数据报,这些内容,如果想要详细了解,则看计算机网络对应教材,此处不再赘述。

        想要在Java编程中使用到网络编程,则需要我们引入java.NET包。java.Net包实现为了实现对网络通讯的三种操作:地址控制、端口控制、规则控制。其中,端口因为不需要太多操作,且完全由数字及IANA规定了使用规则,Java中就只需要实现地址控制和规则操作了。

 

InetAddress系列IP地址类

        java.net包中,用来描述IP地址的是InetAddress类和他的两个字累Inte4Address与Inet6Address。IP地址类主要被我们用于对IP地址信息的Java操作。常用来获取指定主机或IP地址的网络信息。如例:

[java] view plain copy
  1. import java.net.*;  
  2.    
  3. class  MyIPTest  
  4. {  
  5.        public static void main(String[] args)  
  6.        {  
  7.               //本地主机地址获取  
  8.               InetAddress i =InetAddress.getLocalHost();  
  9.               System.out.println(i.toString());  
  10.               System.out.println("name:"+i.getHostName());  
  11.               System.out.println("address:"+i.getHostAddress());  
  12.    
  13.               //获取目标IP对应主机地址信息  
  14.               InetAddress i2 =InetAddress.getByName("8.8.8.8");  
  15.               System.out.println("name:"+i.getHostName());  
  16.               System.out.println("address:"+i.getHostAddress());  
  17.    
  18.               //获取目标主机名所对应的一系列IP地址  
  19.               InetAddress[] i3 =InetAddress.getAllByName("www.google.com");  
  20.               for(int n = 0; n<i3.length();n++)  
  21.               {  
  22.                      System.out.println("name:"+i3[n].getHostName());  
  23.                      System.out.println("address:"+i[n].getHostAddress());  
  24.               }  
  25.    
  26.        }  
  27. }  

        该例就是对获取主机地址信息常用方法的一个类比应用。可以从其结果和参数上,看出各个方法的着重点。

 

        java.net包中,用于描述传输规则的类分为两种类型方向,UDP和TCP。

        UDP:不可靠,即时性,面向无连接,每隔数据报大小限制在64K之内。

        TCP:“三次握手、四次挥手”式连接,可靠,效率低、非即时,连接形成传输通道。

        所以,即时通讯常用UDP,以保证即时性。但是如一些下载文件之类的通信,常用TCP以保证完整性。

        这两个网络规则的实现,避免不了要涉及到文件的传输和对应进程的定位。实现这种操作的基础就是Socket套接字服务。Socket套接字,计算机间通过套接字来实现端对端通信。简易定义Socket=(IP地址,端口号)。

        我们首先从UDP入手。

 

UDP服务

        UDP的套接字类包括:DatagramSocket和DatagramPacket。UDP的收发服务及数据传输,在Java中就是由这两个类来实现的。我们通过实例来说明具体步骤。

        文件发送端步骤演示:

[java] view plain copy
  1. import java.net.*;  
  2.    
  3. class  UDP_sendDemo  
  4. {  
  5.        public static void main(String[] args)  
  6.        {  
  7.               //1.通过DatagramSocket创立UDP服务  
  8.               DatagramSocket UDPs = newDatagramSocket();  
  9.    
  10.               //2.确定传输数据,将其封入DatagramPacket对象中,进行传输前准备  
  11.               byte[] buf = "This is an UDPpacket.".getBytes();  
  12.               DatagramPacket UDPp =  
  13.               newDatagramPacket(buf,buf.length,InetAddress.getByName("127.0.0.1"),7070);  
  14.    
  15.               //3.通过开启的UDP服务,将数据发给指定接收方  
  16.               UDPs.send(UDPp);  
  17.    
  18.               //4.发送结束,关闭服务  
  19.               UDPs.close();  
  20.        }  
  21. }  
  22. /*当然,有发送就得有接受,没有给定接收方,这样的发送必然发生丢包。 
  23. */  

        文件接收端步骤演示:

[java] view plain copy
  1. import java.net.*;  
  2.    
  3. class  UDP_receiveDemo  
  4. {  
  5.        public static void main(String[] args)  
  6.        {  
  7.               //1.通过DatagramSocket创立UDP接收端点,并指定监听端口  
  8.               DatagramSocket UDPs = newDatagramSocket(7070);  
  9.    
  10.               //2.定义本地DatagramPacket对象用于存储接收数据,便于用其类封装的方法  
  11.               byte[] buf = new byte[1024];  
  12.               DatagramPacket UDPp =  
  13.                      newDatagramPacket(buf,buf.length);  
  14.    
  15.               //3.通过UDP服务,接收数据  
  16.               UDPs.receive(UDPp);  
  17.    
  18.               //4.通过DatagramPacket类封装方法获取数据  
  19.               String ip =UDPp.getAddress().getHostAddress();  
  20.               String res =UDPp.getAddress().getHostName();  
  21.               String data = newString(UDPp.getData(),0,UDPp.getLength());  
  22.                
  23.               int port = UDPp.getPort();  
  24.                                    //此处获取的端口号为发送端定义的端口号,该号由发送端决定  
  25.    
  26.               System.out.println(res+"::"+ip+"::"+port+"::"+data);  
  27.    
  28.               //5.接收结束,关闭服务(可选操作)  
  29.               UDPs.close();  
  30.        }  
  31. }  
  32. /*在定义接收端的时候,一般会为接收端指定一个和发送端端口对应的端口。用于分辨 
  33.   对应接收信息。receive方法是一个阻塞式方法,如果没有接收到所求数据,则程序 
  34.   在该位置被阻塞。直到获取所需信息。 
  35.   另一个需要注意的是,如果没有指定端口号,不论是发送端还是接收端,系统都会为 
  36.   未指定端口的进程分配一个系统端口。该端口号,数字上是随程序顺序递增的。通常, 
  37.   为我们为了保证程序收发的正常进行,会为接收端指定端口号。我们也可用一样的方 
  38.   法为发送端指定端口号。 
  39. */  

        当然,想要发送成功,就如上面两例演示所展示,必须得先开接收端。有了接收端,发送端的数据才不会丢包。

        实际生活中,接收端一般都是24小时运行的。一般情况下,作为接收端,我们会将它内部接受信号部分(即除去UDP服务开启部分),放入一个while条件循环当中。这样是为了保证接收端能够不断的接收到客户端信息。

 


TCP服务

        TCP的套接字类包括:Socket和SeverSocket。从字面上不难看出TCP的类是以客户端、服务端进行分类的。客户端和服务端作为两个端点,只要建立好并完成连接,就可以通讯实现数据传输。

网络编程-概念及UDP、TCP连接

图9.TCP客户端与服务端数据传输示意图

 

        我们通过实例来说明上图中的客户端与服务端的信息传递具体步骤。下面这则实例,我们描述了从客户端向服务端发送数据的过程。

        客户端文件发送步骤演示:

[java] view plain copy
  1. import java.net.*;  
  2. import java.io.*;  
  3.    
  4. class  TCP_clientDemo  
  5. {  
  6.        public static void main(String[] args)  
  7.        {  
  8.               //1.建立客户端socket服务,指定服务端IP和对应端口  
  9.               Socket client = newSocket("8.8.8.8",9920);  
  10.    
  11.               //2.建立输出流,发送需求数据  
  12.               OutputStream out =client.getOutputStream();  
  13.               out.write("This is a TCPdata".getBytes());  
  14.    
  15.               //3.任务完成,客户端关闭  
  16.               client.close();  
  17.    
  18.        }  
  19. }  

        服务端文件接收步骤演示:

[java] view plain copy
  1. import java.net.*;  
  2. import java.in.*;  
  3.    
  4. class  TCP_serverDemo  
  5. {  
  6.        public static void main(String[] args)  
  7.        {  
  8.               //1.建立服务端socket服务,并设立监听端口  
  9.               ServerSocket server = newServerSocket(9220);  
  10.    
  11.               //2.通过服务端SeverSocket类中的accept方法,获取客户端对象  
  12.               Socket sc = server.accept();  
  13.    
  14.               //3.建立输入流,读取客户端发送信息  
  15.               {  
  16.                      InputStream in =sc.getInputStream();  
  17.    
  18.                      byte[] buf = newbyte[1024];  
  19.                      int length = in.read(buf);  
  20.                      String s = newString(buf,0,length);  
  21.    
  22.                      System.out.println(s);  
  23.               }  
  24.               //4.接收结束,关闭客户端  
  25.               sc.close();  //服务端关闭可规定在一定时间之后。  
  26.    
  27.               //5.关闭服务端(可选操作)  
  28.               server.close();  
  29.    
  30.        }  
  31. }  


        上例中,我们只演示了从客户端向服务端发送信息的情况。接下来为我们来演示一下,当服务端接受到请求后,向客户端发送数据的过程。

        TCP客户端代码:

[java] view plain copy
  1. import java.net.*;  
  2. import java.io.*;  
  3.    
  4. class  TCP_client_receiveDemo  
  5. {  
  6.        public static void main(String[] args)  
  7.        {  
  8.               Socket client = newSocket("8.8.8.8",9980);  
  9.    
  10.               OutputStream out =client.getOutputStream();  
  11.               out.write("This is aboutgeting answers.".getBytes());  
  12.    
  13.               //关闭客户端输出流:  
  14.               client.shutdownOutput();  
  15.    
  16.               //客户端接受服务端文件:  
  17.               InputStream in =client.getInputStream();  
  18.               byte[] buf = new byte[1024];  
  19.               int len = in.read(buf);  
  20.    
  21.               System.out.println("client_request::"+newString(buf,0,len));  
  22.    
  23.               client.close();  
  24.        }  
  25. }  

        TCP服务端代码:

[java] view plain copy
  1. import java.net.*;  
  2. import java.io.*;  
  3.    
  4. class  TCP_server_sendDemo  
  5. {  
  6.        public static void main(String[] args)  
  7.        {  
  8.               ServerSocket server = newServerSocket(9980);  
  9.    
  10.               Socket s = server.accept();  
  11.    
  12.               String ip =s.getInetAddress().getHostAddress();  
  13.               InputStream in =s.getInputStream();  
  14.               byte[] buf = new byte[1024];  
  15.               int len = in.read(buf);  
  16.    
  17.               System.out.println(ip+"::"+newString(buf,0,len));  
  18.    
  19.               //服务端发送文件给客户端:  
  20.               OutputStream out =s.getOutputStream();  
  21.               out.write("Request_Datapackage".getBytes());  
  22.                
  23.               s.close();  
  24.        }  
  25. }  

        从上面的TCP实例中,我们不难看出。实际应用中TCP连接所对应服务端,也可以主动关闭客户端和自身之间的连接。而实际应用中,我们的确加入了时间判定用于关闭超时的无用连接。这样的特征是和TCP“三次握手、四次挥手”的特点相对应的。

        需要注意的是,TCP是一种面向连接的服务。这就决定了该服务的服务端和客户端是有先后顺序的。服务端早于客户端启动。服务端在前,客户端在后。且,当一方数据传输结束后,我们需要关闭输出流。使用对应Socket对象来调用shutdownOutput()方法来实现。

        当然,我们也可以通过这种方式来建立服务器,用浏览器作为客户端访问。Tomcat服务器,就是这样的一种封装了SeverSocket的Java服务器软件,它具体的实现过程都是一样的。Windows当中也提供了telnet这个CMD命令,来实现命令控制台直连服务器,这种访问形式。题外话,不难想到,Windows这样的命令也间接的反映了它安全性的问题。

        我们访问网页的时候常常要用到URL统一标示语言,来定位访问的网站的IP、端口,和位置路径。在没有封装URL类对象之前,我们常用下面这种手动方法,来进行分割提取信息。这也是URL类封装后的信息提取原理。

[java] view plain copy
  1. String url ="http://192.168.1.1:8080/Requto/default.html"  
  2. int i1 = url.indexOf("//")+2;  
  3. int i2 =url.indexOf("/",i1);  
  4.    
  5. String str =url.substring(i1,i2);  
  6. String[] buf =str.split(":");  
  7. String host = buf[0];  
  8. int port =Integer.parseInt(buf[1]);  
  9. String path =url.substring(i2);  

        当人们发现这样做不利于程序封装,进而重新设立了URL类,来统一管理此类数据后。我们的网络地址数据提取,就变得容易了许多。通常情况下,我们常用的方法有:

        String getFile():获取本URL的文件名;

        String getHost():获取本URL的主机名;

        String getPort():获取本URL的端口号;

        String getPath():获取本URL的路径部分;

        String getQuery():获取本URL的查询部;

        String getProtocol():获取本URL的协议名;

        URLConnection  openConnection():返回本URL的一个远程对象的连接引用;

        这些方法,能够帮助我们从URL对象中,获取我们所需的信息。要注意的是URLConnection类,本类将原本连接及一系列相关操作、信息,通过封装而将其升高到了应用层的层面上。这样做,简化了对应网络端对端连接的操作。由于Socket在URLConnection内部封装,URLConnection对象,不需要在结束时关闭本身。关闭操作由内部自动完成。同时,由于URLConnection是应用层类,它要求并实现了本身在输出数据的时候,会自动拆包。

        除了openConnection方法外,URLConnection还提供了openStream方法。该方法能够直接返回输出流,其本身实际上是openConnection和getInputStream两者的组合。先调用方法openConnection,再调用方法getInputStream。