第14章网络编程
等到JavaWeb阶段全是网络编程的内容
网络编程概述
一、网络编程中有两个主要的问题:
1.如何准确地定位网络上一台或多台主机;定位主机上的特定应用
2.找到主机后如何可靠高效地进行数据传输
二、网络编程中的两个要素:
1.对于问题一:IP 和 端口号
2.对应问题二:提供网络通信协议:TCP/IP参考模型(应用层、传输层、网络层、物理+数据链路层)
IP和端口号的理解
三、通信要素一:IP和端口号
1.IP:唯一的标识Internet上的计算机(通信实体)
2.在Java中使用InetAddress类代表IP。一个InetAddress类的对象就表示一个IP地址。
3.IP分类:IPv4 和 IPv6 ; 万维网 和 局域网
4.域名:www.baidu.com www.sina.com
5.本机的回路地址:127.0.0.1 对应着:localhost
6.如何实例化InetAddress:两个方法:getByName(String host)、getLocalHost(). 两个常用方法:getHostName() / getHostAddress()
7.端口号:正在计算机上运行的进程。
要求:不同的进程有不同的端口号。
范围:被规定为一个16位的整数 0~ 65535
8.端口号与IP地址的组合得出一个网络套接字:Socket
InetAddress类
InetAddress类实例化,InetAddress类没有对外暴露构造器,将构造器私有化了,需要调用静态方法返回对象。
一个InetAddress类的对象就表示一个IP地址。
InetAddress类没有提供公共的构造器,而是提供了如下几个静态方法来获取InetAddress实例
- public static InetAddress getLocalHost()
- public static InetAddress getByName(String host)
InetAddress提供了如下几个常用的方法
- public String getHostAddress():返回 IP 地址字符串(以文本表现形式)。
- public String getHostName():获取此 IP 地址的主机名
- public boolean isReachable(int timeout):测试是否可以达到该地址
@Test
public void test1(){
try {
InetAddress inetAddress1 = InetAddress.getByName("220.181.38.251");
System.out.println(inetAddress1);
InetAddress inetAddress2 = InetAddress.getByName("www.baidu.com");
System.out.println(inetAddress2);
InetAddress inetAddress3 = InetAddress.getByName("127.0.0.1");
System.out.println(inetAddress3);
//获取本机的IP地址
InetAddress inetAddress4 = InetAddress.getLocalHost();
System.out.println(inetAddress4);
//获取域名
System.out.println(inetAddress2.getHostName());
//获取IP地址
System.out.println(inetAddress2.getHostAddress());
} catch (UnknownHostException e) {
e.printStackTrace();
}
}
Internet上的主机有两种方式表示地址:
- 域名(hostName):www.baidu.com
- IP 地址(hostAddress):220.181.38.251
**InetAddress类主要表示IP地址,**两个子类:Inet4Address、Inet6Address。
InetAddress 类对象含有一个 Internet 主机地址的域名和IP 地址:www.baidu.com 和220.181.38.251。
域名容易记忆,当在连接网络时输入一个主机的域名后,域名服务器(DNS)负责将域名转化成IP地址,这样才能和主机建立连接。 -------域名解析
域名解析的过程:
域名解析时会先去寻找本机的hosts文件查看是否有对应的域名IP地址。若没有再提交给DNS(域名服务器)。可以手动在hosts文件中添加域名和IP地址的映射关系。
hosts文件的作用:
其作用就是将一些常用的网址域名与其对应的IP地址建立一个关联“数据库”,当用户在浏览器中输入一个需要登录的网址时,系统会首先自动从Hosts文件中寻找对应的IP地址,一旦找到,系统会立即打开对应网页,如果没有找到,则系统会再将网址提交DNS域名解析服务器进行IP地址的解析。对于要经常访问的网站,我们可以通过在Hosts中配置域名和IP的映射关系,提高域名解析速度。由于有了映射关系,当我们输入域名计算机就能很快解析出IP,而不用请求网络上的DNS服务器。清空hosts文件对系统正常运行并没有影响。
在命令提示符下输入ping + 域名可以得到对应的IP地址
端口号补充
端口号标识正在计算机上运行的进程(程序)
- 不同的进程有不同的端口号
- 被规定为一个 16 位的整数 0~65535。
端口分类:
- 公认端口:0~1023。被预先定义的服务通信占用(如:HTTP占用端口80,FTP占用端口21,Telnet占用端口23)
- 注册端口:1024~49151。分配给用户进程或应用程序。(如:Tomcat占用端口8080,MySQL占用端口3306,Oracle占用端口1521等)。
- 动态/私有端口:49152~65535。
端口号与IP地址的组合得出一个网络套接字:Socket。
网络通信协议
TCP协议:
- 使用TCP协议前,须先建立TCP连接,形成传输数据通道
- 传输前,采用“三次握手”方式,点对点通信,是可靠的
- TCP协议进行通信的两个应用进程:客户端、服务端。
- 在连接中可进行大数据量的传输
- 传输完毕,需释放已建立的连接,效率低
UDP协议:
- 将数据、源、目的封装成数据包,不需要建立连接
- 每个数据报的大小限制在64K内
- 发送不管对方是否准备好,接收方收到也不确认,故是不可靠的
- 可以广播发送
- 发送数据结束时无需释放资源,开销小,速度快
TCP网络编程例子
TCP网络编程创建Socket对象,指明服务器端的ip和端口号,因为TCP需要先建立连接,所以在创建对象时就需要指明ip和端口号。
先启动服务器端,再启动客户端。
例一、客户端发送内容给服务端,服务端将内容打印到控制台上。
客户端:
1.创建Socket对象,指明服务器端的ip和端口号
2.获取一个输出流,用于输出数据
3.写出数据的操作
4.资源的关闭
服务器端:
1.创建服务器端的ServerSocket,指明自己的端口号
2.调用accept()表示接收来自于客服端的socket
3.获取输入流
4.获取输入流中的数据
5.资源的关闭
注意:套接字socket也需要关闭;熟悉使用ByteArrayOutputStream,理解其底层原理。理解出现乱码的原因。
/*
网络编程例子1
*/
//客户端
@Test
public void client(){
Socket socket = null;
OutputStream outputStream = null;
try {
//1.创建Socket对象,指明服务器端的ip和端口号
InetAddress inetAddress = InetAddress.getByName("127.0.0.1");
socket = new Socket(inetAddress,8899);
//2.获取一个输出流,用于输出数据
outputStream = socket.getOutputStream();
//3.写出数据的操作
outputStream.write("你好我是客户端".getBytes());
} catch (IOException e) {
e.printStackTrace();
} finally {
//4.资源的关闭
try {
if (outputStream != null)
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
if (socket != null)
socket.close();//socket套接字也需要关闭
} catch (IOException e) {
e.printStackTrace();
}
}
}
//服务器端
@Test
public void server(){
ServerSocket serverSocket = null;
Socket socket = null;//接收
InputStream inputStream = null;
ByteArrayOutputStream bas = null;
try {
//1.创建服务器端的ServerSocket,指明自己的端口号
serverSocket = new ServerSocket(8899);
//2.调用accept()表示接收来自于客服端的socket
socket = serverSocket.accept();
//3.获取输入流
inputStream = socket.getInputStream();
/*
不建议这样写,因为这是字节流,处理中文时可能会乱码。
出现乱码的原因,在UTF-8中中文占3个字节,如果在装进byte[]数组时,正好将一个中文3个字节给分开了,
那么这个装满的数组在转成字符串(解码)时就会出现乱码,接着后面的都会出现错乱。
byte[] buffer = new byte[20];
int len;
while ((len = inputStream.read(buffer)) != -1){
String str = new String(buffer,0,len);
System.out.println(str);
}
*/
//采用ByteArrayOutputStream
byte[] buffer = new byte[20];
int len;
//4.获取输入流中的数据
bas = new ByteArrayOutputStream();
while ((len = inputStream.read(buffer)) != -1){
/*
ByteArrayOutputStream内部是一个可变长的char[]数组,调用write时可以先将数据写入数组,
这个时候不会解码,等全部装完转成字符串统一进行解码,这样就不会出现乱码了。
*/
bas.write(buffer,0,len);
}
System.out.println(bas.toString());//转换要放在循环外
System.out.println("收到了来自:" + socket.getInetAddress().getHostAddress() + "的数据");
} catch (IOException e) {
e.printStackTrace();
} finally {
//5.资源的关闭
try {
if (bas != null)
bas.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
if (inputStream != null)
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
if (socket != null)
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
if (serverSocket != null)
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
例二、客户端发送文件给服务端,服务端将文件保存在本地。
/*
例二
*/
@Test
public void client1() {
Socket socket = null;
OutputStream os = null;
FileInputStream fis = null;
try {
//1.创建Socket对象,指明服务器端的ip和端口号
socket = new Socket(InetAddress.getByName("127.0.0.1"), 9090);
//2.获取一个输出流,用于输出数据
os = socket.getOutputStream();
//3.获取一个输入流,实现从哪个文件读数据
fis = new FileInputStream("图片1.jpg");
//4.具体实现步骤
byte[] buffer = new byte[1024];
int len;
while ((len = fis.read(buffer)) != -1) {
os.write(buffer, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//5.资源关闭
try {
if (fis != null)
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
if (os != null)
os.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
if (socket != null)
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
@Test
public void server1(){
ServerSocket ss = null;
Socket socket = null;
InputStream is = null;
FileOutputStream fos = null;
try {
//1.创建服务器端的ServerSocket,指明自己的端口号
ss = new ServerSocket(9090);
//2.调用accept()表示接收来自于客服端的socket
socket = ss.accept();
//3.获取输入流
is = socket.getInputStream();
//4.将数据写入本地
fos = new FileOutputStream("图片4.jpg");
//5.实现步骤
byte[] buffer = new byte[1024];
int len;
while ((len = is.read(buffer)) != -1){
fos.write(buffer,0,len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//6.关闭资源
try {
if (fos != null)
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
if (is != null)
is.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
if (socket != null)
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
if (ss != null)
ss.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
例三:从客户端发送文件给服务端,服务端保存到本地。并返回“发送成功”给客户端。并关闭相应的连接。
学习服务器端是怎么给客户端反馈的。客户端在数据传输完毕后需要调用shutdownOutput()方法告诉服务器端数据传输完毕。
/*
例三
*/
@Test
public void client2() {
Socket socket = null;
OutputStream os = null;
FileInputStream fis = null;
InputStream is = null;
ByteArrayOutputStream bas = null;
try {
//1.创建Socket对象,指明服务器端的ip和端口号
socket = new Socket(InetAddress.getByName("127.0.0.1"), 9090);
//2.获取一个输出流,用于输出数据
os = socket.getOutputStream();
//3.获取一个输入流,实现从哪个文件读数据
fis = new FileInputStream("图片1.jpg");
//4.具体实现步骤
byte[] buffer = new byte[1024];
int len;
while ((len = fis.read(buffer)) != -1) {
os.write(buffer, 0, len);
}
//关闭数据的输出
socket.shutdownOutput();
//5.接收来自服务器端的反馈。必须在关闭连接之前接收
is = socket.getInputStream();
bas = new ByteArrayOutputStream();
byte[] buffer1 = new byte[1024];
int len1;
while ((len1 = is.read(buffer1)) != -1){
bas.write(buffer1,0,len1);
}
System.out.println(bas.toString());
} catch (IOException e) {
e.printStackTrace();
} finally {
//6.资源关闭
try {
if (fis != null)
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
if (os != null)
os.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
if (socket != null)
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
if (bas != null)
bas.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
if (is != null)
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
@Test
public void server2(){
ServerSocket ss = null;
Socket socket = null;
InputStream is = null;
FileOutputStream fos = null;
OutputStream os = null;
try {
//1.创建服务器端的ServerSocket,指明自己的端口号
ss = new ServerSocket(9090);
//2.调用accept()表示接收来自于客服端的socket
socket = ss.accept();
//3.获取输入流
is = socket.getInputStream();
//4.将数据写入本地
fos = new FileOutputStream("图片4.jpg");
//5.实现步骤
byte[] buffer = new byte[1024];
int len;
while ((len = is.read(buffer)) != -1){
fos.write(buffer,0,len);
}
/*
解释:read()是一个阻塞式的方法,就是如果没有明确告诉传输完毕那么就不会退出循环。
比如读取一个文件时,读到文件末尾返回值为-1,说明传输完毕,但是在网络编程中,客户端什么时候才算
传完呢。如果客户端关闭了,说明客户端传输完毕,但是现在客户端还在等待服务器端的反馈,因此客户端还没有
进行关闭,所以必须想一个办法告诉服务器端客户端已经传输完成,这样才能退出循环,否则一直在循环中,
程序无法继续执行。
仅需在客户端调用shutdownOutput()方法告诉服务器端数据传输完毕。
*/
//6.服务器端给予客户端反馈
//这时候这个socket已经是接收到的从客户端发送过来的socket了,就是要给这个客户端反馈的
os = socket.getOutputStream();
os.write("照片已经收到!".getBytes());
} catch (IOException e) {
e.printStackTrace();
} finally {
//7.关闭资源
try {
if (fos != null)
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
if (is != null)
is.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
if (socket != null)
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
if (ss != null)
ss.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
if (os != null)
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
IO流中read()是一个阻塞式的方法,就是如果没有明确告诉传输完毕那么就不会退出循环。比如读取一个文件时,读到文件末尾返回值为-1,说明传输完毕,但是在网络编程中,客户端什么时候才算传完呢。如果客户端关闭了,说明客户端传输完毕,但是现在客户端还在等待服务器端的反馈,因此客户端还没有进行关闭,所以必须想一个办法告诉服务器端客户端已经传输完成,这样才能退出循环,否则一直在循环中,程序无法继续执行。仅需在客户端调用shutdownOutput()方法告诉服务器端数据传输完毕。
而在 NIO 中就是非阻塞式了,功能更强大,等学习到框架时会细说。
配置Tomcat
Tomcat配置,使用等到JavaWeb细讲。
UDP网络编程例子
UDP需要创建DatagramSocket对象,此时不需要指明ip和端口号,因为UDP是不可靠连接,ip和端口号放在数据报中。
//UDP网络编程例子
@Test
public void sender(){
DatagramSocket socket = null;
try {
socket = new DatagramSocket();
String str = "我是UDP";
byte[] buffer = str.getBytes();
InetAddress localAddress = InetAddress.getLocalHost();
DatagramPacket packet = new DatagramPacket(buffer,0,buffer.length,localAddress,9090);
socket.send(packet);
} catch (IOException e) {
e.printStackTrace();
} finally {
if (socket != null)
socket.close();
}
}
@Test
public void receiver(){
DatagramSocket socket = null;
try {
socket = new DatagramSocket(9090);
byte[] buffer = new byte[1024];
DatagramPacket packet = new DatagramPacket(buffer,0,buffer.length);
socket.receive(packet);
System.out.println(new String(packet.getData(),0,packet.getLength()));
} catch (IOException e) {
e.printStackTrace();
} finally {
if (socket != null)
socket.close();
}
}
URL网络编程
1.URL:统一资源定位符,对应着互联网的某一资源地址
2.格式:
http://localhost:8080/examples/beauty.jpg?username=Tom
协议 主机名 端口号 资源地址 参数列表
@Test
public void test2() throws MalformedURLException {
URL url = new URL("http://localhost:8080/examples/beauty.jpg?username=Tom");
System.out.println(url.getProtocol());//获取该URL的协议名
System.out.println(url.getHost());//获取该URL的主机名
System.out.println(url.getPort());//获取该URL的端口号
System.out.println(url.getPath());//获取该URL的文件路径
System.out.println(url.getFile());//获取该URL的文件名
System.out.println(url.getQuery());//获取该URL的查询名
}
URL网络编程实现从Tomcat服务器端下载数据
这里只是简单用一下,实际上并不会这么操作。在JavaWeb中会讲从浏览器端下载数据。
把Tomcat服务器启动起来,相应的文件资源放在对应的目录下,运行程序即可完成下载。(实际开发中不会用这样的)
@Test
public void test3() {
HttpURLConnection urlConnection = null;
InputStream is = null;
FileOutputStream fos = null;
try {
URL url = new URL("http://localhost:8080/examples/beauty.jpg?username=Tom");
urlConnection = (HttpURLConnection) url.openConnection();
urlConnection.connect();//获取与服务器端的连接
is = urlConnection.getInputStream();
fos = new FileOutputStream("beauty.jpg");
byte[] buffer = new byte[1024];
int len;
while ((len = is.read(buffer)) != -1) {
fos.write(buffer, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (fos != null)
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
if (is != null)
is.close();
} catch (IOException e) {
e.printStackTrace();
}
if (urlConnection != null)
urlConnection.disconnect();
}
}