一、网络传输的前提
1、确定双方的通信地址:使用IP地址定位一台主机,使用端口号定位一个应用(即一个进程,因此不同的应用在同一个机器上的端口是不能相同的),可以通过域名或者IP地址来获取InetAddress对象:上网的时候若输入的是域名,域名会先发送到DNS(域名解析服务器)上,解析出一个ip地址,将此ip地址返回,再根据ip地址访问网络服务器中的资源
InetAddress inet = InetAddress.getByName("192.168.10.165");
InetAddress inet = InetAddress.getByName("www.baidu.com");//也可用ip地址
//本机网络地址信息
InetAddress localHost = InetAddress.getLocalHost();//获取本机的IP信息
String hostName = localHost.getHostName();//计算机名并非域名
String hostAddress = localHost.getHostAddress();//本机ip
Tip
:端口号被规定为一个16位的正数(0-65535)。其中,0-1023被预先定义的服务通信占用(如HTTP占用80端口),除非我们要访问这些特定服务,否则,就应该使用1024-65535这些端口中的某一个进行通信,以免发生端口冲突。端口号与IP地址的组合就是一个网络套接字。
2、可靠、高效的数据传输:依靠网络通信协议
IP(Internet Protocol)协议是网络层的主要协议,支持网间互连的数据通信。网络通信可看作是数据封装和拆封的过程,很像寄送快递的过程,要有地址、要包装,到了以后又要拆开包装,拿到快递。
二、传输层协议TCP和UDP
1、TCP(传输控制协议)协议:适用于数据要求较高的场景,几乎不会丢失数据包,基于Socket和ServerSocket编程
①使用TCP,须先建立TCP连接,形成数据传输通道
②数据传输前,会进行“三次握手”,是可靠的
③数据传输完毕,需释放已建立的连接,效率低
2、UDP(用户数据报协议)协议:适用于数据要求较低的场景,可能会丢失数据包,比如影视类的数据就可以采用UDP协议传输,基于DatagramSocket和DatagramPacket编程
①将数据、源、目的封装成数据包,不需要事先建立连接,但不可靠(有时会丢包)
②每个数据包的大小限制在64K内
③数据发送完毕无需释放资源,效率高
三、Socket
网络套接字(Socket):是IP和端口号的组合,java中有对应的Socket类,可以创建Socket对象。利用套接字(Socket)开发网络应用程序早已被广泛采用,以至于成为事实上的标准。通信的两端都要都Socket,是两台机器间通信的端点,网络通信其实就是Socket间的通信。Socket允许程序把网络连接当成一个流,数据在两个Socket间通过IO传输。
客户端传输数据步骤:
①创建Socket对象(ip地址,端口号):
Socket socket = new Socket(InetAddress,port);//一定要指明IP地址和端口号
②获取输出流对象,以往外传输数据:
OutputStream os = socket.getOutputStream();
③通过写的方式传递数据:
os.write(“hello,world!”.getBytes());
④关闭流和Socket:
os.close();
socket.close();
服务端接收数据步骤:
①通过端口号创建ServerSocket对象:
ServerSocket ss = new ServerSocket(port);//此端口号要与客户端一致
②再通过ServerSocket对象的accept()方法获取Socket对象:
Socket s = ss.accept();
③通过Socket对象获取InputStream对象:
InputStream is = s.getInputStream();
④利用InputStream对象读取客户端传输过来的内容,方式与IO流中的读取方式相同
⑤关闭所有的资源:
is.close();
s.close();
ss.close();
注意
:先开服务端,再开客户端,否则会中断连接
示例1:客户端与服务端的简单通信
public class TestCS {
@Test
public void client() {
Socket socket = null;
OutputStream os = null;
InputStream is = null;
InputStreamReader isr = null;
try {
socket = new Socket("127.0.0.1", 9090);
os = socket.getOutputStream();
os.write("这里是空中一号,请求迫降!".getBytes());
// 告诉服务端输出完毕,否则会引起阻塞
socket.shutdownOutput();// 这句话很重要,否则会引起阻塞
is = socket.getInputStream();//获取服务端的响应信息
isr = new InputStreamReader(is);
char[] b = new char[10];
int len;
while ((len = isr.read(b)) != -1) {
String str = new String(b, 0, len);
System.out.print(str);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (isr != null) {
try {
isr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (os != null) {
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
@Test
public void server() {
ServerSocket ss = null;
Socket s = null;
InputStream is = null;
InputStreamReader isr = null;
OutputStream os = null;
try {
ss = new ServerSocket(9090);
s = ss.accept();
is = s.getInputStream();//获取客户端的请求信息
isr = new InputStreamReader(is);
char[] b = new char[10];
int len;
while ((len = isr.read(b)) != -1) {
String str = new String(b, 0, len);
System.out.print(str);
}
System.out.println();
System.out.println("收到来自:" + s.getInetAddress().getHostAddress() + "的请求。");
os = s.getOutputStream();
os.write("已收到你的请求,可以迫降!".getBytes());//给客户端的响应
} catch (IOException e) {
e.printStackTrace();
} finally {
if (isr != null) {
try {
isr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (os != null) {
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (s != null) {
try {
s.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (ss != null) {
try {
ss.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
注意
:节点流中的read()方法是阻塞式的,在没有内容可读的时候会一直等待,当在读取客户端传输的数据时,由于不知道数据什么时候传输完毕,就会一直等待读取数据,就会引起阻塞,所以发送数据的那一端要调用socket.shutdownOutput();方法知会另一端,告诉它数据已传输完毕,以避免阻塞。缓冲流(Buffered)中的read()方法是非阻塞式的。
示例2:客户端从本地读取文件并传输到服务端,服务端收到文件并下载到本地
public class DownloadTest {
@Test
public void client() throws Exception {
Socket socket = new Socket("127.0.0.1", 9797);
OutputStream os = socket.getOutputStream();
FileInputStream fis = new FileInputStream("07.jpg");
byte[] b = new byte[20];// 此数组可供该方法中的多个流公用
int len;
while ((len = fis.read(b)) != -1) {
os.write(b, 0, len);
}
socket.shutdownOutput();
InputStream is = socket.getInputStream();
while ((len = is.read(b)) != -1) {
String str = new String(b, 0, len);
System.out.print(str);
}
}
@Test
public void server() throws Exception {
ServerSocket ss = new ServerSocket(9797);
Socket socket = ss.accept();
InputStream is = socket.getInputStream();
FileOutputStream fos = new FileOutputStream("1.jpg");
byte[] b = new byte[30];
int len;
while ((len = is.read(b)) != -1) {
fos.write(b, 0, len);
}
System.out.println("收到来自:" + socket.getInetAddress().getHostAddress() + "发来的图片.");
OutputStream os = socket.getOutputStream();
os.write("你发送的图片已收到".getBytes());
socket.shutdownOutput();
}
}
四、DatagramSocket
Socket和ServerSocket是基于TCP协议的,UDP协议下使用的是DatagramSocket和DatagramPacket。
UDP数据报通过数据报套接字(DatagramSocket)发送和接收,系统不保证UDP数据报一定能够安全送到目的地,也不确定什么时候可以抵达。
DatagramPacket对象封装了UDP数据报,在数据报中包含了发送端的IP地址和端口号以及接收端的IP地址和端口号。UDP协议中每个数据报都给出了完整的地址信息,因此无需建立发送方和接收方的网络连接。
UDP发送信息的步骤:
①创建DatagramSocket(数据报套接字)的对象:
DatagramSocket ds = new DatagramSocket();
②创建DatagramPacket(数据报)的对象:
//携带数据和目的地的IP和端口号
DatagramPacket dp = new DatagramPacket(b,0,len,address,port);
③调用DatagramSocket对象的send()方法发送数据:
ds.send(dp);//发送的即为DatagramPacket数据报
④关闭套接字对象:
ds.close();
UDP接收信息的步骤:
①根据发送端的端口号创建DatagramSocket的对象:
DatagramSocket ds = new DatagramSocket(port);//该端口号要与发送端的一致
②创建一个字节数组,用来存放接收到的数据:
byte[] b = new byte[1024];
③以字节数组为参数创建一个数据报对象保存数据:
//不需要ip和端口号,因为是接收数据,不需要发送出去
DatagramPacket dp = new DatagramPacket(b,0,b.length);
④调用套接字对象的receive()方法,接收数据:
//由于端口号和发送端的相同,因此接收到的数据肯定是来自发送端的,将接收来的数据保存在dp对象中
ds.recieve(dp);
⑤读取数据:
String str = new String(dp.getData(),0,dp.getLength());
⑥打印输出:
System.out.println(str);
示例:在接收字符串的时候,有可能定义的字节数组的长度不够,而只能接收到一部分传送过来的字符串而导致数据丢失,这时可以另外定义一个字符串,让每次读取的字符串都加在它身上
public class UDPTest {
@Test
public void send() {
DatagramSocket ds = null;
try {
ds = new DatagramSocket();
byte[] b = "我是发送端...".getBytes();
DatagramPacket pack = new DatagramPacket(b, 0, b.length, InetAddress.getByName("127.0.0.1"), 9090);
ds.send(pack);
} catch (IOException e) {
e.printStackTrace();
} finally {
ds.close();
}
}
@Test
public void recieve() {
DatagramSocket ds = null;
try {
ds = new DatagramSocket(9090);
byte[] b = new byte[1024];
DatagramPacket dp = new DatagramPacket(b, 0, b.length);
ds.receive(dp);
String str = new String(dp.getData(), 0, dp.getLength());
System.out.println(str);
} catch (IOException e) {
e.printStackTrace();
} finally {
ds.close();
}
}
}
注意:要先开接收端,否则虽然不抛异常但接收不到数据
五、URL
URL(Uniform Resource Locator),统一资源定位符,它表示Internet上某一资源的地址。通过URL我们可以访问Internet上的各种网络资源,比如常见的www、ftp(文件传输协议-File Transfer Protocol)站点。浏览器通过解析给定的URL可以在网络上查找相应的文件或其他资源。
URL的基本结构:<传输协议>://<主机名>:<端口号>/<文件名>,例如:http://192.168.1.100:8080/hello/index.jsp。
和File对象对应一个硬盘中的文件类似,一个URL的对象对应一个网络中的资源:
URL url = new URL("http://127.0.0.1:8080/examples/Hello.txt");
URL类的构造方法都抛出非运行时异常,通常使用try-catch进行捕获处理。一个URL对象生成后,其属性是不能被改变的,可以通过它给定的方法来获取这些属性的值,相应的方法如下:
public String getProtocol();//获取URL的协议名称
public String getHost();//获取URL的主机名
public String getPort();//获取URL的端口号
public String getPath();//获取URL的文件路径
public String getFile();//获取URL的文件名
public String getRef();//获取URL在文件中的相对位置
public String getQuery();//获取URL的查询字符串
URL的openStream()方法返回一个InputStream,可以将网络上数据读取到内存,当然,读取到内存后也可以使用输出流输出;URL还提供了一种输出数据的方式,通过URLConnection先与URL建立连接,然后对其进行读写。
URLConnection表示到URL所引用的远程对象的连接,当与一个URL建立连接时,首先要一个URL对象通过openConnection()方法生成对应的URLConnection对象,如果连接失败,将抛出IOException异常:
URL url = new URL("http://www.bdm.com/index.jsp");
URLConnection uc = url.openConnection();
从网络上获取资源的步骤:
①创建URL的对象:
URL url = new URL("http://127.0.0.1:8080/examples/Hello.txt");
②用URL对象调用其openStream()方法:
InputStream is = url.openStream();//获取输入流
③读数据:和之前的流读数据时一样,如果需要将该文件保存下来,可以再将读取的内容写成一个对应格式的文件
byte[] b = new byte[20];
int len;
while(){
//……
}
④关闭流:
is.close();
也可以通过URLConnection对象获取输入流和输出流,实现和现有的CGI程序交互:
public Object getContent()
public int getContentLength()
public String getContentType()
public long getDate()
public long getLastModified()
public InputStream getInputStream()
public OutputStream getOutputStream()
示例:网络下载资源
public class NetDownload {
public static void main(String[] args) throws Exception {
URL url = new URL("http://127.0.0.1:8080/examples/Hello.txt");
// 从控制台输出网上的资源
InputStream is = url.openStream();
InputStreamReader isr = new InputStreamReader(is, "GB2312");// 读取的格式要和写入的一致
char[] c = new char[10];
int len;
String str = "";
while ((len = isr.read(c)) != -1) {
String s = new String(c, 0, len);
str += s;
}
System.out.println(str);
is.close();
System.out.println("----------------------------");
// 将文件下载到本地
URLConnection openConnection = url.openConnection();
InputStream is1 = openConnection.getInputStream();
FileOutputStream fos = new FileOutputStream("hello.txt");
byte[] b = new byte[10];
int len1;
while ((len1 = is1.read(b)) != -1) {
// String str1 = new String(b);
// fos.write(b.toString().getBytes());
fos.write(b, 0, len1);
}
fos.close();
is1.close();
}
}