// 根据主机名来获取对应的InetAddress实例 InetAddress ip = InetAddress.getByName("www.oneedu.cn"); // 判断是否可达 System.out.println("oneedu是否可达:" + ip.isReachable(2000)); // 获取该InetAddress实例的IP字符串 System.out.println(ip.getHostAddress()); // 根据原始IP地址(字节数组形式)来获取对应的InetAddress实例 InetAddress local = InetAddress .getByAddress(new byte[] { 127, 0, 0, 1 }); System.out.println("本机是否可达:" + local.isReachable(5000)); // 获取该InetAddress实例对应的全限定域名 System.out.println(local.getCanonicalHostName());
1.2 URLDecoder和URLEncoder
这两个类可以别用于将application/x-www-form-urlencoded MIME类型的字符串转换为普通字符串,将普通字符串转换为这类特殊型的字符串。使用URLDecoder类的静态方法decode()用于解码,URLEncoder类的静态方法encode()用于编码。具体使用方法如下。
// 将application/x-www-form-urlencoded字符串 // 转换成普通字符串 String keyWord = URLDecoder.decode("%E6%9D%8E%E5%88%9A+j2ee", "UTF-8"); System.out.println(keyWord); // 将普通字符串转换成 // application/x-www-form-urlencoded字符串 String urlStr = URLEncoder.encode("ROR敏捷开发最佳指南", "GBK"); System.out.println(urlStr);1.3 URL和URLConnection
URL可以被认为是指向互联网资源的“指针”,通过URL可以获得互联网资源相关信息,包括获得URL的InputStream对象获取资源的信息,以及一个到URL所引用远程对象的连接URLConnection。
URLConnection对象可以向所代表的URL发送请求和读取URL的资源。通常,创建一个和URL的连接,需要如下几个步骤:
a. 创建URL对象,并通过调用openConnection方法获得URLConnection对象;
b. 设置URLConnection参数和普通请求属性;
c. 向远程资源发送请求;
d. 远程资源变为可用,程序可以访问远程资源的头字段和通过输入流来读取远程资源返回的信息。
这里需要重点讨论一下第三步:如果只是发送GET方式请求,使用connect方法建立和远程资源的连接即可;如果是需要发送POST方式的请求,则需要获取URLConnection对象所对应的输出流来发送请求。这里需要注意的是,由于GET方法的参数传递方式是将参数显式追加在地址后面,那么在构造URL对象时的参数就应当是包含了参数的完整URL地址,而在获得了URLConnection对象之后,就直接调用connect方法即可发送请求。
而POST方法传递参数时仅仅需要页面URL,而参数通过需要通过输出流来传递。另外还需要设置头字段。以下是两种方式的代码。
// 1. 向指定URL发送GET方法的请求 String urlName = url + "?" + param; URL realUrl = new URL(urlName); // 打开和URL之间的连接 URLConnection conn = realUrl.openConnection(); // 设置通用的请求属性 conn.setRequestProperty("accept", "*/*"); conn.setRequestProperty("connection", "Keep-Alive"); conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)"); // 建立实际的连接 conn.connect(); // 2. 向指定URL发送POST方法的请求 URL realUrl = new URL(url); // 打开和URL之间的连接 URLConnection conn = realUrl.openConnection(); // 设置通用的请求属性 conn.setRequestProperty("accept", "*/*"); conn.setRequestProperty("connection", "Keep-Alive"); conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)"); // 发送POST请求必须设置如下两行 conn.setDoOutput(true); conn.setDoInput(true); // 获取URLConnection对象对应的输出流 out = new PrintWriter(conn.getOutputStream()); // 发送请求参数 out.print(param);
另外需要注意的是,如果既需要读取又需要发送,一定要先使用输出流,再使用输入流。因为远程资源不会主动向本地发送请求,必须要先请求资源。
2. 基于TCP协议的网络编程
TCP协议是一种可靠的通络协议,通信两端的Socket使得它们之间形成网络虚拟链路,两端的程序可以通过虚拟链路进行通讯。Java使用socket对象代表两端的通信端口,并通过socket产生的IO流来进行网络通信。
// 创建一个ServerSocket,用于监听客户端Socket的连接请求 ServerSocket ss = new ServerSocket(30000); // 采用循环不断接受来自客户端的请求 while (true) { // 每当接受到客户端Socket的请求,服务器端也对应产生一个Socket Socket s = ss.accept(); // 将Socket对应的输出流包装成PrintStream PrintStream ps = new PrintStream(s.getOutputStream()); // 进行普通IO操作 ps.println("您好,您收到了服务器的新年祝福!"); // 关闭输出流,关闭Socket ps.close(); s.close(); }
2.2 Socket
Socket socket = new Socket("127.0.0.1", 30000); // 将Socket对应的输入流包装成BufferedReader BufferedReader br = new BufferedReader(new InputStreamReader( socket.getInputStream())); // 进行普通IO操作 String line = br.readLine(); System.out.println("来自服务器的数据:" + line); // 关闭输入流、socket br.close(); socket.close();
2.3 使用多线程
在复杂的通讯中,使用多线程非常必要。对于服务器来说,它需要接收来自多个客户端的连接请求,处理多个客户端通讯需要并发执行,那么就需要对每一个传过来的Socket在不同的线程中进行处理,每条线程需要负责与一个客户端进行通信。以防止其中一个客户端的处理阻塞会影响到其他的线程。对于客户端来说,一方面要读取来自服务器端的数据,另一方面又要向服务器端输出数据,它们同样也需要在不同的线程中分别处理。具体代码如下,服务器端:
public class MyServer { // 定义保存所有Socket的ArrayList public static ArrayList<Socket> socketList = new ArrayList<Socket>(); public static void main(String[] args) throws IOException { ServerSocket ss = new ServerSocket(30000); while (true) { // 此行代码会阻塞,将一直等待别人的连接 Socket s = ss.accept(); socketList.add(s); // 每当客户端连接后启动一条ServerThread线程为该客户端服务 new Thread(new ServerThread(s)).start(); } } }客户端:
public class MyClient { public static void main(String[] args) throws IOException { Socket s = s = new Socket("127.0.0.1", 30000); // 客户端启动ClientThread线程不断读取来自服务器的数据 new Thread(new ClientThread(s)).start(); // 获取该Socket对应的输出流 PrintStream ps = new PrintStream(s.getOutputStream()); String line = null; // 不断读取键盘输入 BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); while ((line = br.readLine()) != null) { // 将用户的键盘输入内容写入Socket对应的输出流 ps.println(line); } } }
public interface YeekuProtocol { // 定义协议字符串的长度 int PROTOCOL_LEN = 2; // 下面是一些协议字符串,服务器和客户端交换的信息 // 都应该在前、后添加这种特殊字符串。 String MSG_ROUND = "§γ"; String USER_ROUND = "∏∑"; String LOGIN_SUCCESS = "1"; String NAME_REP = "-1"; String PRIVATE_ROUND = "【"; String SPLIT_SIGN = "※"; }
在字段时可以加上这些字符,如下代码:
while (true) { String userName = JOptionPane.showInputDialog(tip + "输入用户名"); // 将用户输入的用户名的前后增加协议字符串后发送 ps.println(YeekuProtocol.USER_ROUND + userName+ YeekuProtocol.USER_ROUND); // 读取服务器的响应 String result = brServer.readLine(); // 如果用户重复,开始下次循环 if (result.equals(YeekuProtocol.NAME_REP)) { tip = "用户名重复!请重新"; continue; } // 如果服务器返回登陆成功,结束循环 if (result.equals(YeekuProtocol.LOGIN_SUCCESS)) { break; } }
收到发送来的字段时候,也再次拆分成所需要的部分,如下代码:
if (line.startsWith(YeekuProtocol.PRIVATE_ROUND) && line.endsWith(YeekuProtocol.PRIVATE_ROUND)) { // 得到真实消息 String userAndMsg = getRealMsg(line); // 以SPLIT_SIGN来分割字符串,前面部分是私聊用户,后面部分是聊天信息 String user = userAndMsg.split(YeekuProtocol.SPLIT_SIGN)[0]; String msg = userAndMsg.split(YeekuProtocol.SPLIT_SIGN)[1]; // 获取私聊用户对应的输出流,并发送私聊信息 Server.clients.get(user).println( Server.clients.getKeyByValue(ps) + "悄悄地对你说:" + msg); }
3. UDP协议的网络编程 UDP协议是一种不可靠的网络协议,它在通讯实例的两端个建立一个Socket,但这两个Socket之间并没有虚拟链路,这两个Socket只是发送和接受数据报的对象,Java提供了DatagramSocket对象作为基于UDP协议的Socket,使用DatagramPacket代表DatagramSocket发送和接收的数据报。
//接收端的DatagaramSocket内部包含一个空的数组,接收传递过来的数据报中的数组信息。可以通过DatagaramSocket对象的getData()方法返回的数组来获取其中的包含的数组。 Private DatagaramSocket udpSocket=new DatagaramSocket(buf,buf.length); //发送端的DatagaramSocket内部包含一个将要传递的数组,同时需要包含目标IP和端口。如果初始化时传递的数组参数是空,可以通过调用DatagaramSocket对象的setData()方法设置内容。 Private DatagaramSocket udpSocket=new DatagaramSocket(buf,buf.length,IP,PORT); udpSocket。setData(outBuf);作为这两个方法的参数,作用和构造不同的。作为接收方法中的参数,DatagramPacket中的数组一个空的数组,用来存放接收到的DatagramPacket对象中的数组;而作为发送方法参数,DatagramPacket本身含有了目的端的IP和端口,以及存储了要发送内容的指定了长度的字节型数组。
另外,DatagramPacket对象还提供了setData(Byte[] b)和Byte[] b= getData()方法,用于设置DatagramPacket中包含的数组内容和获得其中包含数组的内容。
使用TCP和UDP通讯的编码区别:
a. 在TCP中,目标IP和端口由Socket指定包含;UDP中,目标IP由DatagramPacket包含指定,DatagramSocket只负责发送和接受。
b. 在TCP中,通讯是通过Socket获得的IO流来实现;在UDP中,则通过DatagramSocket的send和receive方法。
// 创建用于发送、接收数据的MulticastSocket对象 // 因为该MulticastSocket对象需要接收,所以有指定端口 socket = new MulticastSocket(BROADCAST_PORT); broadcastAddress = InetAddress.getByName(BROADCAST_IP); // 将该socket加入指定的多点广播地址 socket.joinGroup(broadcastAddress); // 设置本MulticastSocket发送的数据报被回送到自身 socket.setLoopbackMode(false); // 初始化发送用的DatagramSocket,它包含一个长度为0的字节数组 outPacket = new DatagramPacket(new byte[0], 0, broadcastAddress, BROADCAST_PORT);
4. 使用代理服务器
Java中可以使用Proxy直接创建连接代理服务器,具体使用方法如下:
public class ProxyTest { Proxy proxy; URL url; URLConnection conn; // 从网络通过代理读数据 Scanner scan; PrintStream ps; // 下面是代理服务器的地址和端口, // 换成实际有效的代理服务器的地址和端口 String proxyAddress = "202.128.23.32"; int proxyPort; // 下面是你试图打开的网站地址 String urlStr = "http://www.oneedu.cn"; public void init() { try { url = new URL(urlStr); // 创建一个代理服务器对象 proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress( proxyAddress, proxyPort)); // 使用指定的代理服务器打开连接 conn = url.openConnection(proxy); // 设置超时时长。 conn.setConnectTimeout(5000); scan = new Scanner(conn.getInputStream()); // 初始化输出流 ps = new PrintStream("Index.htm"); while (scan.hasNextLine()) { String line = scan.nextLine(); // 在控制台输出网页资源内容 System.out.println(line); // 将网页资源内容输出到指定输出流 ps.println(line); } } catch (MalformedURLException ex) { System.out.println(urlStr + "不是有效的网站地址!"); } catch (IOException ex) { ex.printStackTrace(); } // 关闭资源 finally { if (ps != null) { ps.close(); } } } }
5. 编码中的问题总结
e. 在JFrame类中,一般不要将自己的代码写进main方法中,可以将代码写到自定义的方法中,然后在main方法中调用。
注: 博文来自对《疯狂java讲义》的学习。