3. UDP数据报通信
UDP通信中,需要建立一个DatagramSocket,与Socket不同,它不存在“连接”的概念,取而代之的是一个数据报包——DatagramPacket。这个数据报包必须知道自己来自何处,以及打算去哪里。所以本身必须包含IP地址、端口号和数据内容。
3.1 示例程序——用UDP实现的聊天程序
用UDP协议通信不需要使用服务器,所以用于聊天的程序只要写一个,分别在不同的机器上运行就可以了,而无须写成服务端和客户端两种形式。
例9. 用UDP实现的聊天程序示例。
package Net.UDPchat; import java.awt.BorderLayout; import java.awt.Container; import java.awt.FlowLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.IOException; import java.net.*; import javax.swing.*; public class UDPchat implements ActionListener,Runnable { JFrame jf; JLabel jl1,jl2,jl3; JTextField recPortText,sendPortText,IPText,msgText; JButton startBtn,sendBtn; JTextArea showArea; JScrollPane jsp; JPanel jp1,jp2; Container con; Thread thread = null; DatagramSocket receiveSocket,sendSocket; DatagramPacket receivePack,sendPack; private InetAddress sendIP; private int sendPort,recPort; private byte inBuf[],outBuf[]; private static final int BUFSIZE = 1024; public UDPchat(){ jf = new JFrame("聊天————UDP协议"); jl1 = new JLabel("接收端口号:"); jl2 = new JLabel("发送端口号:"); jl3 = new JLabel("对方的地址:"); recPortText = new JTextField(); recPortText.setColumns(5); sendPortText = new JTextField(); sendPortText.setColumns(5); IPText = new JTextField(); IPText.setColumns(8); msgText = new JTextField(); msgText.setColumns(40); msgText.setEditable(false); msgText.addActionListener(this); startBtn = new JButton("开始"); startBtn.addActionListener(this); sendBtn = new JButton("发送"); sendBtn.setEnabled(false); sendBtn.addActionListener(this); showArea = new JTextArea(); showArea.setEditable(false); showArea.setLineWrap(true); //自动换行 jsp = new JScrollPane(showArea); jp1 = new JPanel(); jp2 = new JPanel(); jp1.setLayout(new FlowLayout()); jp2.setLayout(new FlowLayout()); jp1.add(jl1); jp1.add(recPortText); jp1.add(jl2); jp1.add(sendPortText); jp1.add(jl3); jp1.add(IPText); jp1.add(startBtn); jp2.add(msgText); jp2.add(sendBtn); con = jf.getContentPane(); con.add(jp1, BorderLayout.NORTH); con.add(jsp, BorderLayout.CENTER); con.add(jp2, BorderLayout.SOUTH); jf.setSize(600, 400); jf.setLocation(300, 200); jf.setVisible(true); jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } //在线程中接收数据 public void run() { String str; while(true){ try { receiveSocket.receive(receivePack); str = new String(receivePack.getData(),0,receivePack.getLength()); showArea.append("对方说:"+str+"\n"); } catch (IOException e) { showArea.append("接收数据出错!\n"); } } } public void actionPerformed(ActionEvent e) { try { if(e.getSource()==startBtn){ inBuf = new byte[BUFSIZE]; sendPort = Integer.parseInt(sendPortText.getText()); recPort = Integer.parseInt(recPortText.getText()); sendIP = InetAddress.getByName(IPText.getText()); sendSocket = new DatagramSocket(); //创建接收数据包 receivePack = new DatagramPacket(inBuf,BUFSIZE); //指定接收数据的端口 receiveSocket = new DatagramSocket(recPort); //创建线程准备接收对方的信息 thread = new Thread(this); thread.setPriority(Thread.MIN_PRIORITY); thread.start(); msgText.setEditable(true); sendBtn.setEnabled(true); startBtn.setEnabled(false); }else{ //按下了“发送”按钮或回车键 outBuf = msgText.getText().getBytes(); //组装要发送的数据 sendPack = new DatagramPacket(outBuf,outBuf.length,sendIP,sendPort); //发送数据 sendSocket.send(sendPack); showArea.append("我说:"+msgText.getText()+"\n"); msgText.setText(null); } } catch (NumberFormatException e1) { e1.printStackTrace(); } catch (UnknownHostException e1) { showArea.append("无法连接到指定地址\n"); } catch (SocketException e1) { showArea.append("无法打开指定端口\n"); } catch (IOException e1) { showArea.append("发送数据失败!\n"); } } public static void main(String[] args) { new UDPchat(); } }
4. Java网络编程的新特性(jdk1.7)
4.1 轻量级的HTTP服务
例10. HTTP服务实现的实例。
package Net.http; import java.io.IOException; import java.net.InetSocketAddress; import java.io.*; import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpHandler; import com.sun.net.httpserver.HttpServer; import com.sun.net.httpserver.spi.HttpServerProvider; public class HTTPServer { public static void main(String[] args) throws Exception { //通过HttpServerProvider的静态方法provider,获取HttpServerProvider的对象 HttpServerProvider httpServerProvider = HttpServerProvider.provider(); //通过InetSocketAddress类,绑定8080作为服务端口 InetSocketAddress addr = new InetSocketAddress(8088); //调用createHttpServer,创建HTTP服务 HttpServer httpServer = httpServerProvider.createHttpServer(addr, 1); //指定HTTP服务的路径 httpServer.createContext("/myapp/", new MyHttpHandler()); httpServer.setExecutor(null); //启动服务执行 httpServer.start(); //输出服务开始的信息 System.out.println("started"); } static class MyHttpHandler implements HttpHandler { //声明抛出异常 public void handle(HttpExchange httpExchange) throws IOException { // 返回客户端的一个字符串 String response = "This is a simple HTTP Server!"; // 返回HTTP访问的状态码 httpExchange.sendResponseHeaders(200, response.length()); //将返回结果信息输出到客户端 OutputStream out = httpExchange.getResponseBody(); out.write(response.getBytes()); out.close(); } } }
上述代码是一个可直接运行的Java程序,启动程序运行后,在浏览地址栏中输入访问地址:http://localhost:8088/myapp/,就可以打开页面。
httpExchange.sendResponseHeaders(int code, int length);其中的code是Http响应的返回值,比如404、403、500等。length指的是response的长度,以字节为单位。
5. IPv6网络应用程序的开发
5.1 获取本机IPv6地址
对地址进行过滤,选出确实可用的地址。下述代码实现了这一功能,思路是遍历网络接口的各个地址,直至找到符合要求的地址。
例11. 通过Java程序获取本机的IPv6地址。
package Net.http; import java.net.*; import java.util.Enumeration; import java.io.IOException; public class GetIPv6Add { public static String getLocalIPv6Address() throws IOException{ InetAddress inetAddress = null; //定义一个枚举变量,列出所有的网卡信息 Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces(); outer: //遍历所有的网卡,从中找到包含IPv6的地址信息 while(networkInterfaces.hasMoreElements()){ Enumeration<InetAddress> inetAds = networkInterfaces.nextElement().getInetAddresses(); while(inetAds.hasMoreElements()){ //判断是否是IPv6地址 if(inetAddress instanceof Inet6Address && !isReservedAddr(inetAddress)){ break outer; } } } String ipAddr = inetAddress.getHostAddress(); //过滤掉非地址信息的符号,有些Windows平台上,IP地址的版本信息后面跟着一个%,因而要去掉 int index = ipAddr.indexOf("%"); if(index>0){ ipAddr = ipAddr.substring(0, index); } return ipAddr; } /** * 过滤本机特殊IP地址 * @param inetAddr(传入的IP地址作为参数) * @return(对传入的IP地址进行判断,返回判断结果 */ private static boolean isReservedAddr(InetAddress inetAddr){ if(inetAddr.isAnyLocalAddress() || inetAddr.isLinkLocalAddress() || inetAddr.isLoopbackAddress()){ return true; } return false; } public static void main(String[] args) { try { System.out.println(getLocalIPv6Address()); } catch (IOException e) { e.printStackTrace(); } } }
注意:在windows平台上,取得的IPv6地址后面可能跟了一个百分号加数字。这里的数字是本机网络适配器的编号。这个后缀并不是IPv6标准地址的一部分,可以去除。