`
randy.chen
  • 浏览: 8598 次
  • 性别: Icon_minigender_1
  • 来自: 上海
最近访客 更多访客>>
社区版块
存档分类
最新评论

java网络编程 UDP编程 Socket编程

    博客分类:
  • NIO
阅读更多

8.1 网络编程的基本概念,TCP/IP协议简介
 8.1.1 网络基础知识
  计算机网络形式多样,内容繁杂。网络上的 计算机要互相通信,必须遵循一定的协议。目前使用最广泛的网络协议是Internet上所使用的TCP/IP协议。
  网络编程的目的就是指直接或间接地通过网络 协议与其他计算机进行通讯。网络编程中有两个主要的问题,一个是如何准确的定位网络上一台或多台主机,另一个就是找到主机后如何可靠高效的进行数据传输。 在TCP/IP协议中IP层主要负责网络主机的定位,数据传输的路由,由IP地址可以唯一地确定Internet上的一台主机。而TCP层则提供面向应用 的可靠的或非可靠的数据传输机制,这是网络编程的主要对象,一般不需要关心IP层是如何处理数据的。
 
  目前较为流行的网络编程模型是 客户机/服务器(C/S)结构。即通信双方一方作为服务器等待客户提出请求并予以响应。客户则在需要服务时向服务器提出申请。服务器一般作为守护进程始终 运行,监听网络端口,一旦有客户请求,就会启动一个服务进程来响应该客户,同时自己继续监听服务端口,使后来的客户也能及时得到服务。 
 
8.1.2 网络基本概念
  IP地址:标识计算机等网络设备的网络地址,由四个8位的二进制数组成,中间以小数点分隔。
     如:166.111.136.3 , 166.111.52.80
  主机名(hostname):网络地址的助记名,按照域名进行分级管理。
     如:
www.tsinghua.edu.cn
       www.fanso.com
  
  端口号(port number):网络通信时同一机器上的不同进程的标识。
     如:80,21,23,25,其中1~1024为系统保留的端口号
  
  服务类型(service):网络的各种服务。
     http, telnet, ftp, smtp
 
  我们可以用以下的一幅图来描述这里我们所提到的几个概念:
 
  在Internet上IP地址和主机名是 一一对应的,通过域名解析可以由主机名得到机器的IP,由于机器名更接近自然语言,容易记忆,所以使用比IP地址广泛,但是对机器而言只有IP地址才是有 效的标识符。
  通常一台主机上总是有很多个进程需要网络资源进行网络通讯。网络通讯的对象准确的讲不是主机,而应该是主机中运行的进程。这时候光 有主机名或IP地址来标识这么多个进程显然是不够的。端口号就是为了在一台主机上提供更多的网络资源而采取得一种手段,也是TCP层提供的一种机制。只有 通过主机名或IP地址和端口号的组合才能唯一的确定网络通讯中的对象:进程。
服务类型是在TCP层上面的应用层的概念。基于TCP/IP协议可以构建出各种 复杂的应用,服务类型是那些已经被标准化了的应用,一般都是网络服务器(软件)。读者可以编写自己的基于网络的服务器,但都不能被称作标准的服务类型。
 
 8.1.3 两类传输协议:TCP;UDP
  尽管TCP/IP协议的名称中只有TCP这个协议名,但是在TCP/IP的传输层同时存在TCP和UDP两个协议。
    TCP是Tranfer Control Protocol的简称,是一种面向连接的保证可靠传输的协议。通过TCP协议传输,得到的是一个顺序的无差错的数据流。发送方和接收方的成对的两个 socket之间必须建立连接,以便在TCP协议的基础上进行通信,当一个socket(通常都是server socket)等待建立连接时,另一个socket可以要求进行连接,一旦这两个socket连接起来,它们就可以进行双向数据传输,双方都可以进行发送 或接收操作。
  UDP是User Datagram Protocol的简称,是一种无连接的协议,每个数据报都是一个独立的信息,包括完整的源地址或目的地址,它在网络上以任何可能的路径传往目的地,因此 能否到达目的地,到达目的地的时间以及内容的正确性都是不能被保证的。
  下面我们对这两种协议做简单比较:
 
  使用UDP时,每个数据报中 都给出了完整的地址信息,因此无需要建立发送方和接收方的连接。对于TCP协议,由于它是一个面向连接的协议,在socket之间进行数据传输之前必然要 建立连接,所以在TCP中多了一个连接建立的时间。
  使用UDP传输数据时是有大小限制的,每个被传输的数据报必须限定在64KB之内。而TCP没有这方面的 限制,一旦连接建立起来,双方的socket就可以按统一的格式传输大量的数据。UDP是一个不可靠的协议,发送方所发送的数据报并不一定以相同的次序到 达接收方。而TCP是一个可靠的协议,它确保接收方完全正确地获取发送方所发送的全部数据。
  总之,TCP在网络通信上有极强的生命力, 例如远程连接(Telnet)和文件传输(FTP)都需要不定长度的数据被可靠地传输。相比之下UDP操作简单,而且仅需要较少的监护,因此通常用于局域 网高可靠性的分散系统中client/server应用程序。
  读者可能要问,既然有了保证可靠传输的TCP协议,为什么还要非可靠传输的UDP协议 呢?主要的原因有两个。一是可靠的传输是要付出代价的,对数据内容正确性的检验必然占用计算机的处理时间和网络的带宽,因此TCP传输的效率不如UDP 高。二是在许多应用中并不需要保证严格的传输可靠性,比如视频会议系统,并不要求音频视频数据绝对的正确,只要保证连贯性就可以了,这种情况下显然使用 UDP会更合理一些。
 
8.2 基于URL的高层次Java网络编程
 8.2.1一致资源定位器URL
  URL(Uniform Resource Locator)是一致资源定位器的简称,它表示Internet上某一资源的地址。通过URL我们可以访问Internet上的各种网络资源,比如最常 见的WWW,FTP站点。浏览器通过解析给定的URL可以在网络上查找相应的文件或其他资源。
  URL是最为直观的一种网络定位方法。使用 URL符合人们的语言习惯,容易记忆,所以应用十分广泛。而且在目前使用最为广泛的TCP/IP中对于URL中主机名的解析也是协议的一个标准,即所谓的 域名解析服务。使用URL进行网络编程,不需要对协议本身有太多的了解,功能也比较弱,相对而言是比较简单的,所以在这里我们先介绍在Java中如何使用 URL进行网络编程来引导读者入门。
 
8.2.2 URL的组成
  protocol://resourceName
  协议名 (protocol)指明获取资源所使用的传输协议,如http、ftp、gopher、file等,资源名(resourceName)则应该是资源的 完整地址,包括主机名、端口号、文件名或文件内部的一个引用。例如:
  
http://www.sun.com/  协议名://主机名
  
http://home.netscape.com/home/welcome.html  协议名://机器名+文件名
  
http://www.gamelan.com:80/Gamelan/network.html#BOTTOM  协议名://机器名+端口号+文件名+内部引用
  
  端口号是和Socket编程相关的一个概念,初学者不必在 此深究,在后面会有详细讲解。内部引用是HTML中的标记,有兴趣的读者可以参考有关HTML的书籍。

8.2.3 创建一个URL
   为了表示URL, java.net中实现了类URL。我们可以通过下面的构造方法来初始化一个URL对象:
  (1) public URL (String spec);
     通过一个表示URL地址的字符串可以构造一个URL对象。
     URL urlBase=new URL("
http://www . 263.net/")
  (2) public URL(URL context, String spec);
      通过基URL和相对URL构造一个URL对象。
     URL net263=new URL ("
http://www.263.net/ ");
     URL index263=new URL(net263, "index.html")
  (3) public URL(String protocol, String host, String file);
     new URL("http", "
www.gamelan.com ", "/pages/Gamelan.net. html");
  (4) public URL(String protocol, String host, int port, String file);
     URL gamelan=new URL("http", "
www.gamelan.com ", 80, "Pages/Gamelan.network.html");
  注意:类URL的构造方法都声明抛弃非运行时例外 (MalformedURLException),因此生成URL对象时,我们必须要对这一例外进行处理,通常是用try-catch语句进行捕获。格式 如下:

  try{
     URL myURL= new URL(…)
  }catch (MalformedURLException e){
  …
  //exception handler code here
   …
  }
8.2.4 解析一个URL
  一个URL对象生成后,其属性是不能被改变的,但是我们可以通过类URL所提供的方法来获取这 些属性:
   public String getProtocol() 获取该URL的协议名。
   public String getHost() 获取该URL的主机名。
   public int getPort() 获取该URL的端口号,如果没有设置端口,返回-1。
   public String getFile() 获取该URL的文件名。
    public String getRef() 获取该URL在文件中的相对位置。
   public String getQuery() 获取该URL的查询信息。
   public String getPath() 获取该URL的路径
   public String getAuthority() 获取该URL的权限信息
   public String getUserInfo() 获得使用者的信息
   public String getRef() 获得该URL的锚

  下面的例子中,我们生成一个URL 对象,并获取它的各个属性。
  import java.net.*;
  import java.io.*;
  public class ParseURL{
  public static void main (String [] args) throws Exception{
  URL Aurl=new URL(" http://java.sun.com:80/docs/books/ ");
  URL tuto=new URL(Aurl,"tutorial.intro.html#DOWNLOADING"); 
   System.out.println("protocol="+ tuto.getProtocol());
   System.out.println("host ="+ tuto.getHost());
   System.out.println("filename="+ tuto.getFile());
   System.out.println("port="+ tuto.getPort());
   System.out.println("ref="+tuto.getRef());
   System.out.println("query="+tuto.getQuery());
   System.out.println("path="+tuto.getPath());
   System.out.println("UserInfo="+tuto.getUserInfo());
   System.out.println("Authority="+tuto.getAuthority());
  }
  }
  执行结果为:
    protocol=http host =java.sun.com filename=/docs/books/tutorial.intro.html 
   port=80 
    ref=DOWNLOADING 
   query=null 
    path=/docs/books/tutorial.intro.html 
   UserInfo=null 
    Authority=java.sun.com:80

 
8.2.5 从URL读取WWW网络资源
  当我们得到一个URL对象后,就可以通过它 读取指定的WWW资源。这时我们将使用URL的方法openStream(),其定义为:
         InputStream openStream();
  
  方法openSteam()与指定的URL建立连接并返回InputStream类的对象以从这一连 接中读取数据。
  public class URLReader {
  public static void main(String[] args) throws Exception { 
                      //声明抛出所 有例外
    URL tirc = new URL("
http://www.tirc1.cs.tsinghua.edu.cn/ "); 
                      //构建一URL对象
    BufferedReader in = new BufferedReader(new InputStreamReader(tirc.openStream()));
     //使用openStream得到一输入流并由此构造一个BufferedReader对象
    String inputLine;
     while ((inputLine = in.readLine()) != null) 
                 //从输 入流不断的读数据,直到读完为止
       System.out.println(inputLine); //把读入的数据打印到屏幕上
     in.close(); //关闭输入流
  }
  }
 8.2.6 通过URLConnetction连接WWW
  通过URL的方法 openStream(),我们只能从网络上读取数据,如果我们同时还想输出数据,例如向服务器端的CGI程序发送一些数据,我们必须先与URL建立连 接,然后才能对其进行读写,这时就要用到类URLConnection了。CGI是公共网关接口(Common Gateway Interface)的简称,它是用户浏览器和服务器端的应用程序进行连接的接口,有关CGI程序设计,请读者参考有关书籍。
  类URLConnection也在包 java.net中定义,它表示Java程序和URL在网络上的通信连接。当与一个URL建立连接时,首先要在一个URL对象上通过方法 openConnection()生成对应的URLConnection对象。例如下面的程序段首先生成一个指向地址 http://edu.chinaren.com/index.shtml 的对象,然后用openConnection()打开该URL对象上的一个连接,返回一个URLConnection对象。如果连接 过程失败,将产生IOException.
  Try{
    URL netchinaren = new URL ("
http://edu.chinaren.com/index.shtml ");
    URLConnectonn tc = netchinaren.openConnection();
   }catch(MalformedURLException e){ //创建URL()对象失败
  …
  }catch (IOException e){ //openConnection()失败
  …
  }
  类URLConnection提供了很多方 法来设置或获取连接参数,程序设计时最常使用的是getInputStream()和getOurputStream(),其定义为:
      InputSteram getInputSteram();
     OutputSteram getOutputStream();
  通过返回的输入/输出流我们可以与远程对 象进行通信。看下面的例子:
  URL url =new URL ("
http://www.javasoft.com/cgi-bin/backwards "); 
  //创建一URL对象
  URLConnectin con=url.openConnection(); 
  //由URL对象获取URLConnection对象
   DataInputStream dis=new DataInputStream (con.getInputSteam()); 
  //由 URLConnection获取输入流,并构造DataInputStream对象
  PrintStream ps=new PrintSteam(con.getOutupSteam());
  //由URLConnection获取输出流,并构造 PrintStream对象
  String line=dis.readLine(); //从服务器读入一行
   ps.println("client…"); //向服务器写出字符串 "client…"
  
  其中backwards为服务器端 的CGI程序。实际上,类URL的方法openSteam()是通过URLConnection来实现的。它等价于
     openConnection().getInputStream();
  
  基于URL的网络编程在底层其实还是基于下面要讲的 Socket接口的。WWW,FTP等标准化的网络服务都是基于TCP协议的,所以本质上讲URL编程也是基于TCP的一种应用。
8.3 基于Socket(套接字)的低层次Java网络编程
 8.3.1 Socket通讯
  网络上的两个程序通过一个双向的通讯连接实 现数据的交换,这个双向链路的一端称为一个Socket。Socket通常用来实现客户方和服务方的连接。Socket是TCP/IP协议的一个十分流行 的编程界面,一个Socket由一个IP地址和一个端口号唯一确定。
  在传统的UNIX环境下可以操作TCP/IP协议的接口不止Socket一 个,Socket所支持的协议种类也不光TCP/IP一种,因此两者之间是没有必然联系的。在Java环境下,Socket编程主要是指基于TCP/IP 协议的网络编程。
  说Socket编程是低层次网络编程并不等于它功能不强大,恰恰相反,正因为层次低,Socket编程比基于URL的网络编程提供 了更强大的功能和更灵活的控制,但是却要更复杂一些。由于Java本身的特殊性,Socket编程在Java中可能已经是层次最低的网络编程接口,在 Java中要直接操作协议中更低的层次,需要使用Java的本地方法调用(JNI),在这里就不予讨论了。
 8.3.2 Socket通讯的一般过程
   
   前面已经提到Socket通常用来实现C/S结构。
  使用Socket进行Client/Server程序设计的一般连接过程是这样的:Server端 Listen(监听)某个端口是否有连接请求,Client端向Server端发出Connect(连接)请求,Server端向Client端发回 Accept(接受)消息。一个连接就建立起来了。Server端和Client端都可以通过Send,Write等方法与对方通信。
  
   对于一个功能齐全的Socket,都要包含以下基本结构,其工作过程包含以下四个基本的步骤:
  (1) 创建Socket;
   (2) 打开连接到Socket的输入/出流;
  (3) 按照一定的协议对Socket进行读/写操作;
  (4) 关闭Socket.
  第三步是程序员用来调用Socket和实现程序功能的关键步骤,其他三步在各种程序中基本相同。
 
以上4个步骤是 针对TCP传输而言的,使用UDP进行传输时略有不同,在后面会有具体讲解。
  java在包java.net中提供了两个类Socket和 ServerSocket,分别用来表示双向连接的客户端和服务端。这是两个封装得非常好的类,使用很方便。其构造方法如下:
   Socket(InetAddress address, int port);
  Socket(InetAddress address, int port, boolean stream);
  Socket(String host, int prot);
   Socket(String host, int prot, boolean stream);
  Socket(SocketImpl impl)
  Socket(String host, int port, InetAddress localAddr, int localPort)
  Socket(InetAddress address, int port, InetAddress localAddr, int localPort)
  ServerSocket(int port);
   ServerSocket(int port, int backlog);
  ServerSocket(int port, int backlog, InetAddress bindAddr)
  其中address、host和port分别是双向连接中另一方的IP地址、主机名和 端口号,stream指明socket是流socket还是数据报socket,localPort表示本地主机的端口号,localAddr和 bindAddr是本地机器的地址(ServerSocket的主机地址),impl是socket的父类,既可以用来创建serverSocket又可 以用来创建Socket。count则表示服务端所能支持的最大连接数。例如:
  Socket client = new Socket("127.0.01.", 80);
  ServerSocket server = new ServerSocket(80);
  注意,在选择端口时,必须小心。每一个端口提供一种特定的服务,只有给出正确的端口,才能获得相应的服务。0~1023的端口号 为系统所保留,例如http服务的端口号为80,telnet服务的端口号为21,ftp服务的端口号为23, 所以我们在选择端口号时,最好选择一个大于1023的数以防止发生冲突。
  在创建socket时如果发生错误,将产生IOException,在程序中必须对之 作出处理。所以在创建Socket或ServerSocket是必须捕获或抛出例外。

8.3.4 客户端的Socket
  下面是一个典型的创建 客户端Socket的过程。
   try{
     Socket socket=new Socket("127.0.0.1",4700); 
     //127.0.0.1是TCP/IP协议中默认的本机地址
    }catch(IOException e){
     System.out.println("Error:"+e);
   }
  这是最简单的在客户端创建一个 Socket的一个小程序段,也是使用Socket进行网络通讯的第一步,程序相当简单,在这里不作过多解释了。在后面的程序中会用到该小程序段。

8.3.5 服务器端的ServerSocket
  下面是一个典型的创建Server端ServerSocket的过程。
   ServerSocket server=null;
  try {
     server=new ServerSocket(4700); 
     //创建一个ServerSocket在端口4700监听客户请求
   }catch(IOException e){
     System.out.println("can not listen to :"+e);
  }
  Socket socket=null;
  try {
     socket=server.accept(); 
    //accept()是一个阻塞的方法,一旦有客户请求,它就会返回一个Socket 对象用于同客户进行交互
  }catch(IOException e){
     System.out.println("Error:"+e);
  }
  以上的程序是Server的典型工作模式,只不过在这里Server只能接 收一个请求,接受完后Server就退出了。实际的应用中总是让它不停的循环接收,一旦有客户请求,Server总是会创建一个服务线程来服务新来的客 户,而自己继续监听。程序中accept()是一个阻塞函数,所谓阻塞性方法就是说该方法被调用后,将等待客户的请求,直到有一个客户启动并请求连接到相 同的端口,然后accept()返回一个对应于客户的socket。这时,客户方和服务方都建立了用于通信的socket,接下来就是由各个socket 分别打开各自的输入/输出流。

8.3.6 打开输入/出流
  类Socket提供了方法getInputStream ()和getOutStream()来得到对应的输入/输出流以进行读/写操作,这两个方法分别返回InputStream和OutputSteam类对 象。为了便于读/写数据,我们可以在返回的输入/输出流对象上建立过滤流,如DataInputStream、DataOutputStream或 PrintStream类对象,对于文本方式流对象,可以采用InputStreamReader和OutputStreamWriter、 PrintWirter等处理。
  例如:
  PrintStream os=new PrintStream(new BufferedOutputStreem(socket.getOutputStream()));
  DataInputStream is=new DataInputStream(socket.getInputStream());
  PrintWriter out=new PrintWriter(socket.getOutStream(),true);
  BufferedReader in=new ButfferedReader(new InputSteramReader(Socket.getInputStream()));
  输入输出流是网络编程的实质性部分,具体 如何构造所需要的过滤流,要根据需要而定,能否运用自如主要看读者对Java中输入输出部分掌握如何。

 
8.3.7 关闭Socket
  每一个Socket存在时,都将占用一定的资源,在Socket对象使用完毕时,要其关闭。关闭Socket可以调用Socket 的Close()方法。在关闭Socket之前,应将与Socket相关的所有的输入/输出流全部关闭,以释放所有的资源。而且要注意关闭的顺序,与 Socket相关的所有的输入/输出该首先关闭,然后再关闭Socket。
  os.close();
  is.close();
   socket.close();
  尽管Java有自动回收机制,网络资源最终是会被释放的。但是为了有效的利用资源,建议读者按照合理的顺序主动释放资源。

 
8.3.8 简单的Client/Server程序设计
  下面我们给出一个用Socket实现的客户和服务器交互的典型的C/S结构的演示程序,读者通过仔细阅读该程序,会对前面所讨论 的各个概念有更深刻的认识。程序的意义请参考注释。
  1. 客户端程序
  2. 服务器端程序
    1. 客户端程序
  import java.io.*;
   import java.net.*;
  public class TalkClient {
    public static void main(String args[]) {
      try{
        Socket socket=new Socket("127.0.0.1",4700); 
        //向本机的4700端口发出客户请求
         BufferedReader sin=new BufferedReader(new InputStreamReader(System.in));
         //由系统标准输入设备构造BufferedReader对象
        PrintWriter os=new PrintWriter(socket.getOutputStream());
        //由Socket对象得到输出流,并构造 PrintWriter对象
        BufferedReader is=new BufferedReader(new InputStreamReader(socket.getInputStream()));
        //由Socket对象得到输入 流,并构造相应的BufferedReader对象
        String readline;
         readline=sin.readLine(); //从系统标准输入读入一字符串
         while(!readline.equals("bye")){ 
        //若从标准输入读入的字符串为 "bye"则停止循环
           os.println(readline); 
          //将从系统标准输入读入的字符串输出到Server
           os.flush(); 
          //刷新输出流,使Server马上收到该字符串
           System.out.println("Client:"+readline); 
          //在系统标准输出上打印读入的字符串
           System.out.println("Server:"+is.readLine()); 
          //从 Server读入一字符串,并打印到标准输出上
          readline=sin.readLine(); //从系统标准输入读入一字符串
        } //继续循环
        os.close(); //关闭Socket输出流
         is.close(); //关闭Socket输入流
        socket.close(); //关闭Socket
       }catch(Exception e) {
        System.out.println("Error"+e); //出错,则打印出错信息
      }
  }
}
 2. 服务器端程序
  import java.io.*;
   import java.net.*;
  import java.applet.Applet;
  public class TalkServer{
    public static void main(String args[]) {
       try{
        ServerSocket server=null;
        try{ 
           server=new ServerSocket(4700); 
        //创建一个ServerSocket在端口4700监听客户 请求
        }catch(Exception e) {
          System.out.println("can not listen to:"+e); 
        //出错,打印出错信息
        }
        Socket socket=null;
        try{
          socket=server.accept(); 
           //使用accept()阻塞等待客户请求,有客户
          //请求到来则产生一个Socket对象,并继续执行
         }catch(Exception e) {
           System.out.println("Error."+e); 
          //出错,打印出错信息
        }
         String line;
        BufferedReader is=new BufferedReader(new InputStreamReader(socket.getInputStream()));
         //由Socket对象得到输入 流,并构造相应的BufferedReader对象
        PrintWriter os=newPrintWriter(socket.getOutputStream());
         //由Socket对象得到输出 流,并构造PrintWriter对象
        BufferedReader sin=new BufferedReader(new InputStreamReader(System.in));
         //由系统标准输入设备构造BufferedReader对象
         System.out.println("Client:"+is.readLine()); 
        //在标准输出上打印从客户端读 入的字符串
        line=sin.readLine(); 
        //从标准输入读入一字符串
         while(!line.equals("bye")){ 
        //如果该字符串为 "bye",则停止循环
           os.println(line); 
          //向客户端输出该字符串
           os.flush(); 
          //刷新输出流,使Client马上收到该字符串
           System.out.println("Server:"+line); 
          //在系统标准输出上打印读入的字符串
           System.out.println("Client:"+is.readLine());
          //从 Client读入一字符串,并打印到标准输出上
          line=sin.readLine(); 
           //从系统标准输入读入一字符串
        }  //继续循环
        os.close(); //关闭Socket输出流
        is.close(); //关闭Socket输入流
         socket.close(); //关闭Socket
        server.close(); //关闭ServerSocket
       }catch(Exception e){
        System.out.println("Error:"+e); 
         //出错,打印出错信息
      }
    }
  }
  从上面的两个程序中我们可以看 到,socket四个步骤的使用过程。读者可以分别将Socket使用的四个步骤的对应程序段选择出来,这样便于读者对socket的使用有进一步的了 解。
   读者可以在单机上试验该程序,最好是能在真正的网络环境下试验该程序,这样更容易分辨输出的内容和客户机,服务器的对应关系。同时也可以修改该程序,提供 更为强大的功能,或更加满足读者的意图。

 
8.3.9 支持多客户的client/server程序设计
  前面提供的Client/Server程序 只能实现Server和一个客户的对话。在实际应用中,往往是在服务器上运行一个永久的程序,它可以接收来自其他多个客户端的请求,提供相应的服务。为了 实现在服务器方给多个客户提供服务的功能,需要对上面的程序进行改造,利用多线程实现多客户机制。服务器总是在指定的端口上监听是否有客户请求,一旦监听 到客户请求,服务器就会启动一个专门的服务线程来响应该客户的请求,而服务器本身在启动完线程之后马上又进入监听状态,等待下一个客户的到来。
  客户端的程序和上面程序是完全一样的,读 者如果仔细阅读过上面的程序,可以跳过不读,把主要精力集中在Server端的程序上。
  1. 客户端程序:MultiTalkClient.java
  2. 服务器端程序: MultiTalkServer.java
  3. 程序ServerThread.java
 1. 客户端程序:MultiTalkClient.java
  import java.io.*;
  import java.net.*;
  public class MultiTalkClient {
   public static void main(String args[]) {
    try{
      Socket socket=new Socket("127.0.0.1",4700); 
      //向本机的4700端口发出客户请求
       BufferedReader sin=new BufferedReader(new InputStreamReader(System.in));
       //由系统标准输入设备构造BufferedReader对象
      PrintWriter os=new PrintWriter(socket.getOutputStream());
      //由Socket对象得到输出流,并构造 PrintWriter对象
      BufferedReader is=new BufferedReader(new InputStreamReader(socket.getInputStream()));
      //由Socket对象得到输入流,并 构造相应的BufferedReader对象
      String readline;
       readline=sin.readLine(); //从系统标准输入读入一字符串
       while(!readline.equals("bye")){ 
      //若从标准输入读入的字符串为 "bye"则停止循环
         os.println(readline); 
        //将从系统标准输入读入的字符串输出到Server
         os.flush(); 
        //刷新输出流,使Server马上收到该字符串
         System.out.println("Client:"+readline); 
        //在系统标准输出上打印读入的字符串
         System.out.println("Server:"+is.readLine()); 
        //从 Server读入一字符串,并打印到标准输出上
        readline=sin.readLine(); 
         //从系统标准输入读入一字符串
      } //继续循环
      os.close(); //关闭Socket输出流
       is.close(); //关闭Socket输入流
      socket.close(); //关闭Socket
     }catch(Exception e) {
      System.out.println("Error"+e); //出错,则打印出错信息
    }
  }
}
 2. 服务器端程序: MultiTalkServer.java
  import java.io.*;
   import java.net.*;
  import ServerThread;
  public class MultiTalkServer{
   static int clientnum=0; //静态成员变量,记录当前客户的个数
    public static void main(String args[]) throws IOException {
     ServerSocket serverSocket=null;
    boolean listening=true;
     try{
      serverSocket=new ServerSocket(4700); 
      //创建一个 ServerSocket在端口4700监听客户请求
    }catch(IOException e) {
       System.out.println("Could not listen on port:4700."); 
      //出错,打印出 错信息
      System.exit(-1); //退出
    }
    while(listening){ //永远循环监听
      new ServerThread(serverSocket.accept(),clientnum).start();
      //监听到客户请 求,根据得到的Socket对象和
       客户计数创建服务线程,并启动之
      clientnum++; //增加客户计数
    }
    serverSocket.close(); //关闭ServerSocket
  }
}
 3. 程序ServerThread.java
  import java.io.*;
  import java.net.*;
  public class ServerThread extends Thread{
   Socket socket=null; //保存与本线程相关的Socket对象
   int clientnum; //保存本进程的客户计数
   public ServerThread(Socket socket,int num) { //构造函数
    this.socket=socket; //初始化socket变量
    clientnum=num+1; //初始化clientnum变量
   }
    public void run() { //线程主体
    try{
      String line;
       BufferedReader is=new BufferedReader(new InputStreamReader(socket.getInputStream()));
  //由Socket对象得到输入流,并构造相应 的BufferedReader对象
      PrintWriter os=newPrintWriter(socket.getOutputStream());
      //由Socket对象得到输出流,并 构造PrintWriter对象
      BufferedReader sin=new BufferedReader(new InputStreamReader(System.in));
      //由系统标准输入设备构造BufferedReader对象
       System.out.println("Client:"+ clientnum +is.readLine());
       //在标准输出上打印从客户端读入的字符串
      line=sin.readLine(); 
      //从标准输入读入一字 符串
      while(!line.equals("bye")){ 
      //如果该字符串为 "bye",则停止循环
         os.println(line); 
        //向客户端输出该字符串
         os.flush(); 
        //刷新输出流,使Client马上收到该字符串
         System.out.println("Server:"+line); 
        //在系统标准输出上打印该字符串
         System.out.println("Client:"+ clientnum +is.readLine());
         //从Client读入一字符串,并打印到标准输出上
        line=sin.readLine(); 
        // 从系统标准输入读入一字符串
      } //继续循环
      os.close(); //关闭Socket输出流
       is.close(); //关闭Socket输入流
      socket.close(); //关闭Socket
       server.close(); //关闭ServerSocket
     }catch(Exception e){
       System.out.println("Error:"+e); 
      //出错,打印出错信息
     }
    }
 }
  这个程序向读者展示了网络应用中最为典型的C/S结构,我们可以用下面的图来描述这样一种模型:
 
  通过以上的学习,读者应该对Java的面 向流的网络编程有了一个比较全面的认识,这些都是基于TCP的应用,后面我们将介绍基于UDP的Socket编程。
 
8.3.10 据报Datagram通讯
  前面在介绍TCP/IP协议的时候,我们已经提到,在TCP/IP协议的传输层除了TCP协议之外还有一个UDP协议,相比而言 UDP的应用不如TCP广泛,几个标准的应用层协议HTTP,FTP,SMTP…使用的都是TCP协议。但是,随着计算机网络的发展,UDP协议正越来越 来显示出其威力,尤其是在需要很强的实时交互性的场合,如网络游戏,视频会议等,UDP更是显示出极强的威力,下面我们就介绍一下Java环境下如何实现 UDP网络传输。
8.3.11 什么是Datagram
  所谓数据报(Datagram)就跟日常生活中的邮件系统一样,是不能保证可靠的寄到 的,而面向链接的TCP就好比电话,双方能肯定对方接受到了信息。在本章前面,我们已经对UDP和TCP进行了比较,在这里再稍作小节:
  TCP,可靠,传输大小无限制,但是需要 连接建立时间,差错控制开销大。
  UDP,不可靠,差错控制开销较小,传输大小限制在64K以下,不需要建立连接。
  总之,这两种协议各有特点,应用的场合也 不同,是完全互补的两个协议,在TCP/IP协议中占有同样重要的地位,要学好网络编程,两者缺一不可。
 
8.3.12 Datagram通讯的表示方法:DatagramSocket;DatagramPacket
  包java.net中提供了两个类 DatagramSocket和DatagramPacket用来支持数据报通信,DatagramSocket用于在程序之间建立传送数据报的通信连 接, DatagramPacket则用来表示一个数据报。先来看一下DatagramSocket的构造方法:
    DatagramSocket();
   DatagramSocket(int prot);
    DatagramSocket(int port, InetAddress laddr)
  
  其中,port指明socket所使 用的端口号,如果未指明端口号,则把socket连接到本地主机上一个可用的端口。laddr指明一个可用的本地地址。给出端口号时要保证不发生端口冲 突,否则会生成SocketException类例外。注意:上述的两个构造方法都声明抛弃非运行时例外SocketException,程序中必须进行 处理,或者捕获、或者声明抛弃。
用数据报方式编写client/server程序时,无论在客户方还是服务方,首先都要建立一个DatagramSocket对象, 用来接收或发送数据报,然后使用DatagramPacket类对象作为传输数据的载体。下面看一下DatagramPacket的构造方法 :
    DatagramPacket(byte buf[],int length);
   DatagramPacket(byte buf[], int length, InetAddress addr, int port);
    DatagramPacket(byte[] buf, int offset, int length);
    DatagramPacket(byte[] buf, int offset, int length, InetAddress address, int port);
  其中,buf中存放数据报数据,length为数据报中数据的长度,addr和port旨明目的地址,offset指明了数据报的 位移量。
   在接收数据前,应该采用上面的第一种方法生成一个DatagramPacket对象,给出接收数据的缓冲区及其长度。然后调用 DatagramSocket 的方法receive()等待数据报的到来,receive()将一直等待,直到收到一个数据报为止。
   DatagramPacket packet=new DatagramPacket(buf, 256);
  Socket.receive (packet);
  发送数据前,也要先生成一个新的DatagramPacket对象,这时要使用上面的第二种构造方法,在给出存放发送数据的缓冲区 的同时,还要给出完整的目的地址,包括IP地址和端口号。发送数据是通过DatagramSocket的方法send()实现的,send()根据数据报 的目的地址来寻径,以传递数据报。
  DatagramPacket packet=new DatagramPacket(buf, length, address, port);
  Socket.send(packet);
在构造数据报时,要给出InetAddress 类参数。类InetAddress在包java.net中定义,用来表示一个Internet地址,我们可以通过它提供的类方法getByName()从 一个表示主机名的字符串获取该主机的IP地址,然后再获取相应的地址信息。
 
 8.3.13 基于UDP的简单的Client/Server程序设计
  有了上面的知识,我们就可以来构件一个基于UDP的C/S 网络传输模型
  1. 客户方程序 QuoteClient.java
  2. 服务器方程序:QuoteServer.java
  3. 程序QuoteServerThread.java   
 1. 客户方程序 QuoteClient.java
  import java.io.*;
  import java.net.*;
  import java.util.*;
  public class QuoteClient {
   public static void main(String[] args) throws IOException 
   {
     if(args.length!=1) { 
    //如果启动的时候没有给出Server的名字,那么出错退出
      System.out.println("Usage:java QuoteClient <hostname>"); 
      //打印出错信息
     return; //返回
    }</hostname>
    DatagramSocket socket=new DatagramSocklet(); 
    //创建数据报套接字
    Byte[] buf=new byte[256]; //创建缓冲区
    InetAddress address=InetAddress.getByName(args [0]); 
//由命令行给出的第一个参数默认为Server的名字,通过它得到Server的IP信息
     DatagramPacket packet=new DatagramPacket (buf, buf.length, address, 4445);
    //创建DatagramPacket对象
    socket.send(packet); //发送
     packet=new DatagramPacket(buf,buf.length); 
    //创建新的 DatagramPacket对象,用来接收数据报
    socket.receive(packet); //接收
     String received=new String(packet.getData()); 
    //根据接收到的字节数组生成相应的字 符串
    System.out.println("Quote of the Moment:"+received ); 
     //打印生成的字符串
    socket.close(); //关闭套接口
   }
 }
 2. 服务器方程序:QuoteServer.java
  public class QuoteServer{
   public static void main(String args[]) throws java.io.IOException 
   {
    new QuoteServerThread().start(); 
    //启动一个QuoteServerThread线程
   }
   }
  3. 程序QuoteServerThread.java
  import java.io.*;
  import java.net.*;
  import java.util.*;
  //服务器线程
  public class QuoteServerThread extends Thread
  {
  protected DatagramSocket socket=null; 
  //记录和本对象相关联的DatagramSocket对象
  protected BufferedReader in=null; 
  //用来读文件的一个Reader
  protected boolean moreQuotes=true; 
  //标志变量,是否继续操作
  public QuoteServerThread() throws IOException { 
  //无参数的构造函数
    this("QuoteServerThread"); 
     //以QuoteServerThread为默认值调用带参数的构造函数
  }
  public QuoteServerThread(String name) throws IOException {
    super(name); //调用父类的构造函数
    socket=new DatagramSocket(4445); 
    //在端口4445创建数 据报套接字
    try{
      in= new BufferedReader(new FileReader(" one-liners.txt"));
      //打开一个文件,构造相应的BufferReader对象
     }catch(FileNotFoundException e) { //异常处理
       System.err.println("Could not open quote file. Serving time instead."); 
        //打印出错信息
    }
  }
  public void run() //线程主体
  {
     while(moreQuotes) {
     try{
       byte[] buf=new byte[256]; //创建缓冲区
       DatagramPacket packet=new DatagramPacket(buf,buf.length);
       //由缓冲区构造DatagramPacket对象
        socket.receive(packet); //接收数据报
       String dString=null;
        if(in= =null) dString=new Date().toString(); 
       //如果初始化的时候 打开文件失败了,
       //则使用日期作为要传送的字符串
       else dString=getNextQuote(); 
       //否则调用成员函数从文件中读出字符串
        buf=dString.getByte(); 
       //把String转换成字节数组,以便传送
       InetAddress address=packet.getAddress(); 
       //从Client端传来的Packet中得到Client地址
        int port=packet.getPort(); //和端口号
       packet=new DatagramPacket(buf,buf.length,address,port); 
       //根据客户端信息构建 DatagramPacket
       socket.send(packet); //发送数据报
       }catch(IOException e) { //异常处理
       e.printStackTrace(); //打印错误栈
        moreQuotes=false; //标志变量置false,以结束循环
      }
    }
     socket.close(); //关闭数据报套接字
  }
  protected String getNextQuotes(){ 
   //成员函数,从文件中读数据
    String returnValue=null;
    try {
        if((returnValue=in.readLine())= =null) { 
       //从文件中读一行,如果读到了文件尾
        in.close( ); //关闭输入流
       moreQuotes=false; 
       //标志变量 置false,以结束循环
       returnValue="No more quotes. Goodbye."; 
        //置返回值
       } //否则返回字符串即为从文件读出的字符串
    }catch(IOEception e) { //异常处理
       returnValue="IOException occurred in server"; 
        //置异常返回值
    }
    return returnValue; //返回字符串
  }
}
  可以看出使用UDP和使用TCP在程序上 还是有很大的区别的。一个比较明显的区别是,UDP的Socket编程是不提供监听功能的,也就是说通信双方更为平等,面对的接口是完全一样的。但是为了 用UDP实现C/S结构,在使用UDP时可以使用DatagramSocket.receive()来实现类似于监听的功能。因为receive()是阻 塞的函数,当它返回时,缓冲区里已经填满了接受到的一个数据报,并且可以从该数据报得到发送方的各种信息,这一点跟accept()是很相象的,因而可以 根据读入的数据报来决定下一步的动作,这就达到了跟网络监听相似的效果。
 
8.3.14 用数据报进行广播通讯
  DatagramSocket只允许数据 报发送一个目的地址,java.net包中提供了一个类MulticastSocket,允许数据报以广播方式发送到该端口的所有客户。 MulticastSocket用在客户端,监听服务器广播来的数据。
  我们对上面的程序作一些修改,利用MulticastSocket实现广播通信。新程 序完成的功能是使同时运行的多个客户程序能够接收到服务器发送来的相同的信息,显示在各自的屏幕上。
  1.客户方程序: MulticastClient.java
  2.服务器方程序: MulticastServer.java
  3.程序 MulticastServerThread.java
 1. 客户方程序:MulticastClient.java
  import java.io.*;
   import java.net.*;
  import java.util.*;
  public class MulticastClient {
    public static void main(String args[]) throws IOException
    {
     MulticastSocket socket=new MulticastSocket(4446); 
     //创建4446端口的广播套接字
     InetAddress address=InetAddress.getByName("230.0.0.1"); 
     //得到230.0.0.1的地址信息
      socket.joinGroup(address); 
     //使用joinGroup()将广播套接字绑定到地址上
      DatagramPacket packet;
     for(int i=0;i<5;i++) {
       byte[] buf=new byte[256]; 
       //创建缓冲区
       packet=new DatagramPacket(buf,buf.length); 
       //创建接收数据报
        socket.receive(packet); //接收
       String received=new String(packet.getData()); 
       //由接收到的数据报得到字节数组,
       //并由此构造 一个String对象
       System.out.println("Quote of theMoment:"+received); 
       //打印得到的字符串
     } //循环5次
      socket.
分享到:
评论

相关推荐

    Java网络编程案例教程习题参考答案 .pdf

    Java_network_programming是Java编程语言中一个基础组件,用于实现网络通信。以下是Java网络编程案例教程习题参考答案中涉及到的知识点: 1. Socket编程:Socket是Java网络编程中最基本的组件,用于实现网络通信。...

    java udp socket 网络编程

    Java UDP套接字网络编程是Java编程领域中的一个重要部分,主要涉及如何利用Java的Socket类库来实现基于用户数据报协议(User Datagram Protocol)的通信。UDP是一种无连接的、不可靠的传输协议,适用于对实时性要求...

    Java网络编程/Java网络编程实例

    1. **Java Socket编程**:Java的Socket类提供了基于TCP/IP协议的网络通信能力。通过ServerSocket创建服务器端,Socket创建客户端,两者建立连接后可以进行双向数据传输。例如,你可以构建一个简单的聊天应用或文件...

    Java Socket编程实现UDP网络测试

    Java Socket编程实现UDP网络测试是计算机网络领域中的一个重要实践,主要涉及到Java编程语言以及网络通信协议UDP(User Datagram Protocol)。Socket在计算机网络中扮演着桥梁的角色,它允许应用程序通过网络发送和...

    Java网络编程(第4版)PDF

    《Java网络编程(第4版)》是一本深入探讨Java平台上的网络编程技术的专业书籍,适合想要提升Java通讯技术的学者阅读。此书全面覆盖了Java网络编程的基础和高级概念,帮助开发者理解如何利用Java语言构建高效、可靠的...

    Java socket网络编程的基础示例

    Java中使用`java.net.DatagramSocket`和`java.net.DatagramPacket`类进行UDP编程。以下是一个简单的UDP服务器端和客户端示例: ```java // 服务器端 DatagramSocket serverSocket = new DatagramSocket(9999); // ...

    Java网络编程期末考试复习题库+答案

    在Java中,网络编程主要依赖于Java的Socket编程、ServerSocket、URL类以及NIO(非阻塞I/O)等核心API。这份"Java网络编程期末考试复习题库+答案"为学生提供了全面的复习资源,涵盖了Java网络编程的主要知识点。 1. ...

    java网络编程

    java网络编程包括socket tcp/udp io/nio讲解 http协议 jdbc rmi java的安全框架等知识

    JAVA Socket 网络编程教程

    Java Socket网络编程是Java开发中一个重要...Java Socket编程是构建网络应用的基础,掌握其原理和实践方法对于开发分布式系统、网络服务等至关重要。通过不断实践和学习,开发者可以更熟练地运用Socket来解决实际问题。

    java网络编程socket编程等

    Socket编程是网络编程的基础,是Java实现网络通信的核心技术。本文将深入探讨Java网络编程和Socket编程的相关知识点。 一、Java网络编程基础 Java网络编程主要涉及TCP/IP协议族,包括TCP(传输控制协议)和UDP...

    总结java_socket编程.doc

    在Java中,Socket编程主要是基于TCP/IP协议的网络编程。 网络编程的两个主要问题 在网络编程中,有两个主要的问题需要解决:一是如何准确地定位网络上的一台或多台主机,二是找到主机后如何可靠高效地进行数据传输...

    Java网络编程第三版.pdf

    1. **Java网络编程基础**:首先,书中会介绍Java中的Socket编程,包括TCP和UDP协议的基础知识,以及如何使用Java的Socket和ServerSocket类创建客户端和服务器端的连接。 2. **I/O与NIO**:Java的I/O流系统是网络...

    《Java网络编程实例:Java网络编程实例》

    3. **Socket编程**:Socket是Java网络编程的基础,它是进程间通信的一种方式。通过创建ServerSocket监听客户端连接,然后通过Socket与客户端建立连接,实现数据交换。 4. **多线程处理**:在网络编程中,为了同时...

    基于UDP和TCP协议的Socket(网络编程/套接字)案例

    以上是对基于UDP和TCP协议的Java Socket编程的基本介绍。实际应用中,还需要考虑异常处理、多线程、数据编码解码等问题,以确保程序的健壮性和可维护性。通过深入理解并实践这些示例,开发者可以更好地掌握网络编程...

    Java网络编程实例(随书源代码)

    Java网络编程是开发分布式应用程序的关键技术,它允许程序通过网络发送和接收数据。《Java网络编程实例》这本书的源代码提供了丰富的示例,帮助读者深入理解这一领域。本压缩包包含的源代码覆盖了Java网络编程的各种...

    基于Socket的java网络编程

    在Java中,Socket编程通常涉及到以下步骤: 1. **创建Socket**:客户端使用`Socket`类创建一个Socket实例,指定服务器的IP地址和端口号,发起连接请求。服务器端则使用`ServerSocket`类监听特定端口,等待客户端...

    Java网络编程 Socket编程

    本篇将详细解析Java网络编程中Socket编程的关键知识点,包括TCP实现、多线程在Socket编程中的应用、UDP程序的实现以及网络编程的基本概念。 首先,我们来了解网络的基本概念。网络是由相互连接的计算机组成的系统,...

    华科-计算机网络实验报告-Java Socket编程-网络组建实验.docx

    * 高度灵活性:Java Socket 编程可以实现多种网络协议,例如 TCP/IP、UDP 等。 * 高效性:Java Socket 编程可以实现高效的网络传输。 Java Socket 编程在计算机网络实验中的应用 在计算机网络实验中,Java Socket ...

    java网络编程UDP和TCP.pdf

    Java网络编程涵盖了两种主要的通信协议:UDP(User Datagram Protocol)和TCP(Transmission Control Protocol)。这两者都是Internet协议的一部分,用于在不同的设备间传输数据。 TCP是一种面向连接的、可靠的传输...

Global site tag (gtag.js) - Google Analytics