Java 网络编程相关知识
- 2020 年 4 月 11 日
- 筆記
网络的一些基础知识
IP地址分类
IP地址根据网络ID的不同分为5种类型,A类地址、B类地址、C类地址、D类地址和E类地址。A类保留给政府机构,B类分配给中等规模的公司,C类分配给任何需要的人,D类用于组播,E类用于实验,各类可容纳的地址数目不同。(IP地址由网络号和主机号组成)。
-
A类地址
一个A类IP地址由1字节的网络地址和3字节主机地址组成,网络地址的最高位必须是“0”, 地址范围从1.0.0.0 到126.0.0.0。可用的A类网络有126个,每个网络能容纳1亿多个主机。A类地址一般分配给大型网络。
- A类地址第1字节为网络地址,其它3个字节为主机地址,默认的子网掩码255.0.0.0。
- A类地址范围:0.0.0.0—126.255.255.255(0.0.0.0和126.255.255.255这种IP一般不使用)。
- A类地址中的私有地址和保留地址。
- 10.X.X.X是私有地址(所谓的私有地址就是在互联网上不使用,而被用在局域网络中的地址)。
- 127.X.X.X是保留地址,用做循环测试用的。
-
B类地址
一个B类IP地址由2个字节的网络地址和2个字节的主机地址组成,网络地址的最高位必须是“10”,地址范围从128.0.0.0到191.255.255.255。可用的B类网络有16382个,每个网络能容纳6万多个主机,一般分配给中型网络 。
- B类地址第1字节和第2字节为网络地址,其它2个字节为主机地址,默认子网掩码是255.255.0.0。
- B类地址范围:128.0.0.0—191.255.255.255(128.0.0.0和191.255.255.255这种地址一般不用)。
- B类地址的私有地址和保留地址。
- 172.16.0.0—172.31.255.255是私有地址。
- 169.254.X.X是保留地址。如果你的IP地址是自动获取IP地址,而你在网络上又没有找到可用的DHCP服务器。就会得到其中一个IP。
-
C类地址
一个C类IP地址由3字节的网络地址和1字节的主机地址组成,网络地址的最高位必须是“110”。范围从192.0.0.0到223.255.255.255。C类网络可达209万余个,每个网络能容纳254个主机。
- C类地址前3个字节为网络地址,第4个个字节为主机地址。另外第1个字节的前三位固定为110,默认子网掩码是255.255.255.0。
- C类地址范围:192.0.0.0—223.255.255.255(192.0.0.0和223.255.255.255一般不用)。
- C类地址中的私有地址。
- 192.168.X.X是私有地址。
-
D类地址
D类IP地址第一个字节以“1110”开始,它是一个专门保留的地址。它并不指向特定的网络,目前这一类地址被用在多点广播(Multicast)中。多点广播地址用来一次寻址一组计算机,它标识共享同一协议的一组计算机。
- D类地址不分网络地址和主机地址,它的第1个字节的前四位固定为1110。
- D类地址范围:224.0.0.1—239.255.255.254。
-
E类地址
以“11110”开始,为将来使用保留。全零(“0.0.0.0”)地址对应于当前主机。全“1”的IP地址(“255.255.255.255”)是当前子网的广播地址。
- E类地址也不分网络地址和主机地址,它的第1个字节的前五位固定为11110。
- E类地址范围:240.0.0.1—255.255.255.254
端口分类
- 公认端口(well know port):从0到1023,他们紧密绑定一些特定服务。比如80端口绑定http服务,443端口绑定https服务,22端口绑定ssh服务等;
- 主粗端口(Registed port):1024到49151,他们松散的绑定一些服务,我们自己写的应用程序应该使用这个范围内的端口;
- 动态或私有端口:49152到65535,这些端口应用程序会动态使用。
Java的基本网络支持
Java中对网络支持的类大都在java.net这个包下面,常用的类有URL、URLConnection、URLDecoder、URLENcoder和InetAddress等。
InetAddress的使用
InetAddress代表IP地址,它有两个子类Inet4Address和Inet6Address。
public class InetAddressDemo { public static void main(String[] args) throws Exception { //获取本机的IP地址 InetAddress localHost = InetAddress.getLocalHost(); System.out.println("host address:"+localHost.getHostAddress()); System.out.println("host name:"+localHost.getHostName()); //根据百度的域名,随机获取百度的一个IP地址 InetAddress baidu = InetAddress.getByName("www.baidu.com"); System.out.println("host address:"+baidu.getHostAddress()); System.out.println("host name:"+baidu.getHostName()); //根据IP地址,货期InetAddress InetAddress loopAddress = InetAddress.getByAddress(new byte[]{127,0,0,1}); System.out.println("host address:"+loopAddress.getHostAddress()); System.out.println("host name:"+loopAddress.getHostName()); //根据域名,获取域名对应的所有IP地址 InetAddress[] baidus = InetAddress.getAllByName("www.baidu.com"); for (InetAddress inetAddress : baidus) { System.out.println("host address:"+inetAddress.getHostAddress()); System.out.println("host name:"+inetAddress.getHostName()); } } }
URLEncoder和URLDecoder的使用
当URL地址中包含非西欧字符的字符串时,系统会将这些非西欧字符自动编码。在我们编程过程中就会涉及到将这些普通的字符串和特殊字符串之间的转换,这时就需要使用URLEncoder和URLDecoder。
String encodedUrl = "https://www.baidu.com/s?wd=ip%E5%9C%B0%E5%9D%80%E5%88%86%E7%B1%BB&rsv_spt=1&rsv_iqid=0xe6273c5300071242&issp=1&f=8&rsv_bp=1&rsv_idx=2&ie=utf-8&rqlang=cn&tn=baiduhome_pg&rsv_enter=1&oq=IP%25E5%259C%25B0%25E5%259D%2580%25E5%2588%2586%25E7%25B1%25BB&rsv_t=8798Pr4JiDoBuHX8kSW6i384TlOk5p8vEQ4c4tWrc0suF31CjvBh6stq0gyq0PtETa9x&inputT=9569&rsv_sug3=24&rsv_sug1=14&rsv_sug7=100&rsv_pq=b3c08c4200035639&bs=IP%E5%9C%B0%E5%9D%80%E5%88%86%E7%B1%BB"; String decodeURL = URLDecoder.decode(encodedUrl, Charset.forName("UTF8").name()); System.out.println("decodeURL:"+decodeURL);
URLEncoder和URLDecoder这两个类只提供了encode和decode方法供我们使用。
URL、URLConnection和URLPermission的使用
这边先讲下URL和URI的区别:
URI:是uniform resource identifier,统一资源标识符,用来唯一的标识一个资源。Web上可用的每种资源如HTML文档、图像、视频片段、程序等都是一个来URI来定位的URI一般由三部组成:①访问资源的命名机制②存放资源的主机名③资源自身的名称,由路径表示,着重强调于资源。
URL是uniform resource locator,统一资源定位器,它是一种具体的URI,即URL可以用来标识一个资源,而且还指明了如何locate这个资源。URL一般由三部组成:①协议(或称为服务方式)②存有该资源的主机IP地址(有时也包括端口号)③主机资源的具体地址。
简单的说,URI是一种互联网上资源的唯一标识符(我们可以将它看成一个资源的id),URL是一种特殊的URI,URL不仅能标识一个互联网资源,而且通过URL能够获取到这个资源。Java中的两个类URI和URL就分别对应这两个概念。URL类可以打开一个流来获取具体的资源。
String[] urls = {"https://img0.pconline.com.cn/pconline/1707/21/9625301_20150814_6d20f056ee9803d9419buyemaASeB0KJ_thumb.jpg"}; for (String url : urls) { URL url1 = new URL(url); int port = url1.getPort(); System.out.println("port:"+port); String host = url1.getHost(); System.out.println("host:"+host); String protocol = url1.getProtocol(); System.out.println("protocol:"+protocol); String file = url1.getFile(); System.out.println("fileName:"+file); //这段是否要设置权限,为什么open总是失败? InputStream inputStream = url1.openStream(); FileOutputStream fos = new FileOutputStream("D:\"+new Date()+".jpeg"); FileCopyUtils.copy(inputStream,fos); inputStream.close(); fos.close(); }
基于TCP协议的网络编程
使用ServerSocket建立TCP服务端
Java中能够接收其他通信实体请求的类是ServerSocket。这个对象可以监听来自客户端的Socket连接(每个TCP连接两个Socket,一个IP加一个端口组成一个Socket)。如果没有连接,它将一直处于等待状态。
- Socket accept():该方法返回客户端Socket,没有连接将一直处于等待状态(同步),线程也被阻塞(阻塞);
ServerSocket存在如下的构造函数:
- public ServerSocket(int port):指定端口,backlog默认50;
- public ServerSocket(int port, int backlog) :backlog用于指定连接队列的长度,这个值和操作系统也有关,尝试了下Windows下最多设置200个,如果我们设置的值超过200,就取200。
- public ServerSocket(int port, int backlog, InetAddress bindAddr):如果机器有多个网卡还可以指定具体监听哪个网卡。
(netstat 命令详解)
使用Socket进行通信
下面是一个很加单的clientSocket和serverSocket的列子:客户端每隔一秒钟给服务端发一个消息,服务端给出响应:
Socket clientSocket = new Socket("127.0.0.1",30000); //inputStream用来接受服务端返回的消息 InputStream inputStream = clientSocket.getInputStream(); //outputStream用来给服务端发消息 OutputStream outputStream = clientSocket.getOutputStream(); while (true){ outputStream.write(("hi, l am clinetSocket").getBytes()); byte[] bytes = new byte[1024]; inputStream.read(bytes); System.out.println("get message from server:"+new String(bytes)); Thread.sleep(1000); }
服务端程序
public class ServerSocketDemo { private static ExecutorService executorService = Executors.newFixedThreadPool(10); public static void main(String[] args) throws Exception { ServerSocket serverSocket = new ServerSocket(30000, 5); while (true) { Socket socket = serverSocket.accept(); System.out.println("get socket:" + socket); executorService.execute(new Printer(socket)); } } private static class Printer implements Runnable { private Socket socket; public Printer(Socket socket) { this.socket = socket; } @Override public void run() { OutputStream outputStream; InputStream inputStream; try { //对于服务端来说,inputStream用来接收客户端的报文 inputStream = socket.getInputStream(); //对于服务端来说,outputStream用来给客户端响应报文 outputStream = socket.getOutputStream(); while (true) { byte[] bytes = new byte[1024]; inputStream.read(bytes); System.out.println("wa.. l got you " + new String(bytes)); outputStream.write("l am serverSocket".getBytes()); } } catch (IOException e) { e.printStackTrace(); } } } }
半关闭的Socket
半关闭的Socket是指只关闭Socket的输入输出流,但是不关闭整个Socket连接。
使用NIO实现非阻塞的Socket通信
Java NIO下几个用于Socket通信的类的简单说明:
- Selector:它是SelectableChannel对象的多路复用器器,所有希望采用非阻塞方式进行通信的Channel都应该注册到Selector对象上。可以调用selector = Selector.open()来构造Selector对象。
- SelectionKey:一个Selector实例有三种SelectionKey集合。第一种是通过selector的keys()方法返回的所有SelectionKey集合,代表所有注册在这个Selector实例上的Channel;第二种是通过selectedKeys()方法返回的SelectionKey集合,代表需要进行IO处理的channel;第三种是已经取消注册的Channel,一般不用。
public class NioServerSocketDemo { private Selector selector; public static final int port = 30000; private Charset charset = Charset.forName("UTF-8"); public void init() throws Exception{ selector = Selector.open(); ServerSocketChannel server = ServerSocketChannel.open(); InetSocketAddress address = new InetSocketAddress("127.0.0.1",port); server.bind(address,5); server.configureBlocking(false); //serverSocketChannel也要注册到selector上面 server.register(selector, SelectionKey.OP_ACCEPT); //selector.select()会阻塞当前线程 //selector.select(long timeout),设置超时时间 //selector.selectNow()不会阻塞线程 while (selector.select()>0){ for(SelectionKey key : selector.selectedKeys()){ //已经处理过了,将其删除 selector.selectedKeys().remove(key); if(key.isConnectable()){ SocketChannel channel = (SocketChannel)key.channel(); System.out.println(channel+" has connected..."); } if(key.isAcceptable()){ SocketChannel acceptChannel = server.accept(); acceptChannel.configureBlocking(false); acceptChannel.register(selector,SelectionKey.OP_READ); key.interestOps(SelectionKey.OP_ACCEPT); } if(key.isReadable()){ SocketChannel channel = (SocketChannel)key.channel(); ByteBuffer buffer = ByteBuffer.allocate(1024); String content = ""; try{ while (channel.read(buffer)>0){ buffer.flip(); content+=charset.decode(buffer); } System.out.println("get content:"+content); channel.write(buffer); key.interestOps(SelectionKey.OP_READ); }catch (IOException ex){ key.cancel(); if(key.channel()!=null){ key.channel().close(); } } } } } } public static void main(String[] args) throws Exception { new NioServerSocketDemo().init(); } }
使用AIO实现非阻塞的Socket通信
public class AIOServerSocket { private static Charset charset = Charset.forName("UTF-8"); public static void main(String[] args) throws Exception { AsynchronousServerSocketChannel serverSocketChannel = AsynchronousServerSocketChannel.open(); serverSocketChannel.bind(new InetSocketAddress(30000),1000); while (true){ Future<AsynchronousSocketChannel> future = serverSocketChannel.accept(); AsynchronousSocketChannel socketChannel = future.get(); ByteBuffer buffer = ByteBuffer.allocate(1024); socketChannel.read(buffer); System.out.println("get from client:"+charset.decode(buffer)); } } }