在文章的开始,我们先了解一下什么是计算机网络:计算机网络就是把各个计算机连接到一起,让网络中的计算机可以互相通信。那么网络编程呢? 既然是编程,那就是需要写代码来实现网络通信---网络编程就是如何在程序中实现两台计算机的通信。
PS:今天小程序更新了有关Netty相关的面试题,感兴趣可以去查看哦。
那么网络编程的作用吧,大家都是会利用网络来查询一些资料的,当我们输入百度的网址的时候,就会有页面展示在我们面前,那么页面是怎么来的? 就是百度的服务器把网页内容作为数据通过网络传输到你的电脑上。 我们通常用到了网络编程了吗? 我想大家都用过Tomcat,我们可以使用tomcat当作服务器,通过启动tomcat,然后本地展示我们自己写的代码。如果我们没有tomcat,怎么办呢?这个时候我们就要用到网络编程了。
我们先来自己尝试一下:如何在浏览器输入地址访问到我们的数据:
public static void main(String[] args) {
try {
ServerSocket server = new ServerSocket(8080);
while (true) {//不使用while(true)就只能请求一次
Socket socket = server.accept();
InputStreamReader r = new InputStreamReader(socket.getInputStream());
BufferedReader br = new BufferedReader(r);
String readLine = br.readLine();
while (readLine != null && !readLine.equals("")) {
System.out.println("获取到数据:" + readLine);
readLine = br.readLine();
}
// 响应行
String resp = "http/1.1 200 ok\n\n" ;// http/1.1:协议的版本; 200:code;ok
// 两个\n\n 我测试发现如果不加的话,下面的文本就不会读,应该是这里当作响应头的终止符吧。
String html = "<p style='color:red'>每天学Java</p>";//响应的数据
PrintWriter pw = new PrintWriter(new OutputStreamWriter(socket.getOutputStream(), "GB18030"));
pw.println(resp+html);
pw.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
效果:
有没有对Tomcat启动后可以访问网页有一些想法?但是Tomcat肯定要比这个复杂的多,还需要下很多功夫去了解。
在看到实际效果之后,我想大家对于网络编程有一些明悟,那我们接下来了解一下网络的基础知识:
一、首先是协议,为什么要有协议。协议也可以理解成规范,如果每一台服务器,都有自己的规范,也就是说每一台机器都说自己的语言,那么如何进行通讯,所以我们要制定一套通用的协议,只要你遵守这个协议,那么恭喜你,我们可以进行沟通了。互联网协议包含了上百种协议标准,但是最重要的两个协议是TCP和IP协议,所以,大家把互联网的协议简称TCP/IP协议。这里扩展一下分层模型,感兴趣可以了解一下:
OSI分层模型和TCP/IP分层模型的对应关系
OSI(Open System Interconnection开放式系统互联)
有了这个模型对应参考图我们在来捋一下他们和协议之间的对应关系
网络层----IP(网络之间的互联协议)
传输层----TCP(传输控制协议),UDP(用户数据报协议)
应用层----HTTP(超文本传送协议),Telnet(Internet远程登录服务的标准协议和主要方式),FTP(文本传输协议)
二、了解到协议之后,我们再说一说IP地址和端口号
计算机相互通信的时候,双方必须知道对方的标识,好比打电话件必须知道对方的电话好吗。互联网上每个计算机的唯一标识就是IP地址,类似192.168.1.123。如果一台计算机同时接入到两个或更多的网络,比如路由器,它就会有两个或多个IP地址,所以,IP地址对应的实际上是计算机的网络接口,通常是网卡。那么我们就知道ip地址,它是用于唯一标示网络中的一个通信实体,这个通信实体可以是一台主机,可以是路由器的某一个端口。而在基于IP协议网络中传输的数据包,必须使用IP地址来进行标示。ip地址就像发快递,必须指定收件人的地址一样。每个被传输的数据包中都包括了一个源IP和目标IP。就像你在网上买了东西,商家发货肯定要写发货地址,而你买之前也必须填写你的收货地址。
ip地址唯一标示了通信实体,那么端口有什么作用?在两台计算机通信时,只发IP地址是不够的,因为同一台计算机上跑着多个网络程序。一个IP返回数据后,到底是交给浏览器还是其他进程,就需要端口号来区分。每个网络程序都向操作系统申请唯一的端口号,这样,两个进程在两台计算机之间建立网络连接就需要各自的IP地址和各自的端口号。也就像你和朋友住酒店一样,IP地址表示了酒店在具体位置,而端口号则表示了你和朋友要入住的房间号。
我们顺便看一下java如何获取主机ip地址
InetAddress address = InetAddress.getLocalHost();
System.out.println(address);
结果:
三、TCP和UDP
再说TCP和UDP之前,我们来认识一下Socket,在上面的例子中,我们用到这个类,那么它是干嘛的,Socket是网络编程的一个抽象概念,通过Socket这个类,我们只需要知道计算机的IP和端口,再指定协议类型就可以连接计算机了,通常我们这些连接使用TCP和UDP的一种,作为连接的协议。那么这两种协议如何理解?
TCP是一种面向连接的保证可靠传输的协议。通过TCP协议传输,得到的是一个顺序的无差错的数据流。它能够提供两台计算机之间的可靠的数据流,HTTP、FTP、Telnet等应用都需要这种可靠的通信通道。
UDP是一种无连接的协议,每个数据报都是一个独立的信息,包括完整的源地址或目的地址,它在网络上以任何可能的路径传送目的地,至于是否能够达到目的地,达到目的地的时间以及内容的正确性都是不能保证的。
可能有同伴会问既然TCP传输这么安全这么好为啥还要费劲巴拉的搞UDP?这个就牵扯到效率问题了。可靠的传输是要付出代价的,对数据内容的正确性的检验必然会占用计算机处理时间和网络带宽。因此TCP的传输效率不如UDP高。
许多应用中并不需要保证严格的传输可靠性,比如视频会议系统,并不要求视频音频数据绝对正确,只要能够连贯就可以了。所以在这些场景下,使用UDP更合适些。
四、URL访问网上资源
URL对象代表统一资源定位器,是指向互联网“资源”的指针。它是用协议名、主机、端口和资源组成,即满足如下格式:
协议://用户名:密码@子域名.域名.顶级域名:端口号/目录/文件名.文件后缀?参数=值#标志
protocol://host:port/resourceName
http://www.crazyit.org/index.php
通过URL对象的一些方法可以访问该URL对应的资源:
String getFile():获取该URL的资源名
String getHost():获取主机名
String getPath():获取路径部分
int getPort():获取端口号
我们来用一下这几个方法
URL url = new URL("https://http://www.135editor.com/users/login?lck=1&referer=%2F");
String file = url.getFile();
System.out.println(file);
String host = url.getHost();
System.out.println(host);
int port = url.getPort();
System.out.println(port);
String query = url.getQuery();
System.out.println(query);
String protocol = url.getProtocol();
System.out.println(protocol);
结果:
下面我们看看serversocket和socket
先看看这张原理图:
看完原理图我们来模拟一遍这个过程。
package net.wyd;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
/**
* @author create by wyd
* @description server端演示
* @date 2018-09-22:01
*/
public class ServerSocketTest {
public static void main(String[] args) throws Exception {
//创建一个ServerSocket,用于监听客户端Socket连接请求
ServerSocket ss = new ServerSocket(8887);
System.out.println("server start");
//采用循环方式监听客户端的请求
while (true) {
//监听并接受到此套接字的连接。此方法在连接传入之前一直阻塞。
Socket socket = ss.accept();
OutputStream os = socket.getOutputStream();
PrintStream ps = new PrintStream(os);
ps.print("您好,您收到了来自服务端的每天学java邀请");
ps.close();
os.close();
socket.close();
}
}
}
结果:
再来看看客户端:
package net.wyd;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.Socket;
/**
* @author create by wyd
* @description 客户端
* @date 2018-09-22:03
*/
public class SocketTest {
public static void main(String[] args) throws Exception {
Socket socket = new Socket("localhost",8887);
InputStream is = socket.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String str = br.readLine();
System.out.println(str);
br.close();
is.close();
socket.close();
}
}
结果:
那么我们来分析一下这个过程:
首先在server端,我们指定端口号8887创建serverSocket对象,通过serverSocket的accpet方法获取套接字,这个方法的特点是:侦听并接受到此套接字的连接,此方法在连接传入之前一直阻塞状态。也就是说,如果没有受到客户端的连接请求那么服务端会一致阻塞在这里。
然后通过套接字socket可以得到输入输出流,关于io流的内容我们前边刚说过,如果有疑惑的地方可以翻一下前边关于io流的文章,也有例子。那么在客户端这边,通过指定的服务器主机名和服务器监听的端口号,获取到Socket,这个时候就表示服务端和客户端的连接已经建立。接下来又是一波io操作,然后就可以通过输入输出流来进行通信了。
那么有一个问题,如果通信双方需要发送一长篇私密的文章的话,上面的方式行吗?答案是否定的。因为关闭socket,会导致无法再从该socket中读取数据了。那么这种问题解决了吗?答案是肯定的,java提供了两个半关闭的方法:
shutdownInput():关闭该Socket的输入流,程序还可以通过该Socket的输出流输出数据。
shutdownOutput():关闭该Socket的输出流,程序还可以通过该Socket的输入流读取数据。
至于这两个方法的用法也很简单,可以自己玩一下,只需要加在上边的dome中就可以。
总结:
根据上面的dome演示模拟,我们可以总结一下网络编程的套路
客户端网络编程步骤
客户端是指网络编程中首先发起连接的程序,客户端一般实现程序界面和基本逻辑实现,在进行实际的客户端编程时,无论客户端复杂还是简单,以及客户端实现的方式,客户端的编程主要由三个步骤实现:
1.建立网络连接
客户端网络编程的第一步都是建立网络连接。在建立网络连接时需要指定连接的服务器的IP地址和端口号,建立完成以后,会形成一条虚拟的连接,后续的操作就可以通过该连接实现数据交换了。
2.交换数据
连接建立以后,就可以通过这个连接交换数据了,交换数据严格要求按照请求响应模型进行,由客户端发送一个请求数据到服务器,服务器反馈一个响应数据后给客户端,如果客户端不发送请求则服务器就不响应。根据逻辑需要,可以多次交换数据,但是还是必须遵循请求响应模型。
3.关闭网络连接
在数据交换完成后,关闭网络连接,释放程序占用的端口、内存等系统资源,结束网络编程。最基本的步骤一般都是这三个步骤,在实际实现时,步骤2会出现重复,在进行代码组织时,由于网络编程是比较耗时的操作,所以一般开启专门的现场进行网络通讯。
服务器端网络编程步骤
服务器是指网络编程中被等待连接的程序,服务器端一般实现程序的核心逻辑以及数据存储等核心功能。服务器端的编程步骤和客户端不同,是由四个步骤实现,依次是:
1监听端口
服务器端属于被动等待连接,所以服务器端启动以后,不需要发起连接,而只需要监听本地计算机的某个固定端口即可。这个端口就是服务器端开放给客户端的端口,服务器端程序运行的本地计算机的IP地址就是服务器端程序的IP地址。
2.获得连接
当客户端连接到服务器端时,服务器端就可以获得一个连接,这个连接包含客户端信息,例如客户端IP地址等,服务器端和客户端通过该连接进行数据交换。一般在服务器端编程中,当获得连接时,需要开启专门的线程处理该连接,每个连接都由独立的线程实现。
3.交换数据
服务器端通过获得的连接进行数据交换。服务器端的数据交换步骤是首先接收客户端发送过来的数据,然后进行逻辑处理,再把处理以后的结果数据发送给客户端。简单来说,就是先接收再发送,这个和客户端的数据交换顺序不同。其实,服务器端获得的连接和客户端的连接是一样的,只是数据交换的步骤不同。当然,服务器端的数据交换也是可以多次进行的。在数据交换完成以后,关闭和客户端的连接。
4.关闭连接
当服务器程序关闭时,需要关闭服务器端,通过关闭服务器端使得服务器监听的端口以及占用的内存可以释放出来,实现了连接的关闭。其实服务器端编程的模型和呼叫中心的实现是类似的,例如移动的客服电话10086就是典型的呼叫中心,当一个用户拨打10086时,转接给一个专门的客服人员,由该客服实现和该用户的问题解决,当另外一个用户拨打10086时,则转接给另一个客服,实现问题解决,依次类推。在服务器端编程时,10086这个电话号码就类似于服务器端的端口号码,每个用户就相当于一个客户端程序,每个客服人员就相当于服务器端启动的专门和客户端连接的线程,每个线程都是独立进行交互的。这就是服务器端编程的模型,只是TCP方式是需要建立连接的,对于服务器端的压力比较大,而UDP是不需要建立连接的,对于服务器端的压力比较小罢了。总之,无论使用任何语言,任何方式进行基础的网络编程,都必须遵循固定的步骤进行操作,在熟悉了这些步骤以后,可以根据需要进行逻辑上的处理,但是还是必须遵循固定的步骤进行。