Java网络编程
一. 那么在学习网络编程前一些必须了解和掌握的网络知识。
1. OSI模型 & TCP/IP网络参考模型:
OSI模型是在协议开发前设计的,比较具有通用性,而TCP/IP是在协议集出现之后设计出来的,不适用与非TCP/IP型的网络。
OSI & TCP/IP的设计都采用了层次结构的概念,而且它们都可以提供面向连接和面向非连接的通信服务机制。
OSI网络参考模型
TCP/IP网络参考模型
1. 模型中的几个重要层次程序开发员是需要了解掌握的
应用层对应于模型的高层,为用户提供所需要的各种服务,例如FTP、Telnet、DNS、SMTP、HTTP协议在应用层
传输层提供端到端的通信功能,保证了数据包的顺序传送及数据的完整性。该层定义了两个主要的协议:传输控制协议(TCP)和用户数据报协议(UDP).
TCP协议提供的是一种可靠的、面向连接的数据传输服务;而UDP协议提供的则是不可靠的、无连接的数据传输服务.
网络层主要解决主机到主机的通信问题。它所包含的协议设计数据包在整个网络上的逻辑传输。注重重新赋予主机一个IP地址来完成对主机的寻址,它还负责数据包在多种网络中的路由。该层有四个主要协议:网际协议(IP)、地址解析协议(ARP)、互联网组管理协议(IGMP)和互联网控制报文协议(ICMP)。
2. 网络传输的三要素
A. 传输协议(TCP/UDP)
UDP:
特点: 面向无连接、发送数据受大小限制,不能超过64K、因为无连接,是不可靠的协议、可以不建立连接,速度快一般应用在网络视频会议,即时聊天软件等等。
TCP:
特点:面向连接、在建立连接的前提下通信是可靠安全的、速度慢、大数据量传输、通过三次握手(第一次本方向对方发送请求,第二次对方回复确认连接,第三次本方再次确认连接成功),最后应用于下载程序上。
B. IP地址(它是网络中的设备标示,不易记忆,可用主机名表示,两者存在映射关系,本机回环IP地址127.0.0.1,主机名为:localhost)
C. 端口号(用于标示进程的逻辑地址,应用程序和系统服务的的身份证号,如果一台计算机中的应用程序要给另外一个计算机中的应用程序发送数据,或形成通信对话,就要靠这个端口号来进行识别,数据接收到后,由哪个程序来接收,发送给哪个主机的端口,就由那个端口对应的那个程序来接收,一个应用程序或进程对应一个端口,有效端口:0 ~ 65535,系统使用或保留的端口是:0 ~ 1024。,端口号只能用数字表示,也较逻辑端口。
二. 网络通信的步骤
1. 找到IP,定位连接对象主机
2. 通过TCP/IP传输协议将将数据发送到连接主机的指定端口的应用程序或服务。
三. 数据传输和接收的封包拆包过程
一般我们的原始数据从应用层开始封装,每一层都使用自己对应的层的特点对数据封包,当封装到最后一层即物理层时,将数据发送到通信对方主机,对方按照网络通信协议和传输协议,将数据又进行拆包,对方主机由物理层开始拆包,一直到最顶层的应用层,这时才将原始数据展现在用户的眼前。
四. Java中描述IP的类和对象InetAddress
既然是网络编程,绝对少不了IP,那么java也抽象除了一个描述IP的类,即InetAddress,该类继承自Object类并且实现了Serializable接口,有木有,又是Serializable,它是一个标记性接口,没有任何成员方法,成员变量,甚至构造方法了,哈,有复习了一下Serializable接口了,实现Serializable的类说你名它是可以被序列化的。
言归正传,回归到InetAddress类吧,此类表示互联网协议(IP) 地址。 一个InetAddress对象代表的是一个网络IP地址或主机名,也可以说它封装了一个网络IP地址或主机名,InetAddress不是一个接口,不是一个抽象方法,但是却没有构造方法,只能通过它特定静态的方法返回一个InetAddress对象。InetAddress可以通过类本身以下静态方法获取InetAddress获取对象:
1. getByName(String host) 在给定主机名的情况下确定主机的 IP 地址。主机名可以是机器名(如 "java.sun.com
"),也可以是其 IP 地址的文本表示形式,如果主机不存在返回null,如果存在返回指定的主机对象InetAddress,如果找不到host
的 IP 地址,抛出UnknowHostException。
2. getLocalHost
()
返回本地主机IP 地址对象InetAddress。
InetAddress
其他开发中常用的方法
1.
String getHostName()
获取此
IP
地址对象的主机名。
2.
String getHostAddress ()
返回此
IP
地址对象的
IP
地址字符串表现形式。
3. String getCanonicalHostName()
获取此 IP 地址对象的完全限定域名。
InetAddress中的方法演示代码
package com.net;
import java.net.InetAddress;
class InetAddressDemo1
{
public static void main(String[] args) throws Exception
{
//获取百度网址www.baidu.com对应的主机名和IP地址
InetAddress i = InetAddress.getByName("www.baidu.com");
String hostName = i.getHostName();
String ip = i.getHostAddress();
System.out.println("Host Name: "+ hostName);
System.out.println("IP Address: "+ ip);
//获取本机的IP和主机名
InetAddress local = InetAddress.getLocalHost();
hostName = local.getHostName();
ip = local.getHostAddress();
System.out.println("Host Name: "+ hostName);
System.out.println("IP Address: "+ ip);
}
}
打印结果:
HostName: www.baidu.com
IP Address:119.75.218.77
Host Name:Lenovo-PC
IP Address:10.34.108.215
五. UDP传输协议网络编程(DatagramSocket+InetAddress+DatagramPacket)
回顾:UDP传输协议是位于传输层的协议之一,具有面向无连接、不可靠、无连接数据丢失、速度快等特点。
Java中用来实现UDP网络编程或网络数据传输的类对象可以用DatagramSocket、DatagramPacket、InetAddress三个主要类对象类实现,下面先分别讲解,然后讲解整体使用方法。
DatagramSocket:
1. 此类表示用来发送和接收数据报包的套接字。数据报套接字是包投递服务的发送或接收点,类似生活中邮局、港口一样。区分不同应用程序进程间的网络通信和连接,主要有3个参数:通信的目的IP地址、使用的传输层协议(TCP或UDP)和使用的端口号。Socket原意是“插座”。通过将这3个参数结合起来,与一个“插座”Socket绑定,应用层就可以和传输层通过套接字接口,区分来自不同应用程序进程或网络连接的通信,实现数据传输的并发服务。
2. 通信两端都要创建Socket,才能建立数据包来往的服务。
3. 网络通信其实就是Socket之间的通信,数据在两个Socket之间通过IO传输。
创建UDP Socket的方法
1. 创建发送端的UDPSocket发送服务对象:
DatagramSocket ds = new DatagramSocket(10009) 对于发送端可以不指定端口。
DatagramSocketds = new DatagramSocket();无参构造,构建数据报套接字并将其绑定到本地主机上任何可用的端口。对于发送端可以不指定端口,只要在发送的时候指定要发送给目的主机上进程的端口即可。
创建接收端的UDPSocket接受服务对象:
DatagramSocketds = new DatagramSocket()
DatagramSocket ds = new DatagramSocket(10008); 创建数据报套接字并将其绑定到本地主机上的指定端口10008,对于接受端必须定义一个可用端口,否则将无法接受其他主机的数据发送。
PS: 建立UDP Socket服务,在此无需指定端口,也可以将端口加入。如果不指定的话,系统会随机分配一个端口,如第一次运行时端口为1093,那么第二次就会顺延为1094,再运行会一直顺延,因为之前的短路还没有得到释放,所以会顺延端口号值。
创建了UDPSocket服务之后,如何创建接受和发送数据用的包?(DatagramPacket)
1. 创建发送端的数据发送包
DatagramPacket dp = new DatagramPacket(byte[] buf,int length, InetAddress address, int port
); 构造数据报包,用来将长度为length
的包发送到指定主机上的指定端口号
DatagramPacket dp = new DatagramPacket
(
byte[] buf, int offset, intlength, InetAddress address, int port)
构造数据报包,用来将长度为length
偏移量为offset
的包发送到指定主机上的指定端口号。
2. 创建接收端的数据接受包
byte[] buf = newbyte[1024*64];
DatagramPacket dp = new DatagramPacket(byte[] buf,int length
);
构造 DatagramPacket
,用来接收长度为 length
的数据包。
byte[] buf = new byte[1024*64];
DatagramPacket dp = new DatagramPacket
(
byte[] buf, int offset, intlength,)
构造DatagramPacket
,用来接收长度为length
的包,在缓冲区中指定了偏移量。
PS: 所以经过上述创建包的方法总结,可以的出规律,只要是构造方法中含有int port 端口号或则含有InetAddress对象参数的一定是发送端的发送包,没有的话,就是接收端接受包了。
3. 以上创建了发送和接收服务的Socket、又创建了发送和接受数据用的包,那么然后去接受一个数据和发送数据呢?
发送端发送数据包:发送数据包应该使用发送服务Socket对象即DatagramSocket的send
(DatagramPacketp)
从此套接字发送数据报包。
ds.send(dp);
接受端接收数据包:接收数据包应该使用接受服务Socket对象即DatagramSocket的receive
(DatagramPacket p)
从此套接字接收数据报包。
ds.send(dp);这个方法是一个阻塞式方法,如果没有数据发送过来,就一直等待。
4. 整理发送和接收的步骤
发送:
1. 建发送服务Soket(使用DatagramSocket创建)
2. 提供数据,发送的数据必须是字节,如果不是先专为字节数据,用byte数组缓存,并创建数据发送包(DatagramPacket)去封装byte数组中的数据和指定发送地点和端口。
3. 通过Socket服务的send(dp)方法将数据发送出去
4. 关闭资源
接受:
1. 建立接收服务Soket(使用DatagramSocket创建)
2. 接收的数据都是字节数据,创建byte缓冲数组,并创建接收数据包(DatagramPacket)去封装管理、接收到数据。
3. 通过Socket服务的receive(dp)方法将数据接收到字节数组中。
4. 关闭资源
5. 为什么数据包要封装在DatagramPacket对象中?在使用接收发送数据的方法中,仍会传入一个DatagramPacket类型的对象参数,这是因为收到的数据太多,需要解析,通过将数据封装成对象,易于解析,所以需要传入参数,便于管理。
6. PS: 对于UDPDatagramSocket发送服务,一个Socket只能用来发送,如果还需要接收,就要在同一个主机创建一个接收服务并开启这个接收应用程序,同理接收服务也是样。
UDP网络编程练习代码
/*
键盘录入
*/
import java.net.*;
import java.io.*;
//发送端:通过UDP传输方式,将一段文字发送出去
class UDPSend
{
public static void main(String[] args) throws Exception
{
//1、创建udp服务,通过DatagramSocket对象
DatagramSocket ds = new DatagramSocket();
//2、确定要发送的数据,并封装成数据包
BufferedReader bufr =
new BufferedReader(new InputStreamReader(System.in));
String line = null;
while((line=bufr.readLine())!=null)
{
if("886".equals(line))
break;
byte[] bt = line.getBytes();
//3、通过socket服务的发送请求,将数据发送出去
DatagramPacket dp =
new DatagramPacket(bt,bt.length,InetAddress.getByName("192.168.1.101"),10001);
ds.send(dp);
}
//4、关闭资源
ds.close();
}
}
import java.net.*;
import java.io.*;
//接收端:定义一个应用程序,用于接收udp协议传输数据并处理
class UDPRece
{
public static void main(String[] args) throws Exception
{
//1、创建udpsocket,建立端点
DatagramSocket ds = new DatagramSocket(10001);
//连续读取收到的数据
while(true)
{
//2、定义数据包,用于存储数据
byte[] by = new byte[1024];
//3、通过服务的receive方法将接收到的数据存入数据包中
DatagramPacket dp = new DatagramPacket(by,by.length);
ds.receive(dp);
//4、通过数据包的方法获取其中的数据
String ip = dp.getAddress().getHostAddress();
String data = new String(dp.getData(),0,dp.getLength());
System.out.println(ip+":"+data);
}
}
}
练习:编写一个简单的聊天程序:
分析:
有收数据的部分,有发数据的部分,这两部分需要同时执行,那就需要多线程技术,一个线程控制接收,一个线程控制发。
因为收和发的动作不一致,所以要定义两个run方法,而且这个两个方法要封装到不同的类中。
import java.io.*;
import java.net.*;
//发送数据
class SendSocket implements Runnable
{
//定义全局变量
private DatagramSocket ds;
//初始化发送类对象的参数
public SendSocket(DatagramSocket ds)
{
this.ds = ds;
}
//覆写run方法,此线程发送键盘录入的数据
public void run()
{
try
{
//创建读取流,读取键盘数据
BufferedReader bufr =
new BufferedReader(new InputStreamReader(System.in));
String line = null;
while((line=bufr.readLine())!=null)
{
if("886".equals(line))
break;
byte[] b = line.getBytes();
//创建发送服务对象,将数据发送出去
DatagramPacket dp =
new DatagramPacket(b,b.length,InetAddress.getByName("192.168.1.255"),10001);
ds.send(dp);
}
//关闭资源
ds.close();
}
catch (Exception e)
{
throw new RuntimeException("发送失败");
}
}
}
//接收数据
class ReceSocket implements Runnable
{
//定义全局变量
private DatagramSocket ds;
//初始化接收类对象的参数
public ReceSocket(DatagramSocket ds)
{
this.ds = ds;
}
//覆写run方法,此线程接收数据
public void run()
{
try
{
//循环读取接收到的数据
while(true)
{
//创建字节数组存储数据
byte[] by = new byte[1024];
//创建接收数据的对象,接收数据
DatagramPacket dp = new DatagramPacket(by,by.length);
ds.receive(dp);
//获取发送方的ip地址
String ip = dp.getAddress().getHostAddress();
//getData获取byte数组中的数据
String data = new String(dp.getData(),0,dp.getLength());
System.out.println(ip+":"+data);
}
}
catch (Exception e)
{
throw new RuntimeException("接收失败");
}
}
}
//测试
class SocketDemo
{
public static void main(String[] args) throws Exception
{
//创建发送和接收服务的对象
DatagramSocket sendSocket = new DatagramSocket();
DatagramSocket receSocket = new DatagramSocket(10001);
//创建两个线程,同时执行
new Thread(new SendSocket(sendSocket)).start();
new Thread(new ReceSocket(receSocket)).start();
}
}