Java基础之网络编程

网络编程概述

网络编程的本质是两个设备之间的数据交换(把一个设备中的数据发送给两外一个设备,然后接受另外一个设备反馈的数据)。

网络模型

  • OSI参考模型
  • TCP/IP 参考模型

网络通讯要素

概述

  • 包括IP地址、端口号和传输协议

Socket

简介

  • Socket就是为网络服务提供的一种机制。
  • 通信的两端都有Socket。
  • 网络通信其实就是Socket间的通信。
  • 数据在两个Socket间通过IO传输。

常用方法:

  • Socket(InetAddress address, int port) 创建一个流套接字并将其连接到指定 IP 地址的指定端口号。
  • InputStream getInputStream() 返回此套接字的输入流。
  • OutputStream getOutputStream() 返回此套接字的输出流。
  • void shutdownInput() 此套接字的输入流置于“流的末尾”。
  • void shutdownOutput() 禁用此套接字的输出流。用于客户端告诉服务端,数据发送完毕,让服务端停止读取。

IP地址

  • 网络中设备的标识,不易记忆,可用主机名
  • 本地回环地址:127.0.0.1 主机名:localhost

InetAddress

  • 概述:
    • 位于java.net包,表示互联网协议 (IP) 地址的类,构造方法私有,不能直接创建对象。
  • 常见方法:
    • static InetAddress getLocalHost() 返回本地主机。
    • static InetAddress getByName(String host) 在给定主机名的情况下确定主机的 IP 地址。
    • static InetAddress[] getAllByName(String host) 在给定主机名的情况下,根据系统上配置的名称服务返回其 IP 地址所组成的数组。
    • String getHostAddress() 返回 IP 地址字符串(以文本表现形式)。
    • String getHostName() 获取此 IP 地址的主机名。

端口号

  • 用于标识进程的逻辑地址,不同进程的标识
  • 有效端口:0~65535,其中 0~1024 系统使用或保留端口。
  • 所谓防火墙,其功能就是将发送到某程序端口的数据屏蔽掉以及将从该程序端口发出的数据也屏蔽掉。

传输协议:通讯的规则

  • 常见协议:UDP、TCP。
    • UDP
      • 将数据及源和目的封装成数据包中,不需要建立连接。
      • 每个数据报的大小在限制在64k内。
      • 因无连接,是不可靠协议。
      • 不需要建立连接,速度快。
    • 应用案例:DNS(域名解析,最先走是本地的hosts(C:\WINDOWS\system32\drivers\etc\hosts)文件,解析失败了,才去访问DNS服务器解析、获取IP地址)、SNMP、QQ、FeiQ聊天、在线视频语音等。
    • TCP
      • 建立连接,形成传输数据的通道。
      • 在连接中进行大数据量传输。
      • 通过三次握手完成连接,是可靠协议。
      • 必须建立连接,效率会稍低。
    • 应用案例:FTP(File Transfer Protocol)、Telnet、SMTP、HTTP、POP3 等。

UDP协议: 发送端&接收端

  • 概述:DatagramSocket(用来发送和接收数据报包的套接字)与DatagramPacket(数据报包)。

  • DatagramSocket中的方法:

    • DatagramSocket(int port) 创建数据报套接字并将其绑定到本地主机上的指定端口。
    • void send(DatagramPacket p) 从此套接字发送数据报包。
    • void receive(DatagramPacket p) 从此套接字接收数据报包。
  • DatagramPacket方法:

    • DatagramPacket(byte[] buf, int length, InetAddress address, int port) 构造数据报包,用来将长度为 length 的包发送到指定主机上的指定端口号。
    • DatagramPacket(byte[] buf, int length) 构造 DatagramPacket,用来接收长度为 length 的数据包。
    • InetAddress getAddress() 返回某台机器的 IP 地址,此数据报将要发往该机器或者是从该机器接收到的。
    • byte[] getData() 返回数据缓冲区。
    • int getLength() 返回将要发送或接收到的数据的长度。
  • UDP发送端:

    1. 使用DatagramSocket对象,建立udp socket服务 DatagramSocket ds = new DatagramSocket(6666);
    2. 确定数据,并封装成数据包。DatagramPacket(byte[] buf, int length, InetAddress address, int port);
    3. 通过DatagramSocket服务的void send(DatagramPacket p); 方法,将数据包发出去
    4. 关闭资源
    • 代码示例:
    class UdpSend {
    	public static void main(String[] args) throws Exception{
    		//1.创建udp服务。通过DatagramSocket对象。
    		DatagramSocket ds = new DatagramSocket(6666);//指定端口6666
    
    		//2.确定数据,并封装成数据包。DatagramPacket(byte[] buf, int length, InetAddress address, int port)
    		byte[] buf ="udp come test".getBytes();
    		DatagramPacket dp =
    			new DatagramPacket(buf,buf.length,InetAddress.getByName("192.168.0.100"),10000);
    
    		//3.通过socket服务,将已有数据包发送出去。通过send方法。
    		ds.send(dp);
    
    		//4.关闭资源。
    		ds.close();
    	}
    }
    
  • UDP接收端:

    1. 定义udp socket 服务。通常会监听一个端口。其实就是给这个接收网络应用程序定义数字标识。方便于明确哪些数据接收。
    2. 定义一个数据包,因为要存储接收到的字节数据。因为数据包对象中有更多功能可以提取字节数据中的不同数据信息。
    3. 通过socket服务的receive方法将收到的数据存入已定义好的数据包中。
    4. 通过数据包对象的特有功能,将这些不同的数据取出。打印在控制台上。
    5. 关闭资源。
    • 代码示例:
    class UdpRece {
    	public static void main(String[] args) throws Exception{
    		//1.创建udp socket服务,建立端点。
    		DatagramSocket ds = new DatagramSocket(10000);
    
    		//2.定义数据包。用于存储数据。
    		byte[] buf = new byte[1024];
    		DatagramPacket dp = new DatagramPacket(buf,buf.length);
    
    		//3.通过服务的receive方法将接收到数据存入数据包中。
    		ds.receive(dp);//阻塞式方法。
    
    		//4.通过数据包的方法获取其中的数据。
    		String ip = dp.getAddress().getHostAddress();
    
    		String data = new String(dp.getData(),0,dp.getLength());
    
    		int port = dp.getPort();
    
    		System.out.println(ip+"::"+data+"::"+port);
    
    		//5.关闭资源
    		ds.close();
    	}
    }
    
  • 聊天程序示例:

/*
需求:编写一个聊天程序。

分析:有收数据的部分和发数据的部分这两部分需要同时执行;
	 就需要用到多线程技术。一个线程控制接收,一个线程控制发。

	 因为收和发动作是不一致的,所以要定义两个run方法。
	 而且这两个方法要封装到不同的类中。

*/
import java.io.*;
import java.net.*;

//定义发送端类,实现Runnable接口,并覆盖run方法
class Send implements Runnable {
	private DatagramSocket ds;
	public Send(DatagramSocket ds) {
		this.ds = ds;
	}

	public void run() {
		try {
			//键盘录入
			BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
			String line = null;

			while ((line=bufr.readLine())!=null) {

				byte[] buf = line.getBytes();//需将字符串转成字节数组,便于发送
				DatagramPacket dp = new DatagramPacket(buf,buf.length,InetAddress.getByName("192.168.0.255"),10002);//0为网络段,255为发送广播
				ds.send(dp);

				if("886".equals(line))
					break;
			}
		}
		catch (Exception e) {
			throw new RuntimeException("发送端失败");
		}
	}
}

//定义接收端类,实现Runnable接口,并覆盖run方法
class Rece implements Runnable{
	private DatagramSocket ds;
	public Rece(DatagramSocket ds) {
		this.ds = ds;
	}

	public void run() {
		try {
			while (true) {
				byte[] buf = new byte[1024];
				DatagramPacket dp = new DatagramPacket(buf,buf.length);
				ds.receive(dp);

				String ip = dp.getAddress().getHostAddress();
				String data = new String(dp.getData(),0,dp.getLength());
				System.out.println(ip+"::"+data);

				if(data.equals("886"))
					System.out.println(ip + "...退出聊天室");
			}
		}
		catch (Exception e) {
			throw new RuntimeException("接收端失败");
		}
	}
}

class ChatDemo {
	public static void main(String[] args) throws Exception{
		DatagramSocket sendScoket = new DatagramSocket();
		DatagramSocket receScoket = new DatagramSocket(10002);
		new Thread(new Send(sendScoket)).start();
		new Thread(new Rece(receScoket)).start();
	}
}

TCP协议: 客户端&服务端

  • 概述:客户端(Client)对应的对象是Socket,服务端(Server)对应的对象是ServerSocket。客户端首先与服务端建立连接,形成通道(其实就是IO流),然后,数据就可以在通道之间进行传输,并且单个Server端可以同时与多个Client端建立连接。 建立连接后,通过Socket中的IO流进行数据的传输。关闭socket。同样,客户端与服务器端是两个独立的应用程序。

  • ServerSocket方法:

    • ServerSocket(int port) 创建绑定到特定端口的服务器套接字。
    • Socket accept() 侦听并接受到此套接字的连接。
  • TCP客户端

    • 客户端需要明确服务器的ip地址以及端口,这样才可以去试着建立连接,如果连接失败,会出现异常。连接成功,说明客户端与服务端建立了通道,那么通过IO流就可以进行数据的传输,而Socket对象已经提供了输入流和输出流对象,通过getInputStream( )和getOutputStream( )获取即可。与服务端通讯结束后,关闭Socket。

    • 代码示例:

    class TcpClient {
    	public static void main(String[] args) throws Exception{
    		//创建客户端的Socket服务。指定目的主机端口
    		Socket s = new Socket("192.168.0.100",10003);
    
    		//为了发送数据,应该获取Socket流中的输出流。
    		OutputStream out = s.getOutputStream();
    		out.write("tcp come test".getBytes());
    
    		s.close();
    	}
    }
    
  • TCP服务端

    • 需要明确它要处理的数据是从哪个端口进入的。当有客户端访问时,要明确是哪个客户端,可通过accept()获取已连接的客户端对象,并通过该对象与客户端通过IO流进行数据传输。当该客户端访问结束,关闭该客户端。
  • 注:TCP协议传输数据必须先开服务端,再开客户端。否则,客户端根本连接不上服务端。

    • 代码示例:
    class TcpServer {
    	public static void main(String[] args) throws Exception {
    		//建立服务端Socket服务。并监听一个端口。
    		ServerSocket ss = new ServerSocket(10003);
    
    		//通过accept方法获取连接过来的客户端对象
    		Socket s = ss.accept();//阻塞式
    
    		String ip = s.getInetAddress().getHostAddress();
    		System.out.println(ip+"...connected");
    
    		//获取客户端发过来的数据。那么要使用客户端的读取流来读取数据。
    		InputStream in = s.getInputStream();
    
    		byte[] buf = new byte[1024];
    		int len = in.read(buf);
    
    		System.out.println(new String(buf,0,len));
    
    		s.close();//关闭客户端。
    		ss.close();
    	}
    }
    
  • TCP协议上传文本文件示例:

import java.io.*;
import java.net.*;
class TextClient {
	public static void main(String[] args) throws Exception{
		Socket s = new Socket("192.168.0.100",10006);

		//读取要上传文件的数据
		BufferedReader bufr =
			new BufferedReader(new FileReader("client.txt"));
		PrintWriter out = new PrintWriter(s.getOutputStream(),true);

		String line = null;
		while ((line=bufr.readLine())!=null) {
			//向输出流中写数据,并刷新
			out.println(line);
		}

		//关闭客户端的输出流,告诉服务端,客户端写完了,相当于给流中加入一个结束标记*1.
		s.shutdownOutput();

		//读取并打印服务器发过来的数据
		BufferedReader bufIn =
			new BufferedReader(new InputStreamReader(s.getInputStream()));
		String str = bufIn.readLine();
		System.out.println(str);

		bufr.close();
		s.close();
	}
}

class TextServer {
	public static void main(String[] args) throws Exception{
		ServerSocket ss = new ServerSocket(10006);
		Socket s = ss.accept();

		String ip = s.getInetAddress().getHostAddress();
		System.out.println(ip+"...connected!");

		//获取客户端的数据,并写入到新文件中
		BufferedReader bufIn =
			new BufferedReader(new InputStreamReader(s.getInputStream()));

		PrintWriter out = new PrintWriter(new FileWriter("server.txt"),true);
		String line = null;
		while ((line=bufIn.readLine())!=null) {
			out.println(line);
		}

		//当文件数据全部写完后,向客户端发送上传成功的消息
		PrintWriter pw = new PrintWriter(s.getOutputStream(),true);
		pw.println("上传成功");

		out.close();
		s.close();
		ss.close();
	}
}

URI与URL

  • URI:统一资源标示符。
  • URL:统一资源定位符,也就是说根据URL能够定位到网络上的某个资源,它是指向互联网“资源”的指针。
  • 每个URL都是URI,但不一定每个URI都是URL。这是因为URI还包括一个子类,即统一资源名称(URN),它命名资源但不指定如何定位资源。

URL类

基本方法

  • URL(String spec) 根据 String 表示形式创建 URL 对象。
  • String getFile() 获取此 URL 的文件名。
  • String getHost() 获取此 URL 的主机名(如果适用)。
  • String getPath() 获取此 URL 的路径部分。
  • int getPort() 获取此 URL 的端口号。
  • String getProtocol() 获取此 URL 的协议名称。
  • String getQuery() 获取此 URL 的查询部
  • InputStream openStream() 打开到此 URL 的连接并返回一个用于从该连接读入的 InputStream。