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编程
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
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 "); //打印出错信息 return; //返回 }
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
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.leaveGroup(address); //把广播套接字从地址上解除绑定 socket.close(); //关闭广播套接字 } }
2. 服务器方程序:MulticastServer.java
public class MulticastServer{ public static void main(String args[]) throws java.io.IOException { new MulticastServerThread().start(); //启动一个服务器线程 } }
3. 程序MulticastServerThread.java
import java.io.*; import java.net.*; import java.util.*; public class MulticastServerThread extends QuoteServerThread //从QuoteServerThread继承得到新的服务器线程类MulticastServerThread { Private long FIVE_SECOND=5000; //定义常量,5秒钟 public MulticastServerThread(String name) throws IOException { super("MulticastServerThread"); //调用父类,也就是QuoteServerThread的构造函数 }
public void run() //重写父类的线程主体 { while(moreQuotes) { //根据标志变量判断是否继续循环 try{ byte[] buf=new byte[256]; //创建缓冲区 String dString=null; if(in==null) dString=new Date().toString(); //如果初始化的时候打开文件失败了, //则使用日期作为要传送的字符串 else dString=getNextQuote(); //否则调用成员函数从文件中读出字符串 buf=dString.getByte(); //把String转换成字节数组,以便传送send it InetAddress group=InetAddress.getByName("230.0.0.1"); //得到230.0.0.1的地址信息 DatagramPacket packet=new DatagramPacket(buf,buf.length,group,4446); //根据缓冲区,广播地址,和端口号创建DatagramPacket对象 socket.send(packet); //发送该Packet try{ sleep((long)(Math.random()*FIVE_SECONDS)); //随机等待一段时间,0~5秒之间 }catch(InterruptedException e) { } //异常处理 }catch(IOException e){ //异常处理 e.printStackTrace( ); //打印错误栈
moreQuotes=false; //置结束循环标志 } } socket.close( ); //关闭广播套接口 } }
至此,Java网络编程这一章已经讲解完毕。读者通过学习,应该对网络编程有了一个清晰的认识,可能对某些概念还不是十分的清楚,还是需要更多的实践来进一步掌握。编程语言的学习不同于一般的学习,及其强调实践的重要性。读者应该对URL网络编程,Socket中的TCP,UDP编程进行大量的练习才能更好的掌握本章中所提到的一些概念,才能真正学到Java网络编程的精髓!
最后几个小节所举的例子,读者务必要亲自试验一下,如果遇到问题,想办法解决之。最好能根据自己的意图加以改进。这样才能更好的理解这几个程序,理解其中所包含的编程思想。
【本讲小结】 本讲主要讲解了Java环境下的网络编程。因为TCP/IP协议是Java网络编程的基础知识,本讲开篇重点介绍了TCP/IP协议中的一些概念,TCP/IP协议本身是一个十分庞大的系统,用几个小节是不可能讲清楚的。所以我们只是联系实际,讲解了一些最基本的概念,帮助学生理解后面的相关内容。重点有一下几个概念:主机名,IP,端口,服务类型,TCP,UDP。
后续的内容分为两大块,一块是以URL为主线,讲解如何通过URL类和URLConnection类访问WWW网络资源,由于使用URL十分方便直观,尽管功能不是很强,还是值得推荐的一种网络编程方法,尤其是对于初学者特别容易接受。本质上讲,URL网络编程在传输层使用的还是TCP协议。
另一块是以Socket接口和C/S网络编程模型为主线,依次讲解了如何用Java实现基于TCP的C/S结构,主要用到的类有Socket,ServerSocket。以及如何用Java实现基于UDP的C/S结构,还讨论了一种特殊的传输方式,广播方式,这种方式是UDP所特有的,主要用到的类有DatagramSocket , DatagramPacket, MulticastSocket。这一块在Java网络编程中相对而言是最难的(尽管Java在网络编程这方面已经做的够"傻瓜"了,但是网络编程在其他环境下的却是一件极为头痛的事情,再"傻瓜"还是有一定的难度),也是功能最为强大的一部分,读者应该好好研究,领悟其中的思想。
最后要强调的是要学好Java网络编程,Java语言,最重要的还是在于多多练习!
|