我们先来看看计算机网络主要功能:资源共享;信息传输和集中处理;负载均衡和分布处理;综合信息服务。
实际上Java的网络编程就是服务器通过ServerSocket建立监听,客户端通过Socket连接到指定服务器后,通信双方就可以通过IO流进行通信了。
1.认识网络编程
计算机网络中实现通信的约定被称为通信协议,通信协议负责对传输速率、传输代码、代码结构、传输控制步骤、出错控制等制定处理标准。
计算机网络的OSI模型(各种计算机网络的参考标准)如下:
1)上层协议
http,ftp,https
2)底层协议
TCP/IP
IP(Internet Protocol,互联网协议):支持网间互联的数据报协议。
TCP(Transmission Control Protocol,传输控制协议):安全度更高,一般没有数据丢失。
UDP:效率更快,安全性相对低,可能会有数据丢失。
拓展: HTTP头:只是在TCP协议里面附带的一段内容。
通信协议组成:语义部分、语法部分、变换规则。
总结:所谓的协议就是在数据传输基础上封装自己的文本内容。
3)Java里的TCP/IP实现——Socket
Socket实现了TCP/IP,TCP/IP只是一份文档,各个不同的语言根据自己的需求对这个协议进行不同的解析,而在JAVA里对TCP/IP的解析实现就是Socket。
4)IP地址和端口号
IP地址:32位整数(4个8位二进制数),NIC统一负责全球IP地址的规划、管理,而Intel NIC、APNIC、RIPE三大网络信息中心具体负责美国及其他地区的IP地址分配,APNIC(总部在日本东京大学)负责亚太地区的IP管理,我国申请IP地址也要通过APNIC。
IP地址被分为A、B、C、D、E五类:
A类:10.0.0.0-10.255.255.255
B类:172.16.0.0-172.31.255.255
C类:192.168.0.0-192.168.255.255
端口号:16位整数,0-65535
公认端口:0-1023
注册端口:1024-49151
动态和私有端口:49152-65535
2.Java的网络支持
java.net包下URL和URLConnection等类提供了以编程方式访问web服务的功能,URLDecoder和URLEncoder提供了普通字符串和application/x-www-form-urlencoded MIME字符串相互转换的静态方法。
1)InetAddress
Java提供了InetAddress类表示IP地址
public class InetAddressTest { public static void main(String[] args) throws Exception { InetAddress ip = InetAddress.getByName("www.baidu.com"); // 根据主机名来获取对应的InetAddress实例 System.out.println("baidu是否可达:" + ip.isReachable(2000)); // 判断是否可达 System.out.println(ip.getHostAddress()); // 获取该InetAddress实例的IP字符串 InetAddress local = InetAddress.getByAddress(new byte[]{127,0,0,1}); // 根据原始IP地址来获取对应的InetAddress实例 System.out.println("本机是否可达:" + local.isReachable(5000)); System.out.println(local.getCanonicalHostName()); // 获取该InetAddress实例对应的全限定域名 } }
2)URLDecoder和URLEncoder
用于普通字符串和application/x-www-form-urlencoded MIME字符串之间的相互转换
public class URLDecoderTest { public static void main(String[] args) throws Exception { // 将application/x-www-form-urlencoded字符串转换成普通字符串 String keyWord = URLDecoder.decode("CSDN%E5%8D%9A%E5%AE%A2", "utf-8"); System.out.println(keyWord); // 将普通字符串转换成application/x-www-form-urlencoded字符串 String urlStr = URLEncoder.encode("CSDN博客" , "utf-8"); System.out.println(urlStr); } /** * 运行结果: * CSDN博客 * CSDN%B2%A9%BF%CD */ }
3)URL、URLConnection和URLPermission
URL:统一资源定位符,包含一个可打开到达该资源的输入流。
Java中还提供了URI:统一资源标识符,不能用于定位任何资源,只能解析,URL是URI的特例。
URLConnection:应用程序与URL之间的通信连接
HttpURLConnection:URL与URL之间的HTTP连接
程序可以通过URLConnection实例向URL发送请求、读取URL引用的资源。
如果既要使用输入流读取URLConnection响应的内容,又要使用输出流发送请求参数,则一定要先使用输出流,再使用输入流。
URL url = new URL(path); // // path:定义下载资源的路径 HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setConnectTimeout(5000); // 设置超时时间 conn.setRequestMethod("GET"); // 设置请求方法 //设置通讯的头部信息,设置访问方式等 conn.setRequestProperty("Accept-Language", "zh-CN"); conn.setRequestProperty("Charset", "UTF-8"); conn.connect(); // 建立实际的连接
下面程序示范了向Web站点发送GET请求、POST请求,并从Web站点取得响应:
public class GetPostTest { /** * 测试发送GET请求和POST请求 * @param args */ public static void main(String args[]) { // 发送GET请求 String s = GetPostTest.sendGet("http://localhost:8888/GetPostServer/test.jsp", null); System.out.println(s); // 发送POST请求 String s1 = GetPostTest.sendPost("http://localhost:8888/GetPostServer/login.jsp", "name=zhangsan&pass=123"); System.out.println(s1); } /** * 向指定URL发送GET方法的请求 * @param url 发送请求的URL * @param param 请求参数,格式满足name1=value1&name2=value2的形式 * @return URL所代表远程资源的响应 */ public static String sendGet(String url , String param) { String result = ""; String urlName = url + "?" + param; try { URL realUrl = new URL(urlName); URLConnection conn = realUrl.openConnection(); // 打开和URL之间的连接 // 设置通用的请求属性 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(); // 建立实际的连接 Map<String, List<String>> map = conn.getHeaderFields(); // 获取所有响应头字段 // 遍历所有的响应头字段 for (String key : map.keySet()) { System.out.println(key + "=" + map.get(key)); } try( // 定义BufferedReader输入流来读取URL的响应 BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream() , "utf-8"))) { String line; while ((line = in.readLine())!= null) { result += "\n" + line; } } } catch(Exception e) { System.out.println("发送GET请求出现异常!" + e); e.printStackTrace(); } return result; } /** * 向指定URL发送POST方法的请求 * @param url 发送请求的URL * @param param 请求参数,格式应该满足name1=value1&name2=value2的形式 * @return URL所代表远程资源的响应 */ public static String sendPost(String url , String param) { String result = ""; try { URL realUrl = new URL(url); URLConnection conn = realUrl.openConnection(); // 打开和URL之间的连接 // 设置通用的请求属性 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); try( // 获取URLConnection对象对应的输出流 PrintWriter out = new PrintWriter(conn.getOutputStream())) { // 发送请求参数 out.print(param); // flush输出流的缓冲 out.flush(); } try( // 定义BufferedReader输入流来读取URL的响应 BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream() , "utf-8"))) { String line; while ((line = in.readLine())!= null) { result += "\n" + line; } } } catch(Exception e) { System.out.println("发送POST请求出现异常!" + e); e.printStackTrace(); } return result; } }
Web站点如下:
test.jsp:
<%@ page contentType="text/html; charset=UTF-8" language="java" errorPage="" %> <!DOCTYPE> <html> <head> <title> 测试页面 </title> <meta name="website" content="http://www.crazyit.org"/> </head> <body> 服务器时间为:<%=new java.util.Date()%> </body> </html>
login.jsp:
<%@ page contentType="text/html; charset=UTF-8" language="java" errorPage="" %> <% request.setCharacterEncoding("UTF-8"); String name = request.getParameter("name"); String pass = request.getParameter("pass"); if(name.equals("zhangsan")&& pass.equals("123")) { out.println("登录成功!"); } else { out.println("登录失败!"); } %>
3.基于TCP协议的网络编程
TCP/IP协议是一种可靠协议,它在通信两端各建立一个Socket,从而在通信两端之间形成网络虚拟链路进行通信,Java使用Socket对象来代表两端的通信接口,并通过Socket产生IO流来进行网络通信。
1)使用ServerSocket创建TCP服务端
// 创建一个ServerSocket,监听客户端Socket的连接请求 ServerSocket socket = new ServerSocket(10000); // 端口号推荐使用1024以上 // 采用循环不断的接受来自客户端的请求 while(true){ Socket s = socket.accept(); // 下面就能使用Socket进行通信了 ... }
注意:每个TCP连接有两个Socket,当ServerSocket使用完毕后应使用ServerSocket的close()方法关闭该ServerSocket。
2)使用Socket进行通信
客户端可以使用Socket的构造方法了连接到指定服务器
Socket提供了如下两个方法来获取输入流和输出流:
InputStream getInputStream():返回该Socket对象对应的输入流
OutputStream getOutputStream():返回该Socket对象对应的输出流
Socket其他常用方法:
设置超时时长:setSoTimeout(int timeout)
为Socket连接服务器指定时长,Socket构造方法里没有提供指定时长的参数,所以需要使用下面代码进行操作:
// 创建一个无连接的Socket Socket socket = new Socket(); // 让该Socket连接到远程服务器,如果经过10秒还没有连接上,则认为连接超时 socket.connect(new InetAddress(host, port), 10000);
下面以最简单的网络通信为例说明基于TCP协议的网络通信。
服务器端:
public class Server { public static void main(String[] args) throws IOException { ServerSocket socket = new ServerSocket(10000); while (true) { Socket s = socket.accept(); PrintStream ps = new PrintStream(s.getOutputStream()); // 将Socket对应的输出流包装成PrintStream ps.println("您收到了服务器的消息!"); // 进行普通IO操作 ps.close(); s.close(); } } }
客户端:
public class Client { public static void main(String[] args) throws IOException { Socket socket = new Socket("127.0.0.1" , 10000); BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream())); String line = br.readLine(); // 进行普通IO操作 System.out.println("来自服务器的数据:" + line); br.close(); socket.close(); } }
4.基于UDP协议的网络编程
1)使用DatagramSocket发送、接受数据
UDP(User Datagram Protocol,用户数据报协议):不可靠网络协议,面向非连接,通信效率较高。
UDP在通信两端各建立一个Socket,但这两个Socket之间并没有虚拟链路,这两个Socket只是发送、接受数据的对象。
Java提供了DatagramSocket(本身只是码头,不维护状态,不能产生IO流,唯一作用就是接受和发送数据报)作为基于UDP协议的Socket,使用DatagramPacket代表DatagramSocket发送、接受的数据报。
DatagramSocket发送和接受的数据都是通过DatagramPacket对象完成的。
UDP和TCP对比如下:
TCP:可靠,传输大小无限制,需要建立连接,差错控制开销大。
UDP:不可靠,传输大小限制在64KB以下,不需要建立连接,差错控制开销小。
接受数据:
DatagramSocket socket = new DatagramSocket(port); // port:端口号 DatagramPacket packet = new DatagramPacket(buf, length); socket.receive(packet); // 从DatagramPacket中接受数据报
发送数据:
DatagramSocket socket = new DatagramSocket(port); // port:端口号 DatagramPacket packet = new DatagramPacket(buf, length, address, port); socket.send(packet); // 以DatagramPacket对象向外发送数据报
2)使用MulticastSocket实现多点广播
DatagramSocket只允许数据报发送到指定的目标地址,而MulticastSocket可以将数据报以广播方式发送到多个客户端。
MulticastSocket继承于DatagramSocket
多点广播示意图如下:
创建MulticastSocket对象后还需要加入到指定的多点广播地址,MulticastSocket使用joinGroup(InetAddress multicastAddr)方法加入到指定组,使用leaveGroup(InetAddress
multicastAddr)方法脱离一个组。
MulticastSocket用于发送、接受数据报的方法和DatagramSocket完全一样。但MulticastSocket多了一个setTimeToLive(int ttl)方法,ttl参数用于设置数据报最多可以跨过多少个网络(0:停留在本地主机;1:本地局域网;32:只能发送到本站点的网络上;64:保留在本地区;128:保留在本大洲;255:所有地方),默认1。
5.代理服务器
从Java5开始,java.net包下提供了Proxy(表示代理服务器)、ProxySelector(表示代理选择器)两个类