网络编程

说到网络编程,能想到的都有哪些呢?

网络编程和socket编程、ServerSocket、InetAddress、 URlConnection、RestTemplate、 HttpClient、套接字、shutdown、TCP、 UDP、OSI网络七层模型 ........

但是这些之间都有什么样的关系呢,究竟哪个对应底层,哪个对应实现呢?网络编程是否也有不同的实现方式呢,以及对应的应用场景分别是什么呢?

 

一、网络基础知识

1、网络分类

计算机网络种类按照不同的划分标准有多种。

规模大小和延伸范围:局域网(LAN)、城域网(MAN)、广域网(WAN)。Internet可是视为世界上最大的广域网

网络拓扑结构:总线型网络、环形网络、树形网络、星型网络

传输介质:双绞线网、同轴电缆网、光纤网、卫星网

 

2、网络设计

既然存在不同类型的网络,不同的网络之间要实现通讯要有统一的约定,这些约定就被称为通讯协议

国际标准化组织提出的『开放系统互联参考模型』即著名的 OSI (Open System Interconnection ), 力求将网络简化,并以模块的方式设计网络。

OSI是一种理想的模型,并没有实际落地。 TCP/IP四层模型是对OSI的简化,这个模型和OSI七层模型有大致的对应关系:

 

3、IP地址和端口号

IP地址用于唯一的标识网络种的一个通讯实体,这个实体可以是一台主机,一个打印机,或者路由器的某个端口。IP地址是数字型的,是一个32位(32bit)整数,为了方便记忆,把它分成4个8位的二进制,每个8位的整数可以转换成一个0~255的十进制整数。也就有了常见的IP格式:202.9.128.88。IP地址分为A B C D E 五类。

IP协议为多点广播提供了一批特殊的IP地址,这些IP地址的范围是224.0.0.0至239.255.255.255。每一个广播地址都被看成一个组,当客户需要发送、接收广播信息时,加入到该组即可。

 

一个通讯实体可以运行多个进程,端口就是进程与外界交流的出入口。不同的应用程序对应唯一的端口号。端口号可以从0到65535,通常可以分为3类:

公认端口:从0~1023, 紧密绑定一些特殊的服务

注册端口:1024~49151,松散绑定一些服务,应用程序通常应该使用这个范围的端口

动态和/或私有端口:49152~65535,应用程序使用的动态端口,应用程序一般不会主动使用这些端口

 

二、Java对网络编程的支持

Java为网络支持提供了java.net包,该包下的URL和URLConnection等类提供了以编程方式访问Web服务的功能,而 URLDecoder 和 URLEncoder 提供了普通字符串 和 applicaiton/w-www-form-urlencoded MIME字符串转换的静态方法(区别于NIO中学习的CharsetDecoder和CharsetEncoder)。

2.1 使用InetAddress代表IP地址

 

InetAddress两个子类,分别代表IPv4和IPv6地址

 

示例:

import java.io.IOException;

import java.net.InetAddress;

import java.net.UnknownHostException;

 

/**

 * InetAddress代表一个IP对象,是网络通讯的基础

 */

public class InetAddressTest {

    public static void main(String[] args) {

        try {

            // 1. 根据主机名获取InetAddress对象

            InetAddress ip = InetAddress.getByName("www.baidu.com");

            // 可达性的判断

            System.out.println(ip.isReachable(2000));

            // 获取IP的数字 字符串

            System.out.println(ip.getHostAddress());

            // 获取IP域名的字符串

            System.out.println(ip.getHostName());

            System.out.println(ip.getCanonicalHostName()); // 输出:180.101.49.11

 

            // 2. 根据原始IP获取对应的InetAddress对象

            InetAddress local = InetAddress.getByAddress(new byte[] {127001});

            System.out.println(local.isReachable(2000));

            // 获取IP地址的全限定域名

            System.out.println(local.getCanonicalHostName()); // 输出:localhost

 

            // 3. 获取本机IP地址对应的InetAddress实例

            InetAddress localHost = InetAddress.getLocalHost();

            System.out.println("localhost is " + localHost.getHostName()); //结果: localhost is MacBook-Pro.local

            System.out.println("localhost is " + localHost.getHostAddress()); // 结果: localhost is 172.18.19.32

 

            InetAddress loopBack = InetAddress.getLoopbackAddress();

            System.out.println("loopBack is " + loopBack.getHostName()); // 结果:loopBack is localhost

            System.out.println("loopBack is " + loopBack.getHostAddress()); // 结果:loopBack is 127.0.0.1

        catch (UnknownHostException e) {

            e.printStackTrace();

        catch (IOException e) {

            e.printStackTrace();

        }

    }

}

 

2.2 URLDecoder 和 URLEncoder 

URLDecoder 和 URLEncoder 提供了普通字符串 和 applicaiton/w-www-form-urlencoded MIME字符串 转换的静态方法

提问:什么是applicaiton/w-www-form-urlencoded MIME字符串呢?

URL地址中包含非西欧字符串时,系统会将这些非西欧字符串进行转换,转换的过程也就是编码的过程,这就需要使用到URLDecoder和URLEncoder类。

在转换中文字符时,每个中文字符占用两个字节,每个字节可以转换成两个十六进制的数据,所以每个中文字符转换成%XX%XX的形式。当然,采用不同的字符集,每个中文字符对应的字节数并不完全相同,所以使用URLDecoder和URLEncoder类进行转换时需要指定字符集。

转换方式:

 

示例:

import java.io.UnsupportedEncodingException;

import java.net.URLDecoder;

import java.net.URLEncoder;

public class URLDecoderTest {

    public static void main(String[] args) {

        try {

            String keyWord = URLDecoder.decode("encode result : java%E7%96%AF%E7%8B%82%E8%AE%B2%E4%B9%89""utf-8");

            System.out.println(keyWord);

 

            String urlStr = URLEncoder.encode("java疯狂讲义""utf-8");

            System.out.println("encode result : " + urlStr);

        catch (UnsupportedEncodingException e) {

            e.printStackTrace();

        }

    }

}


2.3 URL、URLConnection、URLPermission

URL : 代表统一资源定位器(可以进行定位资源),是指向互联网资源的指针, 通常由协议名、主机、端口和资源组成,即满足如下格式:protocol://host:port/resourceName, 示例:http://www.crazyit.org/index.php

URI  : 代表统一资源标识符(不能进行定位资源),相对URL,是不包含可以打开该资源的输入流的

URLConnection: 代表Java应用程序和URL表示的远程资源之间的通信链接。从这个连接中可以获取远程资源的输入流和相应信息

HttpURLConnection : 代表Java应用程序和URL表示的远程资源之间的HTTP链接

URLPermission : Java8中新增的类,用于管理HttpURLConnection的权限问题,如果在 HttpURLConnection安装了安全管理器,通过该对象打开连接时就需要先获得权限。

 

下面编程实现根据一个字符串的url创建连接,分别通过get 、post的方式发送请求,设置请求头信息,获取请求结果,打印响应内容:

主要步骤:

1)通过调用URL对象的 openConnection() 方法创建URLConnection对象

2)设置URLConnection的参数和普通请求属性,可以通过如下方法设置:

 

3) 如果时GET请求,使用connect()方法建立和远程资源嗯实际连接即可;如果是POST方式,则需要获取URLConnection实例对应的输出流来发送请求参数。

4)远程资源变为可用,程序可以访问远程资源的头字段 或 通过输入流读取远程资源的数据。可以通过如下方法访问响应的头字段和内容:

示例:

import java.io.BufferedReader;

import java.io.IOException;

import java.io.InputStreamReader;

import java.io.PrintWriter;

import java.net.MalformedURLException;

import java.net.URL;

import java.net.URLConnection;

import java.util.List;

import java.util.Map;

 

/**

 * 通过URLConnection获取响应的方式,无论是get还是post请求,获取方式一样。

 * <p>

 * 如果程序可以确定远程响应是字符流,则可以使用字符流读取;如果程序无法确定远程响应是字符流还是字节流,则使用字节流读取即可

 * <p>

 * 存在问题:

 * (1)太多的网站是需要登录,如何设置登录信息呢? 可以在请求头中设置Cookie

 * (2)post的大多请求类型是个json串,这种情况如何传参?可以参数就写成json串,并且请求头中设置Content-Type" =  "application/json"

 */

 

public class GetPostTest {

    public static void main(String[] args) {

        String url = "http://10.31.55.140:8618/label/table/queryPdfExtractResult";

        String param = "docId=20200511000001455257&t=1591168309332";

        System.out.println("result is :" + GetPostTest.sendGet(url, param));

 

        String url2 = "http://10.31.55.140:8618/label/ability/queryAbility";

        String param2 = "{\"name\":\"股权\",\"abilityTagIds\":null,\"hotest\":true,\"newest\":null}";

        System.out.println("result is :" + GetPostTest.sendPost(url2, param2));

    }

 

    public static String sendGet(String url, String param) {

        String result = "";

        String userName = url + "?" + param;

        try {

            URL realUrl = new URL(userName);

            URLConnection conn = realUrl.openConnection();

            conn.setRequestProperty("accept""application/json");

            conn.setRequestProperty("Connection""Keep-Alive");

            conn.setRequestProperty("user-agent",

                    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) "

                            "Chrome/83.0.4103.61 Safari/537.36");

            conn.setRequestProperty("Cookie""SESSION=NmE0NDdlYjgtZjdhZi00M2FlLWFhOWUtNTllNmNmY2RjNjYy");

            // 建立实际的连接

            conn.connect();

 

            // 获取所有响应头字段和值

            // 对应浏览器中response Headers中内容

            Map<String, List<String>> map = conn.getHeaderFields();

            for (Map.Entry entry : map.entrySet()) {

                System.out.println(entry.getKey() + " = " + entry.getValue());

            }

            System.out.println("-------------------------------------------");

            // 获取响应头中content-encoding字段值

            System.out.println(conn.getContentEncoding());

            // 获取响应头中content-length字段的值

            System.out.println(conn.getContentLength());

            // 获取响应头中content-type字段的值

            System.out.println(conn.getContentType());

            // 获取响应头中date字段的值

            System.out.println(conn.getDate());

            // 获取响应头中expires字段的值

            System.out.println(conn.getExpiration());

            // 获取响应头中last-modified字段的值

            System.out.println(conn.getLastModified());

            System.out.println("-------------------------------------------");

 

            BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream(), "utf-8"));

            String line;

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

                result += "\n" + line;

            }

 

        catch (MalformedURLException e) {

            System.out.println("MalformedURLException");

            e.printStackTrace();

        catch (IOException e) {

            System.out.println("IOException");

            e.printStackTrace();

        }

        return result;

    }

 

    public static String sendPost(String url, String param) {

        String result = "";

        try {

            URL realUrl = new URL(url);

            URLConnection conn = realUrl.openConnection();

            conn.setRequestProperty("accept""application/json");

            conn.setRequestProperty("Connection""Keep-Alive");

            conn.setRequestProperty("Content-Type""application/json");

            conn.setRequestProperty("user-agent",

                    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) "

                            "Chrome/83.0.4103.61 Safari/537.36");

            conn.setRequestProperty("Cookie""SESSION=NmE0NDdlYjgtZjdhZi00M2FlLWFhOWUtNTllNmNmY2RjNjYy");

 

            // 发送post请求必须设置下面两行

            conn.setDoOutput(true);

            conn.setDoInput(true);

 

            // 获取URLConnection对象对应的输出流

            PrintWriter out = new PrintWriter(conn.getOutputStream());

            // 发送请求参数

            out.print(param);

            // flush输出流的缓冲

            out.flush();

 

            BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream(), "utf-8"));

            String line;

            // readLine是阻塞方法

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

                result += "\n" + line;

            }

 

        catch (MalformedURLException e) {

            System.out.println("MalformedURLException");

            e.printStackTrace();

        catch (IOException e) {

            System.out.println("IOException");

            e.printStackTrace();

        }

        return result;

    }

}


三、基于TCP协议的网络编程

IP协议    是网络层的协议,IP协议负责将消息从一个主机发送到另外一个主机,消息在传输的过程被分割成一个个数据包。无法解决数据分组在传输过程中可能出现的问题。

TCP协议是传输层的协议,是一种可靠的协议,是一种端对端的协议。TCP协议负责将分组的数据包按照适当的次序放好发送,TCP协议使用重发机制,保证数据传输过程的准确无误。

(但是有了TCP协议并不代表数据传输就一定没有问题,还可能出现粘包等问题)

Java对TCP协议的网络通讯提供了良好的封装,Java使用Socket对象来代表两端的通信端口,并通过Socket产生IO流来进行网络通讯。

3.1、使用ServerSocket创建TCP服务端

TCP通讯涉及到两端:服务端和客户端,而当两端的通信链路建立完成后,是无所谓服务端和客户端的,那么如何区分服务端呢?

服务端定义:用于监听客户端请求连接的一方被叫做服务端,负责监听客户端请求的方法是ServerSocket的accept()方法。

所以:Java中服务端用ServerSocket表示,客户端用Socket表示

 

建立TCP连接用到的类主要包括:ServerSocket、Socket;TCP链接中读取数据用到的类主要包括 InputStream、InputStreamReader、BufferedReader、PrintStream

3.1.1 ServerSocket主要方法:

1、构造方法:

ServerSocket(int port) :默认本地的IP地址
ServerSocket(int port, int backlog) : backlog表示连接队列的最大长度
ServerSocket(int port, int backlog, InetAddress bindAddr):指定需要绑定的IP地址

2、常用其他方法

Socket accept(): 如果接收到客户端Socket的连接请求,返回一个与客户端Socket对应的Socket;否则该方法处于等待状态,线程也会被阻塞(程序停在该方法处)

3.1.2 Socket的主要方法

1、构造方法

Socket(InetAddress/String host, int port):创建连接到指定远程主机和端口的Socket,没有指定本地地址和端口,默认是本机地址, 默认使用系统动态分配的端口
Socket(String host, int port, InetAddress localAddr, int localPort) : 

1、主要方法

OutputStream getOutputStream() : 客户端使用,用于往socket中写数据
InputStream getInputStream() : 服务端使用,用于从socket中读数据

3.1.3 基于TCP协议的网络通讯简单示例

------------------------服务端-------------------------------

public class ServerSocketTest {

    public static void main(String[] args) {

        try {

            // 初始化服务端socket并且绑定9999端口

            ServerSocket serverSocket = new ServerSocket(9999);

            // 等待客户端的连接

            // accept会阻塞服务端,直到有客户端连接进来

            Socket socket = serverSocket.accept();

            // 获取输入流

            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));

            // 读取一行数据

            // read会阻塞服务端

            String str = bufferedReader.readLine();

            System.out.println(str);

 

            // 模拟服务端给客户端的返回信息

            PrintStream ps = new PrintStream(socket.getOutputStream());

            ps.println("我是服务端,我已接收到你的请求");

            ps.close();

 

        catch (IOException e) {

            e.printStackTrace();

        }

    }

}

------------------------客户端-------------------------------

 

public class ClientSocket {

    public static void main(String[] args) {

        try {

            Socket socket = new Socket("127.0.0.1"9999);

            BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));

            String str = "你好,这是我的第一个socket";

            bufferedWriter.write(str);

            // 要记得下面这个不能漏掉

            bufferedWriter.flush();

        catch (IOException e) {

            e.printStackTrace();

        }

    }

}

思考:上面客户端的示例中如果没有bufferedWriter.flush(),数据是不能发送到服务端的,那么Socket输出流如何表示输出的数据已结束?看下面3.1.4

3.1.4 Socket输出流如何表示输出的数据已结束?

 

 

重要问题:

Socket输出流如何表示输出的数据已结束?

1、关闭输出流会认为数据结束了(但是关闭输出流意味着对应的Socket也将关闭,这导致程序无法再从该Socket的输入流中读取数据,所以不可取)

2、

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值