服务热线:13616026886

技术文档 欢迎使用技术文档,我们为你提供从新手到专业开发者的所有资源,你也可以通过它日益精进

位置:首页 > 技术文档 > JAVA > 新手入门 > 基础入门 > 查看文档

java教程 第八讲 java网络编程

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.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进行传输时略有不同,在后面会有具体讲解。

8.3.3 创建socket

  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. 客户端程序

  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端的程序上。

 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编程