Java网络编程

记录一些知识点,包括java.net包里的常用类

Posted by LJJ on June 8, 2019

网络编程

简述:做的一些个人感觉容易忽视和比较重要的知识点,关于计算机网络方面会有单独的记录,不详细展开,这里只针对java中的网络编程基础部分。

什么叫做URL
  • 在www上,每一信息资源都有统一且唯一的地址,该地址就叫URL(Uniform Resource Locator),它是www的统一资源定位符。
  • URL由4部分组成:协议 、存放资源的主机域名、资源文件名和端口号。如果未指定该端口号,则使用协议默认的端口。
  • 例如http 协议的默认端口为 80。 在浏览器中访问网页时,地址栏显示的地址就是URL。
  • 在java.net包中提供了URL类,该类封装了大量复杂的涉及从远程站点获取信息的细节。

Scoket

  • 在应用层和传输层之间,使用套接Socket来进行分离。
  • Socket实际是传输层供给应用层的编程接口。
  • Socket编程可以开发客户机和服务器应用程序,可以在本地网络上进行通信,也可通过Internet在全球范围内通信。

.

java.net包中的常用类

Java为了可移植性,不允许直接调用操作系统,而是由java.net包来提供网络功能。Java虚拟机负责提供与操作系统的实际连接。

#### InetAddress

  • 作用:封装计算机的IP地址和DNS(没有端口信息)。
  • 这个类没有构造方法。如果要得到对象,只能通过静态方法:getLocalHost()、getByName()、 getAllByName()、 getAddress()、getHostName()。

InetSocketAddress

  • 包含IP和端口信息,常用于Socket通信。此类实现 IP 套接字地址(IP 地址 + 端口号),不依赖任何协议。

URL

  • URL标识了计算机上的资源。
  • 类 URL 代表一个统一资源定位符,它是指向互联网“资源”的指针。资源可以是简单的文件或目录,也可以是对更为复杂的对象的引用,例如对数据库或搜索引擎的查询。
  • DK中提供了URL类,该类的全名是java.net.URL,有了这样一个类,就可以使用它的各种方法来对URL对象进行分割、合并等处理。

下面是URL类的使用方法:

public class TestUrl {

	public static void main(String[] args) throws MalformedURLException {
	
		URL url = new URL("https://www.google.com/imgres?imgurl=http%3A%2F%2Fpic.netbian.com%2Fuploads%2Fallimg%2F191006%2F005346-1570294426c666.jpg&imgrefurl=http%3A%2F%2Fpic.netbian.com%2F4kfengjing%2F&tbnid=nDH0bFXlObwFBM&vet=12ahUKEwjDsYG8pNrlAhWnHjQIHb7TCzgQMygAegUIARDcAQ..i&docid=HiLtAxuwJ1_OzM&w=450&h=287&q=%E9%A3%8E%E6%99%AF&hl=zh-CN&ved=2ahUKEwjDsYG8pNrlAhWnHjQIHb7TCzgQMygAegUIARDcAQ");
		System.out.println("获取与此url默认关联的协议的默认端口:"+url.getDefaultPort());
		System.out.println("端口号后面的内容getFile:"+url.getFile());
		System.out.println("主机名:"+url.getHost());
		System.out.println("路径:"+url.getPath());
		System.out.println("获取端口号:"+url.getPort());
		System.out.println("获取协议:"+url.getProtocol());
		System.out.println("参数部分:"+url.getQuery());
		System.out.println("锚点:"+url.getRef());

​ }

}

看控制台输出信息:

获取与此url默认关联的协议的默认端口:443
端口号后面的内容getFile:/imgres?imgurl=http%3A%2F%2Fpic.netbian.com%2Fuploads%2Fallimg%2F191006%2F005346-1570294426c666.jpg&imgrefurl=http%3A%2F%2Fpic.netbian.com%2F4kfengjing%2F&tbnid=nDH0bFXlObwFBM&vet=12ahUKEwjDsYG8pNrlAhWnHjQIHb7TCzgQMygAegUIARDcAQ..i&docid=HiLtAxuwJ1_OzM&w=450&h=287&q=%E9%A3%8E%E6%99%AF&hl=zh-CN&ved=2ahUKEwjDsYG8pNrlAhWnHjQIHb7TCzgQMygAegUIARDcAQ
主机名:www.google.com
路径:/imgres
获取端口号:-1
获取协议:https
参数部分:imgurl=http%3A%2F%2Fpic.netbian.com%2Fuploads%2Fallimg%2F191006%2F005346-1570294426c666.jpg&imgrefurl=http%3A%2F%2Fpic.netbian.com%2F4kfengjing%2F&tbnid=nDH0bFXlObwFBM&vet=12ahUKEwjDsYG8pNrlAhWnHjQIHb7TCzgQMygAegUIARDcAQ..i&docid=HiLtAxuwJ1_OzM&w=450&h=287&q=%E9%A3%8E%E6%99%AF&hl=zh-CN&ved=2ahUKEwjDsYG8pNrlAhWnHjQIHb7TCzgQMygAegUIARDcAQ
锚点:null

下面这个是获取百度url的一些东西,得到的是它的整个页面html信息:

public class TestSpider {

	public static void main(String[] args) {
		basicSpider();
	}

	 static void basicSpider() {
	        URL url = null;
	        InputStream is = null;
	        BufferedReader br = null;
	        StringBuilder sb = new StringBuilder();
	        String temp = "";
	        try {
	            url = new URL("http://www.baidu.com");
	            is = url.openStream();
	            br = new BufferedReader(new InputStreamReader(is));
	            /* 
	             * 这样就可以将网络内容下载到本地机器。
	             * 然后进行数据分析,建立索引。这也是搜索引擎的第一步。
	             */
	            while ((temp = br.readLine()) != null) {
	                sb.append(temp);
	            }
	            System.out.println(sb);
	        } catch (MalformedURLException e) {
	            e.printStackTrace();
	        } catch (IOException e) {
	            e.printStackTrace();
	        } finally {
	            try {
	                br.close();
	            } catch (IOException e) {
	                e.printStackTrace();
	            }
	            try {
	                is.close();
	            } catch (IOException e) {
	                e.printStackTrace();
	            }
	        }
	    }
	}

控制台输出:

<!DOCTYPE html><!--STATUS OK--><html> <head><meta http-equiv=content-type content=text/html;charset=utf-8><meta http-equiv=X-UA-Compatible content=IE=Edge><meta content=always name=referrer><link rel=stylesheet type=text/css href=http://s1.bdstatic.com/r/www/cache/bdorz/baidu.min.css><title>鐧惧害涓?涓嬶紝浣犲氨鐭ラ亾</title></head> <body link=#0000cc> <div id=wrapper> <div id=head> <div class=head_wrapper> <div class=s_form> <div class=s_form_wrapper> <div id=lg> <img hidefocus=true src=//www.baidu.com/img/bd_logo1.png width=270 height=129> </div> <form id=form name=f action=//www.baidu.com/s class=fm> <input type=hidden name=bdorz_come value=1> <input type=hidden name=ie value=utf-8> <input type=hidden name=f value=8> <input type=hidden name=rsv_bp value=1> <input type=hidden name=rsv_idx value=1> <input type=hidden name=tn value=baidu><span class="bg s_ipt_wr"><input id=kw name=wd class=s_ipt value maxlength=255 autocomplete=off autofocus></span><span class="bg s_btn_wr"><input type=submit id=su value=鐧惧害涓?涓? class="bg s_btn"></span> </form> </div> </div> <div id=u1> <a href=http://news.baidu.com name=tj_trnews class=mnav>鏂伴椈</a> <a href=http://www.hao123.com name=tj_trhao123 class=mnav>hao123</a> <a href=http://map.baidu.com name=tj_trmap class=mnav>鍦板浘</a> <a href=http://v.baidu.com name=tj_trvideo class=mnav>瑙嗛</a> <a href=http://tieba.baidu.com name=tj_trtieba class=mnav>璐村惂</a> <noscript> <a href=http://www.baidu.com/bdorz/login.gif?login&amp;tpl=mn&amp;u=http%3A%2F%2Fwww.baidu.com%2f%3fbdorz_come%3d1 name=tj_login class=lb>鐧诲綍</a> </noscript> <script>document.write('<a href="http://www.baidu.com/bdorz/login.gif?login&tpl=mn&u='+ encodeURIComponent(window.location.href+ (window.location.search === "" ? "?" : "&")+ "bdorz_come=1")+ '" name="tj_login" class="lb">鐧诲綍</a>');</script> <a href=//www.baidu.com/more/ name=tj_briicon class=bri style="display: block;">鏇村浜у搧</a> </div> </div> </div> <div id=ftCon> <div id=ftConw> <p id=lh> <a href=http://home.baidu.com>鍏充簬鐧惧害</a> <a href=http://ir.baidu.com>About Baidu</a> </p> <p id=cp>&copy;2017&nbsp;Baidu&nbsp;<a href=http://www.baidu.com/duty/>浣跨敤鐧惧害鍓嶅繀璇?</a>&nbsp; <a href=http://jianyi.baidu.com/ class=cp-feedback>鎰忚鍙嶉</a>&nbsp;浜琁CP璇?030173鍙?&nbsp; <img src=//www.baidu.com/img/gs.gif> </p> </div> </div> </div> </body> </html>

基于TCP协议的Socket编程和通信

  • 主机地址就是客户端或服务器程序所在的主机的IP地址。端口地址是指客户端或服务器程序使用的主机的通信端口。
  • 在客户端和服务器中,分别创建独立的Socket,并通过Socket的属性,将两个Socket进行连接,这样,客户端和服务器通过套接字所建立的连接使用输入输出流进行通信。
  • TCP/IP套接字是最可靠的双向流协议,使用TCP/IP可以发送任意数量的数据。

.

通过Socket的编程顺序:

  1. 创建服务器ServerSocket,在创建时,定义ServerSocket的监听端口(在这个端口接收客户端发来的消息)。
  2. ServerSocket调用accept()方法,使之处于阻塞状态。
  3. 创建客户端Socket,并设置服务器的IP及端口。
  4. 客户端发出连接请求,建立连接。
  5. 分别取得服务器和客户端Socket的InputStream和OutputStream。
  6. 利用Socket和ServerSocket进行数据传输。
  7. 关闭流及Socket。

单向通信的服务端代码:

public class BasicSocketServer {

	public static void main(String[] args) {
		Socket socket = null;
		BufferedWriter bw =null;
		try {
			// 建立服务器端套接字,指定监听的接口
			ServerSocket serverScoket = new ServerSocket(8888);
			System.out.println("服务端建立监听");
			// 监听,等待客户端请求,并愿意接受链接
			socket = serverScoket.accept();
			// 获取Scoket的输出流,并用缓冲流进行包装
			bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
			
			// 向客户端发送反馈信息
			bw.write("客户端你好,我是服务端!");
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}finally {
			// 关闭流及Scoket连接
			if(bw!=null) {
				try {
					bw.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
			if(socket!=null) {
				try {
					socket.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}

	}

}

接下来是客户端代码:

public class BasicSocketClient {

​ public static void main(String[] args) {

		Socket socket = null;
		BufferedReader br = null;
		try {
			/**
			 * 创建Socket对象,指定要连接的服务器IP和端口,而不是自己机器的端口。
			 * 发送端口是随机的。
			 */
			socket = new Socket(InetAddress.getLocalHost(), 8888);
			// 获取Socket的输入流,并用缓冲流进行包装
			br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
			// 接收服务端发送的信息
			System.out.println(br.readLine());
		
		} catch (IOException e) {
			e.printStackTrace();
		}finally {
			// 关闭流和Socket
			if(br!=null) {
				try {
					br.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
			if(socket!=null) {
				try {
					socket.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}

}

注意:程序运行要先运行服务端代码,再运行客户端代码。单向通信的意思是指信息只能由服务端传送到客户端。这里需要了解整个服务端到客户端通信的Socket建立过程,以及两者是如何进行的通信。
输出控制台:

客户端接收.PNG

双向通信服务端客户端演示

  • 多线程实现的双向通讯。
  • 服务器端/客户端:一个线程专门发送消息,一个线程专门接收消息。

最终运行效果,客户端输出台:

客户端输出台.PNG

服务端输出台:

服务端输出台.PNG

这样就完成了双向通信。

客户端代码;

public class ChartClient {
    public static void main(String[] args) {
        Socket socket = null;
        BufferedReader in = null;
        try {
            socket = new Socket(InetAddress.getByName("127.0.1.1"), 8888);
            // 创建向服务器端发送信息的线程,并启动
            new ClientThread(socket).start();
            in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            // main线程负责接收服务器发来的信息
            while (true) {
                System.out.println("服务器说:" + in.readLine());
            }
        } catch (UnknownHostException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (socket != null) {
                    socket.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                if (in != null) {
                    in.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
 
/**
 * 用于向服务器发送消息
 * 
 * @author Administrator
 *
 */
class ClientThread extends Thread {
    Socket s;
    BufferedWriter out;
    BufferedReader wt;
 
    public ClientThread(Socket s) {
        this.s = s;
        try {
            out = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
            wt = new BufferedReader(new InputStreamReader(System.in));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
 
    public void run() {
        try {
            while (true) {
                String str = wt.readLine();
                out.write(str + "\n");
                out.flush();
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (wt != null) {
                    wt.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                if (out != null) {
                    out.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

服务端代码;

public class ChartServer {
    public static void main(String[] args) {
        ServerSocket server = null;
        Socket socket = null;
        BufferedReader in = null;
        try {
            server = new ServerSocket(8888);
            socket = server.accept();
            //创建向客户端发送消息的线程,并启动
            new ServerThread(socket).start();
            // main线程负责读取客户端发来的信息
            in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            while (true) {
                String str = in.readLine();
                System.out.println("客户端说:" + str);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (in != null) {
                    in.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                if (socket != null) {
                    socket.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
 
/**
 * 专门向客户端发送消息的线程
 * 
 * @author Administrator
 *
 */
class ServerThread extends Thread {
    Socket ss;
    BufferedWriter out;
    BufferedReader br;
 
    public ServerThread(Socket ss) {
        this.ss = ss;
        try {
            out = new BufferedWriter(new OutputStreamWriter(ss.getOutputStream()));
            br = new BufferedReader(new InputStreamReader(System.in));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
 
    public void run() {
        try {
            while (true) {
                String str2 = br.readLine();
                out.write(str2 + "\n");
                out.flush();
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if(out != null){
                out.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                if(br != null){
                    br.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

这里注意的是,运行时要先启动服务端,后启动客户端。在彼此交流通信时,最好开两个控制输出台来分别控制输出。

  • Eclipse开多个控制台的方法

运行单个程序,在窗口右下角找到下图标:

添加console.png

选择新建一个console,之后点击左边的那个按钮:

控制窗口.png

即可设置控制台所属的程序。

UDP通讯的实现

  • DatagramSocket:用于发送或接收数据报包
    • 当服务器要向客户端发送数据时,需要在服务器端产生一个DatagramSocket对象,在客户端产生一个DatagramSocket对象。
    • 服务器端的DatagramSocket将DatagramPacket发送到网络上,然后被客户端的DatagramSocket接收。
    • DatagramSocket有两种常用的构造函数.
      • DatagramSocket() :构造数据报套接字并将其绑定到本地主机上任何可用的端口。
      • DatagramSocket(int port) :创建数据报套接字并将其绑定到本地主机上的指定端口。
    • 常用方法
      • send(DatagramPacket p) :从此套接字发送数据报包。
      • receive(DatagramPacket p) :从此套接字接收数据报包。
      • close() :关闭此数据报套接字。
  • DatagramPacket:数据容器(封包)的作用
    • 常用方法
      • DatagramPacket(byte[] buf, int length) :构造数据报包,用来接收长度为 length 的数据包。
      • DatagramPacket(byte[] buf, int length, InetAddress address, int port) :构造数据报包,用来将长度为 length 的包发送到指定主机上的指定端口号。
      • getAddress() :获取发送或接收方计算机的IP地址,此数据报将要发往该机器或者是从该机器接收到的。
      • getData() :获取发送或接收的数据。
      • setData(byte[] buf) :设置发送的数据。
  • UDP通信编程基本步骤:
    1. 创建客户端的DatagramSocket,创建时,定义客户端的监听端口。
    2. 创建服务器端的DatagramSocket,创建时,定义服务器端的监听端口。
    3. 在服务器端定义DatagramPacket对象,封装待发送的数据包。
    4. 客户端将数据报包发送出去。
    5. 服务器端接收数据报包。

单向通信示例,从客户端到服务端:

客户端:

public class Client {
	public static void main(String[] args) throws SocketException {
		byte[] b = "从前有个小和尚".getBytes();
		// 封装待发送的数据包,指定发送的计算机及其端口号
		DatagramPacket dp = new DatagramPacket(b, b.length, new InetSocketAddress("localhost", 8888));
		// 创建DatagramSocket数据报套接字,用于发送数据
		DatagramSocket ds = new DatagramSocket(9999);
		try {
			// 发送数据报
			ds.send(dp);
		} catch (IOException e) {
			e.printStackTrace();
		}
		// 关闭资源
		ds.close();
	}
}

服务端:

public class Server {
	public static void main(String[] args) throws IOException {
		// 创建数据报套接字,指定接收信息的端口
		DatagramSocket ds = new DatagramSocket(8888);
		// 创建封装数据报包,指定长度与缓存地
		byte[] b = new byte[1024];
		DatagramPacket dp = new DatagramPacket(b, b.length);
		// 阻塞接收数据报
		ds.receive(dp);
		String string = new String(dp.getData(), 0, dp.getLength());
		System.out.println(string);
		// 关闭资源
		ds.close();
	}
}

注意这里要先运行服务端代码,后运行客户端,实现效果如下:

udp单向.PNG