Java网络编程(Socket、URLConnection)

TCP和OSI参考模型

要想计算机之间是如何进行通信的,我们知道数据都是以0101这种二级制数字在电脑中保存并在互联网中进行通信的,单纯的分析二进制数据完全无从下手,TCP和OSI参考模型为我们进行数据通信的分析提供了方便。这里进行简单的介绍,毕竟这不是重点,我们的重点是java网络编程!!!

在这里插入图片描述

现在TCP/IP已经成为国际标准了,其实以前没有OSI和TCP/IP谁是老大老二,OSI还是IETF组织经过研究的推出的,但是最后还是被这个毛头小子TCP/IP独占天下,所以说被推崇的东西不一定是最好的。言归正传,我们开发一般混迹与传输层和网络层,java不是服务端的语言吗,服务端只管提供服务就好了,至于有没有漂亮的界面(应用层的部分)交给前端去做就ok了,我们只管数据的提供,可以说是我们是数据生产者。各层的功能各位去看计算机网络去吧!

混迹于各层的网络协议

不成规矩不成方圆,就像交通法规一样,红灯停绿灯行黄灯亮了等一等,你就想如果计算机在互联网中传递数据报文如果没有规律,那不得乱成一套。所以说网络协议就相当于生活中的交通法规,比如http、ftp、smtp、tcp、udp、arp等,他们位于网络模型的哪几层,我们要有个清晰的认识

在这里插入图片描述

其中最重要的是,其实都很重要,这里我们说开发中比较常用的比如http、TCP、UDP和IP。互联网中主机的通信对于用户来说大部分都是将数据以一定的形式展示给用户,从底层的01数据流到IP、TCP、UDP相应的数据封装,最后以http协议或者其他以文本格式或者图片形式展示给用户。(大致的分析)

UDP协议

UDP(User Data Protocol)用户数据协议,无连接,不可靠;传输快,系统耗费资源低,适用于对性能要求高于据完整性的场景。

运行于UDP协议之上的协议:

  • DHCP协议:动态主机配置协议,动态配置IP地址
  • NTP协议:网络时间协议,用于网络时间同步
  • BOOTP协议:引导程序协议,DHCP协议的前身,用于无盘工作站从中心服务器上获取IP地址

TCP协议

TCP(Transfer Control Protocol)传输控制协议,三次握手建连接,传输可靠,大数据传输,效率略低于UDP,适用于对数据完整性要求比较高的场景

运行于TCP协议之上的协议:

  • HTTP协议:超文本传输协议,用于普通浏览
  • HTTPS协议:安全超文本传输协议,身披SSL外衣的HTTP协议
  • FTP协议:文件传输协议,用于文件传输
  • POP3协议:邮局协议,收邮件使用
  • SMTP协议:简单邮件传输协议,用来发送电子邮件
  • Telent协议:远程登陆协议,通过一个终端登陆到网络
  • SSH协议:安全外壳协议,用于加密安全登陆,替代安全性差的Telent协议

InetAddress类及其常用方法

InetAddress类提供了操作互联网上IP地址和域名的的相关方法,该类本身没有构造函数,而是通过调用相关静态方法获取实例。常用方法如下:

方法名称说明
byte[] getAddress()返回此 InetAddress 对象的原始 IP 地址
static InetAddress getByAddress(byte[] addr)在给定原始 IP 地址的情况下,返回 InetAddress 对象
static InetAddress getByAddress(String host)在给定主机名的情况下确定主机的 IP 地址
String getCanonicalHostName()获取此 IP 地址的完全限定域名
String getHostAddress()返回 IP 地址字符串(以文本表现形式)
String getHostName()返回此 IP 地址的主机名
static InetAdderss getLocalHost()返回本地主机

UDP之DatagramSocket&DatagramPacket

DatagramPacket的构造方法

构造方法说明
DatagramPacket(byte[] buf,int length)构造 DatagramPacket,用来接收长度为 length 的数据包
DatagramPacket(byte[] buf,int offset, int length)构造 DatagramPacket,用来接收长度为 length 的包,在缓冲区中指定了偏移量
DatagramPacket(byte[] buf,int length, InetAddress address,int port)构造 DatagramPacket,用来将长度为 length 的包发送到指定主机上的指定端口
DatagramPacket(byte[] buf,int length, SocketAddress address)构造数据报包,用来将长度为 length 的包发送到指定主机上的指定端口

DatagramPacket常用方法

方法说明
InetAddress getAddress()返回某台机器的 IP 地址,此数据报将要发往该机器或者从该机器接收。
byte[] getData()返回数据缓冲区。
int getLength()返回将要发送或者接收的数据的长度。
int getPort()返回某台远程主机的端口号,此数据报将要发往该主机或者从该主机接收。
getSocketAddress()获取要将此包发送或者发出此数据报的远程主机的SocketAddress(通常为 IP地址+端口号)。

DatagramSocket构造方法

构造方法说明
DatagramSocket()构造数据报包套接字并将其绑定到本地主机上任何可用的端口。
DatagramSocket(int port)创建数据报包套接字并将其绑定到本地主机上的指定端口。
DatagramSocket(int portJnetAddress addr)创建数据报包套接字,将其绑定到指定的本地地址。
DatagramSocket(SocketAddress bindaddr)创建数据报包套接字,将其绑定到指定的本地套接字地址。

DatagramSocket 的常用方法

方法说明
void bind(SocketAddress addr)将此 DatagramSocket 绑定到特定的地址和端口。
void close()关闭此数据报包套接字。
void connect(InetAddress address,int port)将套接字连接到此套接字的远程地址。
void connect(SocketAddress addr)将此套接子连接到远程套接子地址(IP地址+端口号)。
void disconnect()断开套接字的连接。
InetAddress getInetAddress()返回此套接字连接的地址。
InetAddress getLocalAddress()获取套接字绑定的本地地址。
int getLocalPort()返回此套接字绑定的本地主机上的端口号。
int getPort()返回此套接字的端口。

使用UDP协议模拟从客户端输入信息发送一条消息,服务端接收并显示

public class UDPClient {
 
    public static void main(String[] args) throws IOException {
        // client
        // 建立服务
        DatagramSocket ds = null;
        // 获取输入字符
        BufferedReader br = null;
        try {
            if (ds == null)
                ds = new DatagramSocket();
            if (br == null)
                br = new BufferedReader(new InputStreamReader(System.in));
            // 把读取的资源打包
            byte[] buf = br.readLine().getBytes();
            DatagramPacket dp = new DatagramPacket(buf, buf.length, InetAddress.getLocalHost(), 10005);
            // 将打包好的数据发送出去
            ds.send(dp);
        } catch (IOException e) {
            System.out.println("数据发送失败");
        } finally {
            ds.close();
            if (br != null)
                br.close();
        }
        // server
    }
 
}
public class UDPServer {
 
    public static void main(String[] args) throws IOException {
        // 创建服务
        DatagramSocket ds = null;
        DatagramPacket dp = null;
        try {
            if (ds == null)
                ds = new DatagramSocket(10005);
            // 创建缓冲区
            byte[] buf = new byte[1024];
            // 接收数据
            if (dp == null)
                dp = new DatagramPacket(buf,buf.length);
            ds.receive(dp);
            String ip = dp.getAddress().getHostAddress();
            //输出数据到控制台
            System.out.println(ip+":"+new String(buf, 0, dp.getLength()));
        } catch (IOException e) {
            System.out.println(e.getMessage());
        } finally {
            ds.close();
        }
    }
}

注意先开启server端的程序,因为UDP是无状态的协议,如果先开启Client那么Client只会发送一条消息,他不管你是否监听,只管发送,可能就会导致输出空白,鬼知道你有没有开启Server端口的监听。

UDP之聊天案例

public class NetChatDemo {
 
    public static void main(String[] args) throws IOException {
        new Thread(new Server()).start();
        DatagramSocket ds = null;
        BufferedReader br = null;
        try {
            // 发送数据的时候指定端口
            ds = new DatagramSocket();
            br = new BufferedReader(new InputStreamReader(System.in));
            byte[] buf;
            String line = null;
            while ((line = br.readLine()) != null) {
                buf = line.getBytes();
                DatagramPacket dp = new DatagramPacket(buf, buf.length, InetAddress.getLocalHost(), 10006);
                ds.send(dp);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            ds.close();
            if (br != null) {
                br.close();
            }
        }
    }
}
class Server implements Runnable {
    @Override
    public void run() {
        DatagramSocket ds = null;
        DatagramPacket dp = null;
        // 循环包裹,如果单单一个线程没有循环,那么运行一遍就完事了
        while (true) {
            try {
                ds = new DatagramSocket(10006);
                byte[] buf = new byte[1024];
                dp = new DatagramPacket(buf, buf.length);
                ds.receive(dp);
                String ip = dp.getAddress().getHostAddress();
                System.out.println(ip+" : "+new String(buf, 0, dp.getLength()));
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                ds.close();
                System.out.println("run end...");
            }
        }
    }
}

TCP之Socket&ServerSocket

Socket构造函数

Socket(InetAddress address,int port)创建一个流套接字并将其连接到指定IP地址的端口
Socket(String host,int port)创建一个流套接字并将其连接到指定主机上的端口

Socket常用方法

void close()关闭此套接字。
InetAddress getInetAddress()返回套接字的连接地址。
InputStream getInputStream()返回此套接字的输入流。
OutputStream getOutputStream()返回此套接字的输出流。
int getPort()返回此套接字连接的远程端口。
void shutdownInput( )将套接字的输入流关闭
void shutdownOutput( )将套接字的输出流关闭

Java使用TCP进行发送数据

  • Socket建立连接
  • Socket获取输出流,想输出流中写数据
  • shutdownOutput关闭发送流
  • Socket获取输入流,获取服务端给与的反馈信息
  • 关闭连接

Java使用TCP进行接收数据

  • 实例化ServerSocket对象并设置监听端口
  • 调用ServerSocket的accept方法获取Socket对象
  • 从Socket对象中获取输入流进行操作
  • 利用输出流向客户端发送信息
  • 关闭连接

总结:底层还是借助输入输出流进行数据的传递,只要对java流的操作很熟练,那么这些都不在话下了

public class SocketDemo {
 
    public static void main(String[] args) {
        new Thread(new Server()).start();
        Socket socket = null;
        try {
            socket = new Socket(InetAddress.getLoopbackAddress(), 13000);
            byte[] buf = "新冠带来的影响".getBytes();
 
            // 获取输出流
            OutputStream out = socket.getOutputStream();
            out.write(buf);
 
            // 关闭发送流
            socket.shutdownOutput();
 
            // 获取输入流,获取反馈信息
            InputStream in = socket.getInputStream();
            byte[] buffer = new byte[1024];
            int len = in.read(buffer);
            System.out.println(new String(buffer, 0, len));
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (socket != null) {
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
 
class Server implements Runnable {
 
    @Override
    public void run() {
        ServerSocket ss = null;
        Socket s = null;
        InputStream in = null;
        try {
            ss = new ServerSocket(13000);
            s = ss.accept();
 
 
            String ip = s.getInetAddress().getHostAddress();
            // 输出连接上来的机器
            System.out.println(ip + "->connected");
 
            in = s.getInputStream();
 
        // 读取数据
        byte[] buf = new byte[1024];
        int len = 0;
        while ((len = in.read(buf)) != -1) {
            System.out.println(new String(buf, 0, len));
        }
        s.shutdownInput();
        // 发送反馈信息
        OutputStream out = s.getOutputStream();
        out.write("服务端收到了!".getBytes());
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                s.close();
                ss.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
 
 
    }
}

URI和URL

URI简介

  1. URI(Uniform Resource Identifier)统一资源标识符,字如其名,它是采用特定的语法标识互联网主机上的资源的字符串表示。这里不需要纠结标识的目标资源到底是什么,因为使用者不会见到资源的实体,从服务器接受的只是01流。

  2. URI的语法构成

    URI的语法构成是:一个模式和一个模式特定部分

    [ scheme :][ // authority][ path][ ? query][ # fragment]

    URI的常用模式(scheme)包括data:(链接中直接包含经过BASE64编码的数据。)、file:(本地磁盘上的文件)、ftp、http、mailto(电子邮件协议的地址)、magnet(可以通过对等网络比如端对端P2PBitTorrent下载的资源)、telnet。authority部分指定了负责解析该URI其他部分的授权机构(authority),一般URI都是使用Internet主机作为授权机构。典型的URI的其他三个部分(模式特定部分,也就是授权机构、路径和查询参数)分别由ASCII字母组成(也就是字母A-Z、a-z和数字0-9),此外,还可以使用标点符号-、_、.、!和~,而定界符(如/、?、&和=)有其预定义的用途。所有的其他字符,包括ASCII中拉丁字母,都需要使用百分号(%)转义,转义后的格式是:%+字符按照UTF-8编码再转成16进制的字符串表示。注意一点,如果前面提到的定界符没有作为定界符使用,也需要进行转义。举个简单的例子,如URI中存在中文字符"木",木字的UTF-8编码为0xE6 0x9C 0xA8,那么它在URI中应该转义为%E6%9C%A8。Jdk中的URLEncoder或者apache-codec中的相关类库提供URI(URL)编码的功能。

URL简介

  1. URL(Uniform Resource Location)统一资源定位符。URL本质是一种特殊的URI,它除了可以标识资源外,还可以定位资源,也就是提供了访问资源的入口,通过URL就可以访问到特定主机上的某个资源。

    URL所表示的网络资源位置通常包括用于访问服务器的协议(如http、ftp等)、服务器的主机名或者IP地址、以及资源文件在该服务器上的路径。典型的URL例如http://localhost/myProject/index.html,它指示本地服务器的myProject目录下有一个名为index.html的文档,这个文档可以通过http协议访问(实际上,URL不一定是指服务器中的真实的物理路径,因为我们一般在服务器中部署应用,如Servlet应用,URL访问的很可能是应用的接口,至于最终映射到什么资源可以由应用自身决定)。

  2. URL的语法

    protocol://userInfo@host:port/path?query#fragment

    protocol:其实就是URI模式中schema的另外一个叫法,常用的和schema前面列出的大致一样。

    port:URL中的端口号是指服务器中应用运行的端口,默认为80(可选)

    path:表示服务器上的一个特定文件或目录,可以是相对的。

    query:查询参数,key1=value1&key2=value2…形式

  • URI&URL

    换句话说,每个URL都是URI,但是不是每个URI都是URL的。他们之间最明显的不同就是在java.net.URI你只能看到他的一些属性,他只是表示一个位置,**但是你没有办法通过URI获取到这个对象的流,但是URL就不同了。java.net.URL该类提供方法(openConnection()),通过该方法我们可以通过IO流操作他。**但是URI中我貌似没看到相关的方法。

URI和URL的简单使用

  1. URI的使用,URI并不常用,所以这里没有列举相应的方法,实际中也不会去记忆的每个方法,主要用的时候查看底层源码结合注释来使用相应方法。

    // URI.create(String str)底层还是通过构造new
            URI uri = URI.create("http://localhost:8080/index.html");
            String authority = uri.getAuthority();
            System.out.println("authority-->" + authority);
            String host = uri.getHost();
            System.out.println("host-->" + host);
            String path = uri.getPath();
            System.out.println("path-->" + path);
            int port = uri.getPort();
            System.out.println("port-->" + port);
            // 输出
            // authority-->localhost:8080
            // host-->localhost
            // path-->/index.html
            // port-->8080
            URI uri1 = URI.create("http://localhost:8080/index?key=你好世界");
            /**
             * toString()方法返回未编码的字符串形式,也就是特殊的字符不使用百分号转义,
             * 因此这个方法的返回值不能保证符合URI的语法,尽管它的各个部分是遵循URI的语法规范的。
             * toASCIIString()方法返回经过编码的字符串形式(US-ACSII编码),也就是特殊的字符一定经过了百分号转义。
             * toString()存在的意义是提高URI的可读性,toASCIIString()方法存在的意义是提高URI的可用性。
             */
            System.out.println(uri1.toString());
            System.out.println(uri1.toASCIIString());
            // 输出
            // http://localhost:8080/index?key=你好世界
            // http://localhost:8080/index?key=%E4%BD%A0%E5%A5%BD%E4%B8%96%E7%95%8C
    
  2. URL的使用

    URL的构造

    //基于URL的各个部分构造URL实例,其中file相当于path、query和fragment三个部分组成
    public URL(String protocol, String host, int port, String file) throws MalformedURLException
    
    //基于URL的各个部分构造URL实例,其中file相当于path、query和fragment三个部分组成,使用默认端口80
    public URL(String protocol, String host, String file) throws MalformedURLException
    
    //基于URL模式构造URL实例
    public URL(String spec) throws MalformedURLException
    
    //基于上下文(父)URL和URL模式构造相对URL实例
    public URL(URL context, String spec) throws MalformedURLException
    

    获取URL的属性

    public final InputStream openStream() throws java.io.IOException
    
    public URLConnection openConnection() throws java.io.IOException
    
    public URLConnection openConnection(Proxy proxy) throws java.io.IOException
    
    public final Object getContent() throws java.io.IOException
    
    public final Object getContent(Class[] classes) throws java.io.IOException
    

    获取URL的组成部分

    
    //获取模式(协议)
    public String getProtocol()
    
    //获取主机名
    public String getHost()
    
    //获取授权机构,一般是host:port的形式
    public String getAuthority()
    
    //获取端口号port
    public int getPort()
    
    //返回协议的默认端口,如http协议的默认端口号为80,如果没有指定协议的默认端口则返回-1
    public int getDefaultPort()
    
    //返回URL字符串中从主机名后的第一个斜杆/一直到片段标识符的#字符之前的所有字符
    public String getFile()
    
    //返回的值和getFile()相似,但是不包含查询字符串
    public String getPath()
    
    //返回URL的片段标识符部分
    public String getRef()
    
    //返回URL的查询字符串
    public String getQuery()
    
    //返回URL中的用户信息,不常用
    public String getUserInfo()
    

    示例

    // 注意file前要加前缀 "/"
            URL url = new URL("http", "localhost", 80, "/index?key=hello");
            System.out.println(url.toString());// http://localhost:80/index?key=hello
            System.out.println(url.getPath());// /index 不包含查询字段
            System.out.println(url.getFile());//  /index?key=hello包含查询路径
    
            // 1.1 使用URL.openStream获取输入流
            URL url1 = new URL("http://www.baidu.com");
            InputStream in = url1.openStream();
            BufferedReader reader = new BufferedReader(new InputStreamReader(in));
            String result;
            while ((result = reader.readLine()) != null) {
                System.out.println(result);// 输出 百度首页 html代码
            }
            in.close();
            reader.close();
    
            // 1.2. 使用URLConnection.getInputStream获取输入流
            URLConnection urlConnection = url1.openConnection();
            in = urlConnection.getInputStream();
            reader = new BufferedReader(new InputStreamReader(in));
            while ((result = reader.readLine()) != null) {
                System.out.println(result);// 输出 百度首页 html代码
            }
            // 关闭流
            in.close();
            reader.close();
    
            
    

URL的编码和解码

为什么需要进行编码呢?这个是历史遗留原因,因为发明Web或者说Http(s)协议的时候,Unicode编码并未完全普及,URL中使用的字符必须来自ASCII的一个固定子集,这个固定子集是:

  • 大写字母A-Z。
  • 小写字母a-z。
  • 数字0-9。
  • 标点符号字符-_.!~*'(和,)

其他字符如:/&?@#;$+=%也可以使用,但是它们限定于使用在特定的用途,如果这些字符串出现在URL路径或者查询字符串,它们以及路径和查询字符串的内容必须进行编码。(避免歧义)

这里有个需要注意的地方:URL编码只针对URL路径和URL路径之后的部分,因为URL规范中规定,路径之前的部分必须满足ASCII固定子集的内容。

URL url = new URL("http://www.baidu.com/s?wd=新冠");
// URL编码针对path及path之后的
// String s = URLEncoder.encode(url.toString(), "utf-8");
// 很显然不符合URL规范 http%3A%2F%2Fwww.baidu.com%2Fs%3Fwd%3D%E6%96%B0%E5%86%A0
String file = url.getFile();
String[] split = file.split("=");
String encode = URLEncoder.encode(split[1], "utf-8");
String decode = URLDecoder.decode(encode, "utf-8");
System.out.println(decode);

URLConnection&HttpURLConnection

在JDK的java.net包中已经提供了访问HTTP协议的基本功能的类:HttpURLConnection。

HttpURLConnection是Java的标准类,它继承自URLConnection,可用于向指定网站发送GET请求、POST请求。

注意事项:

  • HttpURLConnection对象不能直接构造,需要通过URL类中的openConnection()方法来获得。

  • HttpURLConnection的connect()函数,实际上只是建立了一个与服务器的TCP连接,并没有实际发送HTTP请求。HTTP请求实际上直到我们获取服务器响应数据(如调用getInputStream()、getResponseCode()等方法)时才正式发送出去。

  • 对HttpURLConnection对象的配置都需要在connect()方法执行之前完成。

  • HttpURLConnection是基于HTTP协议的,其底层通过socket通信实现。如果不设置超时(timeout),在网络异常的情况下,可能会导致程序僵死而不继续往下执行。

  • HTTP正文的内容是通过OutputStream流写入的, 向流中写入的数据不会立即发送到网络,而是存在于内存缓冲区中,待流关闭时,根据写入的内容生成HTTP正文。
    调用getInputStream()方法时,返回一个输入流,用于从中读取服务器对于HTTP请求的返回信息。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值