1、本章学习目标:了解Java 网络相关的API掌握Socket类及其方法的使用掌握ServerSocket类的使用第十五章第十五章 网络编程网络编程第第1 1节节partJava网络API 最初,Java就是作为网络编程语言而出现的,其本身就对网络通信提供了支持,允许使用网络上的各种资源和数据,与服务器建立各种传输通道,实现数据的传输,使网络编程实现起来变得简单。Java中有关网络方面的功能都定义在包中,该包下的URL和URLConnection等类提供了以程序的方式来访问Web服务,而URLDecoder和URLEncoder则提供了普通字符串和application/x-www-form-u
2、rlencode MIME字符串相互转换的静态方法。Java网络API本节概述 Java提供InetAddress类来封装IP地址或域名,InetAddress类有两个子类:Inet4Address类和Inet6Address类,分别用于封装4个字节的IP地址和6个字节的IP地址。InetAddress内部对地址数字进行隐藏,用户不需要了解实现地址的细节,只需了解如何调用相应的方法即可。InetAddress类无构造方法,因此不能直接创建其对象,而是通过该类的静态方法创建一个InetAddress对象或InetAddress数组。InetAddress类常用方法如表15-1所示。表15-1 I
3、netAddress类常用方法15.1.1InetAddress类InetAddress类下述案例示例了InetAddress类的使用,代码如下所示。【代码15.1】InetAddressExample.javapackage com;import java.io.IOException;import .InetAddress;import .UnknownHostException;public class InetAddressExample public static void main(String args)try/获取本机地址信息InetAddress localIp=InetAd
4、dress.getLocalHost();System.out.println(localIp.getCanonicalHostName()=+localIp.getCanonicalHostName();System.out.println(localIp.getHostAddress()=+localIp.getHostAddress();System.out.println(localIp.getHostName()=+localIp.getHostName();System.out.println(localIp.toString()=+localIp.toString();Syste
5、m.out.println(localIp.isReachable(5000)=“+localIp.isReachable(5000);System.out.println(-);InetAddress类15.1.1/获取指定域名地址信息InetAddress baiduIp=InetAddress.getByName();System.out.println(baiduIp.getCanonicalHostName()=+baiduIp.getCanonicalHostName();System.out.println(baiduIp.getHostAddress()=+baiduIp.ge
6、tHostAddress();System.out.println(baiduIp.getHostName()=+baiduIp.getHostName();System.out.println(baiduIp.toString()=+baiduIp.toString();System.out.println(baiduIp.isReachable(5000)=+baiduIp.isReachable(5000);System.out.println(-);/获取指定原始IP地址信息InetAddress ip=InetAddress.getByAddress(new byte 127,0,0
7、,1);/InetAddress ip=InetAddress.getByName(127.0.0.1);System.out.println(ip.getCanonicalHostName()=“+ip.getCanonicalHostName();System.out.println(ip.getHostAddress()=“+ip.getHostAddress();System.out.println(ip.getHostName()=“+ip.getHostName();System.out.println(ip.toString()=+ip.toString();InetAddres
8、s类15.1.1System.out.println(ip.isReachable(5000)=“+ip.isReachable(5000);catch(UnknownHostException e)e.printStackTrace();catch(Exception e)e.printStackTrace();InetAddress类 上述代码分别获取本机、指定域名以及指定IP地址的InetAddress对象。其中,调用getLocalHost()可以获取本机InetAddress对象;调用getByName()可以获取指定域名的InetAddress对象;调用getByAddress()
9、可以获取指定IP地址的InetAddress对象,该方法的参数使用字节数组存放IP地址。也可以直接通过getByName()获取指定IP地址的InetAddress对象,此时,IP地址作为字符串即可。15.1.1程序运行结果如下所示:localIp.getCanonicalHostName()=192.168.0.101localIp.getHostAddress()=192.168.0.101localIp.getHostName()=shouchao-PClocalIp.toString()=shouchao-PC/192.168.0.101localIp.isReachable(5000
10、)=true-baiduIp.getCanonicalHostName()=180.97.33.108baiduIp.getHostAddress()=180.97.33.108baiduIp.getHostName()=baiduIp.toString()= 需要注意的是:在获得Internet上的域名所对应的地址信息时,需保证运行环境能访问Internet,否则将抛出UnknownHostException异常。15.1.1 URL(Uniform Resource Locator,统一资源定位器)表示互联网上某一资源的地址。资源可以是简单的文件或目录,也可以是对更为复杂对象的引用,例如对
11、数据库或搜索引擎的查询。URL是最为直观的一种网络定位方法,符合人们的语言习惯,且容易记忆。在通常情况下,URL可以由协议名、主机、端口和资源四个部分组成,其语法格式如下所示:protocol:/host:port/resourceName 其中:protocol是协议名,指明获取资源所使用的传输协议,例如http、ftp等,并使用冒号“:”与其他部分进行隔离;host是主机名,指定获取资源的域名,此部分由左边的双斜线“/”和右边的单斜线“/”或可选冒号“:”限制;port是端口,指定服务的端口号,是一个可选参数,由主机名左边的冒号“:”和右边的斜线“/”限制;resourceName是资源名
12、,指定访问的文件名或目录。例如:URL地址 http:/127.0.0.1:8080/student/index.jsp15.1.2U R L 类URL类 为了方便处理,Java将URL封装成URL类,通过URL对象记录下完整的URL信息。URL类常用方法及功能如表15-2所示。15.1.2U R L 类下述案例示例了根据指定的路径构造URL对象,并获取当前URL对象的相关属性。代码如下所示。【代码15.2】URLExample.javapackage com;import .MalformedURLException;import .URL;public class URLExample p
13、ublic static void main(String args)try URL mybook=new URL(http:/127.0.0.1:8080/student/index.jsp);System.out.println(协议protocol=+mybook.getProtocol();System.out.println(主机host=+mybook.getHost();System.out.println(端口port=+mybook.getPort();System.out.println(文件filename=+mybook.getFile();System.out.pri
14、ntln(锚ref=+mybook.getRef();System.out.println(查询信息query=+mybook.getQuery();System.out.println(路径path=+mybook.getPath();catch(MalformedURLException e)e.printStackTrace();程序运行结果如下所示:协议protocol=http主机host=127.0.0.1:8080端口port=-1文件filename=/student/index.jsp锚ref=null查询信息query=null路径path=/student/index.j
15、sp15.1.2U R L 类 URLConnection代表与URL指定的数据源的动态连接,该类提供一些比URL类更强大的服务器交互控制的方法,允许使用POST或PUT和其他HTTP请求方法将数据送回服务器。URLConnection是一个抽象类,其常用方法如表15-3所示。15.1.3URLConnection类URLConnection类下述案例示例了使用URLConnection类读取网络资源信息并打印,代码如下所示。【代码15.3】URLConnectionExample.javapackage com;import java.io.BufferedReader;import jav
16、a.io.IOException;import java.io.InputStreamReader;import .MalformedURLException;import .URL;import .URLConnection;public class URLConnectionExample public static void main(String args)try/构建一URL对象URL mybook=new URL(https:/ urlConn=mybook.openConnection();/设置请求属性,字符集是UTF-8urlConn.setRequestProperty(C
17、harset,GBK);/由URLConnection获取输入流,并构造BufferedReader对象BufferedReader br=new BufferedReader(new InputStreamReader(urlConn.getInputStream();15.1.3URLConnection类String inputLine;/循环读取并打印数据while(inputLine=br.readLine()!=null)System.out.println(inputLine);/关闭输入流br.close();catch(MalformedURLException e)e.pr
18、intStackTrace();catch(IOException e)e.printStackTrace();上述代码的运行结果是输出指定网页页面的源代码,此处不显示输出效果,读者可以自己调试程序查看。15.1.3URLConnection类 当URL地址中包含非西欧字符时,系统会将这些非西欧字符转换成特殊编码(如“%XX”格式),此种编码称为application/x-www-form-urlencoded MIME。在编程过程中如果涉及到普通字符串和application/x-www-form-urlencoded MIME字符串之间相互转换时,就需要使用URLDecoder和URLEn
19、coder两个工具类。URLDecoder工具类提供了一个decode(String s,String enc)静态方法,该方法将application/x-www-form-urlencoded MIME字符串转换成普通字符串;URLEncoder工具类提供了一个encode(String s,String enc)静态方法,该方法与decode()方法正好相反,能够将普通的字符串转换成application/x-www-form-urlencoded MIME字符串。15.1.4URLDecoder和URLEncoder类URLDecoder和URLEcoder类下述案例示例了URLDec
20、oder和URLEncoder两个工具类的使用,代码如下所示。【代码15.4】URLDecoderExample.javapackage com;import java.io.UnsupportedEncodingException;import .URLDecoder;import .URLEncoder;public class URLDecoderExample public static void main(String args)try/将普通字符串转换成application/x-www-form-urlencoded字符串String urlStr=URLEncoder.enco
21、de(面向对象程序设计Java,GBK);System.out.println(urlStr);/将application/x-www-form-urlencoded字符串 转换成普通字符串String keyWord=URLDecoder.decode(%C3%E6%CF%F2%B6%D4%CF%F3%B3%CC%D0%F2%C9%E8%BC%C6Java,GBK);System.out.println(keyWord);catch(UnsupportedEncodingException e)e.printStackTrace();程序运行结果如下:%C3%E6%CF%F2%B6%D4%C
22、F%F3%B3%CC%D0%F2%C9%E8%BC%C6Java面向对象程序设计Java15.1.4URLDecoder和URLEncoder类第第2 2节节part基于TCP的网路编程 TCP/IP通信协议是一种可靠的、双向的、持续的、点对点的网络协议。使用TCP/IP协议进行通信时,会在通信的两端各建立一个Socket(套接字),从而在通信的两端之间形成网络虚拟链路,其通信原理如图15.1所示。基于TCP的网路编程本节概述 Java对基于TCP的网络通信提供了封装,使用Socket对象封装了两端的通信端口。Socket对象屏蔽了网络的底层细节,例如媒体类型、信息包的大小、网络地址、信息的重
23、发等。Socket允许应用程序将网络连接当成一个IO流,既可以向流中写数据,也可以从流中读取数据。一个Socket对象可以用来建立Java的IO系统到Internet上的任何机器(包括本机)的程序连接。包中提供了网络编程所需的类,其中基于TCP协议的网络编程主要使用下面两种Socket:ServerSocket:是服务器套接字,用于监听并接收来自客户端的Socket连接;Socket:是客户端套接字,用于实现两台计算机之间的通信。基于TCP的网路编程15.2.1Socket类 使用Socket套接字可以较为方便地在网络上传递数据,从而实现两台计算机之间的通信。通常客户端使用Socket来连接指
24、定的服务器,Socket的两个常用构造方法如下:Socket(InetAddress|String host,int port):创建连接到指定远程主机和端口号的Socket对象,该构造方法没有指定本地地址和本地端口号,默认使用本地主机IP地址和系统动态分配的端口;Socket(InetAddress|String host,int port,InetAddress localAddr,int localPort):创建连接到指定远程主机和端口号的Socket对象,并指定本地IP地址和本地端口号,适用于本地主机有多个IP地址的情况。需要注意的是:上述两个Socket构造方法都声明抛出IOExc
25、eption异常,因此在创建Socket对象必须捕获或抛出异常。端口号建议采用注册端口(范围是102449151之间的数),通常应用程序使用该范围内的端口,以防止发生冲突。Socket类 例如:创建Socket对象 try Socket s=new Socket(192.168.1.128,9999);./Socket通信 catch(IOException e)e.printStackTrace();除了构造方法,Socket类常用的其他方法如表15-4所示。15.2.1Socket类 通常使用Socket进行网络通信的具体步骤如下:根据指定IP地址和端口号创建一个Socket对象;调用ge
26、tInputStream()方法或getOutputStream()方法打开连接到Socket的输入/输出流;客户端与服务器根据协议进行交互,直到关闭连接;关闭客户端的Socket。下述案例示例了创建客户端Socket的过程,代码如下所示。15.2.1Socket类【代码15.5】ClientSocketExample.javapackage com;import java.io.BufferedReader;import java.io.IOException;import java.io.InputStreamReader;import java.io.PrintStream;import
27、 .Socket;import .UnknownHostException;public class ClientSocketExample public static void main(String args)try/创建连接到本机、端口为9999的Socket对象Socket socket=new Socket(127.0.0.1,9999);/将Socket对应的输出流包装成PrintStreamPrintStream ps=new PrintStream(socket.getOutputStream();/往服务器发送信息ps.println(我喜欢Java);ps.flush();
28、/将Socket对应的输入流包装成BufferedReaderBufferedReader br=new BufferedReader(new InputStreamReader(socket.getInputStream();15.2.1Socket类/读服务器返回的信息并显示String line=br.readLine();System.out.println(来自服务器的数据:+line);/关闭br.close();ps.close();socket.close();catch(UnknownHostException e)e.printStackTrace();catch(IOEx
29、ception e)e.printStackTrace();上述代码先创建了一个连接到本机、端口为9999的Socket对象;再使用getOutputStream()获取Socket对象的输出流,用于往服务器发送信息;然后使用getInputStream()获取Socket对象的输入流,读取服务器返回的数据;最后关闭输入/输出流和Socket连接,释放所有的资源。15.2.1Socket类15.2.2ServletSocket类 ServerSocket是服务器套接字,运行在服务器端,通过指定端口主动监听来自客户端的Socket连接。当客户端发送Socket请求并与服务器端建立连接时,服务器将
30、验证并接收客户端的Socket,从而建立客户端与服务器之间的网络虚拟链路;一旦两端的实体之间建立了虚拟链路,就可以相互传送数据。ServerSocket类常用的构造方法如下:ServerSocket(int port):根据指定端口来创建一个ServerSocket对象;ServerSocket(int port,int backlog):创建一个ServerSocket对象,指定端口和连接队列长度,此时增加一个用来改变连接队列长度的参数backlog;ServerSocket(int port,int backlog,InetAddress localAddr):创建一个ServerSock
31、et对象,指定端口、连接队列长度和IP地址;当机器拥有多个IP地址时,才允许使用localAddr参数指定具体的IP地址。ServletSocket类 需要注意的是:ServerSocket类的构造方法都声明抛出IOException异常,因此在创建ServerSocket对象必须捕获或抛出异常。另外,在选择端口号时,建议选择注册端口(范围是102449151的数),通常应用程序使用这个范围内的端口,以防止发生冲突。下面几行代码示例了创建一个ServerSocket对象:try ServerSocket server=new ServerSocket(9999);catch(IOExcepti
32、on e)e.printStackTrace();ServerSocket类常用的其他方法如表15-5所示。15.2.2ServletSocket类通常使用ServerSocket进行网络通信的具体步骤如下:根据指定的端口号来实例化一个ServerSocket对象;调用ServerSocket对象的accept()方法接收客户端发送的Socket对象;调用Socket对象的getInputStream()/getOutputStream()方法来建立与客户端进行交互的IO流;服务器与客户端根据一定的协议交互,直到关闭连接;关闭服务器端的Socket;回到第2步,继续监听下一次客户端发送的Soc
33、ket请求连接。下述案例示例了创建服务器端ServerSocket的过程,代码如下所示。15.2.2ServletSocket类【15.6】ServerSocketExample.javapackage com;import java.io.BufferedReader;import java.io.IOException;import java.io.InputStreamReader;import java.io.PrintStream;import .ServerSocket;import .Socket;public class ServerSocketExample extends
34、Thread/声明一个ServerSocketServerSocket server;/计数int num=0;public ServerSocketExample()/创建ServerSocket,用于监听9999端口是否有客户端的Sockettry server=new ServerSocket(9999);catch(IOException e)e.printStackTrace();15.2.2ServletSocket类/启动当前线程,即执行run()方法this.start();System.out.println(服务器启动.);public void run()while(th
35、is.isAlive()try/接收客户端的SocketSocket socket=server.accept();/将Socket对应的输入流包装成BufferedReaderBufferedReader br=new BufferedReader(new InputStreamReader(socket.getInputStream();/读客户端发送的信息并显示String line=br.readLine();System.out.println(line);/将Socket对应的输出流包装成PrintStreamPrintStream ps=new PrintStream(socke
36、t.getOutputStream();/往客户端发送信息ps.println(您是第+(+num)+个访问服务器的用户!);ps.flush();15.2.2ServletSocket类/关闭br.close();ps.close();socket.close();catch(IOException e)/TODO Auto-generated catch blocke.printStackTrace();public static void main(String args)new ServerSocketExample();15.2.2ServletSocket类 上述代码服务器端是一个
37、多线程应用程序,能为多个客户提供服务。在ServerSocketExample()构造方法中,先创建一个用于监听9999端口的ServerSocket对象,再调用this.start()方法启动线程。在线程的run()方法中,先调用ServerSocket对象的accept()方法来接收客户端发送的Socket对象;再使用getInputStream()获取Socket对象的输入流,用于读取客户端发送的数据信息;然后使用getOutputStream()获取Socket对象的输出流,往客户端发送信息;最后关闭输入、输出流和Socket,释放所有资源。前面编写的客户端程序ClientSocket
38、Example与服务器端程序ServerSocketExample能够形成网络通信,运行时先运行服务器端ServerSocketExample应用程序,服务器端先显示如下提示:服务器启动.然后,运行客户端ClientSocketExample应用程序,此时服务器端又会增加打印一条信息:我喜欢Java 客户端应用程序会显示:来自服务器的数据:您是第1个访问服务器的用户!15.2.2ServletSocket类 一般服务器和客户端之间,使用Socket进行基于C/S架构的网络通信,程序设计的过程如下:服务器端通过某个端口监听是否有客户端发送Socket连接请求;客户端向服务器端发出一个Socket
39、连接请求;服务器端调用accept()接收客户端Socket并建立连接;通过调用Socket对象的getInputStream()/getOutputStream()方法进行IO流操作,服务器与客户端之间进行信息交互;关闭服务器端和客户端的Socket。15.2.2ServletSocket类15.2.3聊 天 室 基于TCP网络编程的典型应用就是聊天室,下述内容使用Socket和ServerSocket实现多人聊天的聊天室程序。聊天室程序是基于C/S架构,分客户端代码和服务器端代码。其中,客户端是一个窗口应用程序,代码如下。聊天室【代码15.7】ChatClient.javapackage
40、com;import java.awt.BorderLayout;import java.awt.event.ActionEvent;import java.awt.event.ActionListener;import java.io.BufferedReader;import java.io.IOException;import java.io.InputStreamReader;import java.io.PrintWriter;import .Socket;import .UnknownHostException;import javax.swing.*;/聊天室客户端public
41、class ChatClient extends JFrame Socket socket;PrintWriter pWriter;BufferedReader bReader;JPanel panel;JScrollPane sPane;JTextArea txtContent;15.2.3聊 天 室JLabel lblName,lblSend;JTextField txtName,txtSend;JButton btnSend;public ChatClient()super(聊天室);txtContent=new JTextArea();/设置文本域只读txtContent.setEdi
42、table(false);sPane=new JScrollPane(txtContent);lblName=new JLabel(昵称:);txtName=new JTextField(5);lblSend=new JLabel(发言:);txtSend=new JTextField(20);btnSend=new JButton(发送);panel=new JPanel();panel.add(lblName);panel.add(txtName);panel.add(lblSend);panel.add(txtSend);panel.add(btnSend);15.2.3聊 天 室thi
43、s.add(panel,BorderLayout.SOUTH);this.add(sPane);this.setSize(500,300);this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);try/创建一个套接字socket=new Socket(127.0.0.1,9999);/创建一个往套接字中写数据的管道,即输出流,给服务器发送信息pWriter=new PrintWriter(socket.getOutputStream();/创建一个从套接字读数据的管道,即输入流,读服务器的返回信息bReader=new BufferedRead
44、er(new InputStreamReader(socket.getInputStream();catch(UnknownHostException e)e.printStackTrace();catch(IOException e)e.printStackTrace();/注册监听btnSend.addActionListener(new ActionListener()public void actionPerformed(ActionEvent e)15.2.3聊 天 室/获取用户输入的文本String strName=txtName.getText();String strMsg=t
45、xtSend.getText();if(!strMsg.equals()/通过输出流将数据发送给服务器pWriter.println(strName+说:+strMsg);pWriter.flush();/清空文本框txtSend.setText(););/启动线程new GetMsgFromServer().start();/接收服务器的返回信息的线程class GetMsgFromServer extends Thread public void run()while(this.isAlive()try 15.2.3聊 天 室String strMsg=bReader.readLine()
46、;if(strMsg!=null)/在文本域中显示聊天信息txtContent.append(strMsg+n);Thread.sleep(50);catch(Exception e)e.printStackTrace();public static void main(String args)/创建聊天室客户端窗口实例,并显示new ChatClient().setVisible(true);15.2.3聊 天 室 上述代码在构造方法中先创建客户端图形界面,并创建一个Socket对象连接服务器,然后获取Socket对象的输入流和输出流,用于与服务器进行信息交互,输出流可以给服务器发送信息,输
47、入流可以读取服务器的返回信息。再对“发送”按钮添加监听事件处理,当用户单击“发送”按钮时,将用户在文本框中输入的数据通过输出流写到Socket中,实现将信息发送给服务器。GetMsgFromServer是一个用于不断循环接收服务器的返回信息的线程,只要接收到服务器的信息,就将该信息在窗口的文本域中显示。注意在构造方法的最后创建一个GetMsgFromServer线程实例并启动。聊天室的服务器端代码如下所示。15.2.3聊 天 室【代码15.8】ChatServer.javapackage com;import java.io.BufferedReader;import java.io.IOEx
48、ception;import java.io.InputStreamReader;import java.io.PrintWriter;import .ServerSocket;import .Socket;import java.text.SimpleDateFormat;import java.util.ArrayList;import java.util.Date;import java.util.LinkedList;/聊天室服务器端public class ChatServer/声明服务器端套接字ServerSocketServerSocket serverSocket;/输入流列表
49、集合ArrayList bReaders=new ArrayList();/输出流列表集合ArrayList pWriters=new ArrayList();15.2.3聊 天 室/聊天信息链表集合LinkedList msgList=new LinkedList();public ChatServer()try/创建服务器端套接字ServerSocket,在28888端口监听serverSocket=new ServerSocket(9999);catch(IOException e)e.printStackTrace();/创建接收客户端Socket的线程实例,并启动new Accept
50、SocketThread().start();/创建给客户端发送信息的线程实例,并启动new SendMsgToClient().start();System.out.println(服务器已启动.);/接收客户端Socket套接字线程class AcceptSocketThread extends Thread public void run()while(this.isAlive()try 15.2.3聊 天 室/接收一个客户端Socket对象Socket socket=serverSocket.accept();/建立该客户端的通信管道if(socket!=null)/获取Socket对