“Java是Internet上的语言”,其从语言级别上提供了完备的对网络应用程序的支持,联网的具体底层细节被封装在Java提供的网络类库中,而向所有平台提供统一的网络编程环境。
网络编程的目的就是通过网络协议实现和其他计算机的数据交换和通讯,而这个目的带来两个主要问题:
其中第一个问题是通过IP地址+端口解决,IP地址定位主机位置,端口号定位主机上的特定应用,两者组合得到套接字(Socket)。而第二个问题这通过网络通信协议解决,而我们事实上的国际网络通信协议标准是TCP/IP参考模型
其唯一标识Internet上的一个通信实体,而任何一个实体能够通过唯一的回环地址(127.0.0.1)指向自身。我们通常能够接触到的比如www.baidu.com这种称为“域名”,其是Internet上主机的另一种标识方式,本机回环使用的域名是localhost。当使用域名进行连接时,需要由域名服务器(DNS)进行解析,将域名转换成目标IP地址才能建立连接,这是域名解析的过程。
IP地址共有两种分类方式:
InetAddress类也因此分为两个子类:Inet4Address和Inet6Address。该类没有公有构造方法,需要通过getByName()或getLocalHost()方法返回对应的实例。可以通过getHostName()或getHostAddress()获取相关信息。可以发现,Java把域名解析、连接DNS服务器等底层细节都封装地非常完全。
端口是主机用于标识目前正在计算机上运行的应用,或者我们可以称为程序。为了规范,我们对端口有一套公认的分配标准:
网络通信协议,我们耳熟能详,但是它究竟规定了些什么呢?网络通信协议覆盖的范围极广,比如压缩解压缩、如何控制流量、如何指定地址、如何进行加密,这也使得网络通信协议非常复杂。
TCP/IP采用了通信协议分层的思想,将一个复杂的通信协议分解为多个协议,并分层管理:上一层调用下一层而不能与再下面的一层发生关系,同层间可通信。
TCP/IP中的TCP(Transmission Control Protocol)和IP(Internet Protocol)只是TCP/IP协议簇中的两个,事实上,TCP/IP是一个极为庞大的多个协议组成的协议簇,从顶到底分为应用层、传输层、网络层、物理+数据链路层四层,其中TCP属于传输层,IP属于网络层,此外还有HTTP(属于应用层)、DNS(属于应用层)、Link(属于物理链路层)。
URL,Uniform Resource Locator,统一资源定位符。我们之前说,我们可以用IP地址+Port定位到Internet上任意一个主机上的应用,但是这其实还不够。我们不仅访问应用,还需要访问特定的资源,比如图片、视频等。同时,我们在链接到一个通信实体时并不知道他支持什么样的通信协议,贸然链接可能引起很多问题。
URL由五部分组成:
http://192.168.1.100:8080/helloworld/index.jsp#a?username=shkstart&password=123
Java下利用URL类来描述一个URL,并提供解析和构造方法。我们可以通过字符串来创建一个URL对象,并调用Java提供的方法来分析这个URL请求的究竟是什么资源。
当我们使用URL访问同一个资源时,理论上不同的操作对应不同的参数列表(至多再对应不同的片段名),即我们使用参数列表进行传参,从而告诉服务端我们需要进行什么操作。
而Restful的风格鼓励我们使用请求地址传参,即将参数放在路径名中成为路径的一部分,而省去参数列表:
https://mygraph.cn/addTwoInt?a=100&b=1000 传统 https://mygraph.cn/addTwoInt/100/1000 restful风格
好处在于路径更加整洁;同时由于框架会自动进行类型转换,获取参数更加方便;以及参数异常时不再是方法内参数转化失败, 而是指示路径和方法不匹配。
TCP网络编程基础是Socket,即套接字。
Socket将网络连接视作一个流,数据在两个Socket间通过IO传输,分为两类:
Java下TCP网络编程分为服务端编程和客户端编程。
客户端需要:
服务端需要:
UDP是另一种传输层的网络通信协议,不同于TCP的流传输,其采用类似于消息的数据报方式传递信息。其将发送方和接收方的IP+port放到了数据报中
两个通信实体间通过互相发送数据报的方式传递信息,就像发送快递包裹一样。这带来很高的不可靠性,任何一方都不会关心对方是否正确接收数据。
但是这也使得双方能够在不建立连接的情况下进行通信,极大地减少了通信延迟,这使得当实时性需求很高时,UDP通信比TCP更加常见。同时这种“不负责任”的乱丢数据,也支持一对多的广播机制。
Java使用DatagramSocket类表示套接字,DatagramPacket类表示数据报。
发送方:
接收方:
// Server.java import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.net.ServerSocket; import java.net.Socket; public class Server { ServerSocket server; public Server(){ try{ this.server = new ServerSocket(8888); } catch (Exception e){ e.printStackTrace(); } } public void serve(){ System.out.println("Qiume is online!"); while (true){ try{ Socket communication = this.server.accept(); OutputStream out = communication.getOutputStream(); synchronized (out){ out.write("Hello, who are U?".getBytes()); } InputStream in = communication.getInputStream(); StringBuilder receivedMsg = new StringBuilder(); synchronized (in){ int c; for (c = in.read(); in.available()!=0; c = in.read()) { receivedMsg.append((char) c); } receivedMsg.append((char) c); } System.out.println("Qiume received:" + receivedMsg.toString()); synchronized (out){ out.write("I love you, too".getBytes()); } communication.close(); } catch (Exception e) { e.printStackTrace(); } } } }
// Client.java import java.io.InputStream; import java.io.OutputStream; import java.net.Socket; public class Client { Socket communication; public Client() { } public void start() { System.out.println("Norton visits!"); try { try { this.communication = new Socket("localhost", 8888); } catch (Exception e) { e.printStackTrace(); } InputStream in = communication.getInputStream(); StringBuilder receivedMsg = new StringBuilder(); synchronized (in) { int c; for (c = in.read(); in.available() != 0; c = in.read()) { receivedMsg.append((char) c); } receivedMsg.append((char) c); } System.out.println("Norton received:" + receivedMsg.toString()); OutputStream out = communication.getOutputStream(); synchronized (out) { out.write("Hello Qiume, this is Norton, I love you!".getBytes()); } synchronized (in) { receivedMsg = new StringBuilder(); int c; for (c = in.read(); in.available() != 0; c = in.read()) { receivedMsg.append((char) c); } receivedMsg.append((char) c); } System.out.println("Norton received:" + receivedMsg.toString()); communication.close(); System.out.println("Norton out!"); } catch (Exception e) { e.printStackTrace(); } } }
//Main.java public class Main { public static void main(String[] args) throws InterruptedException { Runnable client = new Runnable() { @Override public void run() { Client norton = new Client(); norton.start(); } }; Runnable server = new Runnable() { @Override public void run() { Server qiume = new Server(); qiume.serve(); } }; new Thread(server).start(); while (true){ Thread.sleep(1000); new Thread(client).start(); } } }