ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

Java基础学习(十九)网络编程

2021-02-28 18:01:57  阅读:123  来源: 互联网

标签:Java Socket 编程 System println new null 十九 客户端


一、基本概念

1.1 七层网络模型

  • OSI(Open System Interconnect),即开放式系统互联,是ISO(国际标准化组织)组织在1985年研究的网络互连模型。

  • OSI七层模型划分如下:
    从上到下依次为应用层、表示层、会话层、传输层、网络层、数据链路层、物理层,其中应用层到传输层为端到端,网络层到物理层为点到点

  • 当发送数据时,需要对发送的内容按照上述七层模型进行层层加包后发送出去;当接收数据时,需要对接收的内容按照上述七层模型相反的次序层层拆包并显示出来。

  • 然而由于OSI的七层协议实现过分复杂、运行效率低、层次划分不合理,有些功能在多个层次中重复出现,只具有理论研究意义。

  • 事实上的工业标准是TCP/IP五层模型,划分如下图右侧五层部分:
    在这里插入图片描述

  • 应用层:负责处理特定程序细节,如Telnet(远程登录)、FTP(简单邮件传送协议)、SNMP(简单网络管理协议)

  • 传输层:为2台主机上的应用程序提供端到端的通信,有两个不同的协议:TCP(传输控制协议)、UDP(用户数据包协议)。

  • 网络层(N),又称互联网层、IP层,处理分组在网络中的活动(如路由选择)。包含了IP协议(网际协议)、ICMP协议(互联网控制报文协议)和IGMP协议(互联网组管理协议)。

  • 数据链路层(DL),又称链路层或网络接口层,处理与电缆的物理接口细节,根据LAN的特点(可用媒体较多且多个设备共享公共传输媒体),将此层分为两个子层:逻辑链路控制子层LLC(负责将不可靠的传输信道转换为可靠的信道)、媒体访问控制子层MAC(负责解决由哪个设备占有媒体的问题)。

  • 物理层(PH):负责体现机械、电气、功能和过程方面的特性,以建立、维护和拆除物理链路。


Q: 传输层和网络层服务非常相似,为什么要设置两个独立的层?
A:传输层代码完全运行在用户的机器上,而网络层代码主要运行在由运营商控制的路由器上,用户对网络层没有真正的控制权,所以需要传输层来提高网络的服务质量;
传输服务原语可以用过调用库程序来实现,从而使这些原语独立于网络服务原语。

传输服务原语:

PrimitivePacket sentMeaning
LISTEN(none)Block until some process tries to connect
CONNECTCONNECTION REQActively attempt to establish a connection
SENTDATASent Information
RECEIVE(none)Block until a DATA packet arrives
DISCONNECTDISCONNECTION REQThe side wants to release the connection

一个简单的面向连接的传输服务:

  • 建立连接:LISTEN、CONNECT原语

  • 数据传输:SEND、RECEIVE原语

  • 释放连接:DISCONNECTION原语

  • 非对称释放:任何一方发DISCONNECTION原语

  • 对称释放:双方都发DISCONNECTION原语


1.2 相关的协议

(1)协议的概念

  • 计算机在网络中实现通信就必须有一些约定或者规则,这种约定和规则就叫做通信协议,通信协议可以对速率、传输代码、代码结构、传输控制步骤、出错控制等制定统一的标准。

(2)TCP协议(传输控制协议)

  • 传输控制协议(Transmission Control Protocol),是一种面向连接的协议,类似于打电话。把应用程序交给它的数据分成合适的小块交给下面的网络层,确认收到的分组,设置发送,最后确认分组的超时时钟等。
  • 建立连接 => 进行通信 => 断开连接
  • 在传输前采用"三次握手"方式。能够区分旧的、重复的连接请求。

在这里插入图片描述

  • 在通信的整个过程中全程保持连接,形成数据传输通道。
  • 保证了数据传输的可靠性和有序性,即:具有顺序控制、差错控制和流量控制功能。
  • 是一种全双工字节流通信方式,可以进行大数据量的传输。
  • 传输完毕后需要释放已建立的连接,发送数据的效率比较低。
  • 断开时四次挥手
    在这里插入图片描述

(3)UDP协议(用户数据包协议) eg.在线视频

  • 用户数据报协议(User Datagram Protocol),是一种**非面向连接(面向无连接)**的协议,类似于写信。为应用层提供一种非常简单的服务,不保证可靠性,需要应用层负责可靠性,优点是速度快
  • 在通信的整个过程中不需要保持连接,其实是不需要建立连接。
  • 不保证数据传输的可靠性和有序性,即:无顺序控制、差错控制和流量控制功能,而是将这些功能交给应用程序完成。
  • 是一种全双工的数据报通信方式,每个数据报的大小限制在64K内。
  • 发送数据完毕后无需释放资源,开销小,发送数据的效率比较高,速度快。

1.3 IP地址(重点)

  • 192.168.1.1 - 是绝大多数路由器的登录地址,主要配置用户名和密码以及Mac过滤。
  • IP地址是互联网中的唯一地址标识,本质上是由32位二进制组成的整数,叫做IPv4,当然也有128位二进制组成的整数,叫做IPv6,目前主流的还IPv4。
  • 日常生活中采用点分十进制表示法来进行IP地址的描述,将每个字节的二进制转化为一个十进制整数,不同的整数之间采用小数点隔开。
  • 如:
    0x01020304 => 1.2.3.4
  • 查看IP地址的方式:
    Windows系统:在dos窗口中使用ipconfig或ipconfig/all命令即可
    Unix/linux系统:在终端窗口中使用ifconfig或/sbin/ifconfig命令即可
  • 特殊的地址
    本地回环地址(hostAddress):127.0.0.1 主机名(hostName):localhost

1.4 端口号(重点)

  • IP地址 - 可以定位到具体某一台设备。
  • 端口号 - 可以定位到该设备中具体某一个进程。
  • 端口号本质上是16位二进制组成的整数,表示范围是:0 ~ 65535,其中0 ~ 1024之间的端口号通常被系统占用,建议编程从1025开始使用。
  • 特殊的端口:
    HTTP:80 FTP:21 Oracle:1521 MySQL:3306 Tomcat:8080
  • 网络编程需要提供:IP地址 + 端口号,组合在一起叫做网络套接字:Socket

二、基于tcp协议的编程模型(重点)

2.1 C/S架构的简介

  • 在C/S模式下客户向服务器发出服务请求,服务器接收请求后提供服务。
  • 例如:在一个酒店中,顾客找服务员点菜,服务员把点菜单通知厨师,厨师按点菜单做好菜后让服务员端给客户,这就是一种C/S工作方式。如果把酒店看作一个系统,服务员就是客户端,厨师就是服务器。这种系统分工和协同工作的方式就是C/S的工作方式。
  • 客户端部分:为每个用户所专有的,负责执行前台功能。
  • 服务器部分:由多个用户共享的信息与功能,招待后台服务。

2.2 编程模型

  • 服务器:
    (1)创建ServerSocket类型的对象并提供端口号;
    (2)等待客户端的连接请求,调用accept()方法;
    (3)使用输入输出流进行通信;
    (4)关闭Socket;
  • 客户端:
    (1)创建Socket类型的对象并提供服务器的IP地址和端口号;
    (2)使用输入输出流进行通信;
    (3)关闭Socket;

2.3 相关类和方法的解析
(1)ServerSocket类

  • java.net.ServerSocket类主要用于描述服务器套接字信息(大插排)。
  • 常用的方法如下:
方法声明功能介绍
ServerSocket(int port)根据参数指定的端口号来构造对象
Socket accept()侦听并接收到此套接字的连接请求
void close()用于关闭套接字

(2)Socket类

  • java.net.Socket类主要用于描述客户端套接字,是两台机器间通信的端点(小插排)。
  • 常用的方法如下:
方法声明功能介绍
Socket(String host, int port)根据指定主机名和端口来构造对象
InputStream getInputStream()用于获取当前套接字的输入流
OutputStream getOutputStream()用于获取当前套接字的输出流(打印可用PrintStream类)
void close()用于关闭套接字

服务器只有一个主线程时:

public class ServerStringTest {

    public static void main(String[] args) {
        ServerSocket ss = null;
        Socket s = null;
        BufferedReader br = null;
        PrintStream ps = null;

        try {
            // 1.创建ServerSocket类型的对象并提供端口号
            ss = new ServerSocket(8888);

            // 2.等待客户端的链接请求,调用accept方法
            System.out.println("等待客户端的连接请求...");
            // 当没有客户端时,程序阻塞在accept方法这里
            s = ss.accept();
            System.out.println("客户端连接成功!");

            br = new BufferedReader(new InputStreamReader(s.getInputStream()));
            ps = new PrintStream(s.getOutputStream());
            // 3.使用输入输出流进行通信
            // 实现对客户端发来的字符串内容的接收并打印
            while(true) {
                // 当没有数据发来时,下面的方法会形成阻塞
                String s1 = br.readLine();
                System.out.println("客户端发来的字符串内容时:" + s1);
                // 如果客户端发来的内容为bye时,结束对话
                if("bye".equalsIgnoreCase(s1)){
                    System.out.println("客户端已下线!");
                    break;
                }
                // 实现服务器向客户端回发字符串的内容"I received!"
                ps.println("I received!");
                System.out.println("服务器发送数据成功!");
            }

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 4.关闭Socket并释放有关的资源
            if(null != ps){
                ps.close();
            }
            if(null != br){
                try {
                    br.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (null != s) {
                try {
                    s.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(null != ss){
                try {
                    ss.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
public class ClientStringTest {

    public static void main(String[] args) {
        Socket s = null;
        PrintStream ps = null;
        Scanner sc = null;
        BufferedReader br = null;

        try {
            // 1.创建Socket类型的对象并提供服务器的主机名和端口号
            s = new Socket("127.0.0.1", 8888);
            System.out.println("连接服务器成功!");

            // 2.使用输入输出流进行通信
            sc = new Scanner(System.in);
            ps = new PrintStream(s.getOutputStream());
            br = new BufferedReader(new InputStreamReader(s.getInputStream()));

            while(true) {
                //Thread.sleep(10000);
                // 实现客户端发送的内容由用户从键盘输入
                System.out.println("请输入要发送的数据内容:");
                String str1 = sc.next();
                // 实现客户端向服务器发送字符串内容"hello"
                //ps.println("hello");
                ps.println(str1);
                System.out.println("客户端发送数据内容成功!");
                // 当发送的数据内容为"bye"时,则聊天结束
                if ("bye".equalsIgnoreCase(str1)) {
                    System.out.println("聊天结束!");
                    break;
                }
                // 实现接收服务器发来的字符串内容并打印
                String str2 = br.readLine();
                System.out.println("服务器回发的消息是:" + str2);
            }

        } catch (IOException /*| InterruptedException*/ e) {
            e.printStackTrace();
        } finally {
            // 3.关闭Socket并释放有关的资源
            if (null != br) {
                try {
                    br.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (null != ps) {
                ps.close();
            }
            if (null != sc) {
                sc.close();
            }
            if (null != s) {
                try {
                    s.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

可以改造服务器,实现并发(要在IDEA中看到并发效果,可以将上面的客户端程序拷贝多份运行):

public class ServerStringTest {

    public static void main(String[] args) {
        ServerSocket ss = null;
        Socket s = null;

        try {
            // 1.创建ServerSocket类型的对象并提供端口号
            ss = new ServerSocket(8888);

            // 2.等待客户端的链接请求,调用accept方法
            while(true) {
                System.out.println("等待客户端的连接请求...");
                // 当没有客户端时,程序阻塞在accept方法这里
                s = ss.accept();
                System.out.println("客户端"+s.getInetAddress()+"连接成功!");
                // 每当有一个客户端连接成功,则需要启动一个新线程
                new Thread(new ServerThread(s)).start();
            }

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 4.关闭Socket并释放有关的资源
            if(null != ss){
                try {
                    ss.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
public class ServerThread implements Runnable {
    private Socket s;

    public ServerThread(Socket s) {
        this.s = s;
    }

    @Override
    public void run() {
        BufferedReader br = null;
        PrintStream ps = null;

        try {
            br = new BufferedReader(new InputStreamReader(s.getInputStream()));
            ps = new PrintStream(s.getOutputStream());
            // 3.使用输入输出流进行通信
            // 实现对客户端发来的字符串内容的接收并打印
            while (true) {
                // 当没有数据发来时,下面的方法会形成阻塞
                String s1 = br.readLine();
                InetAddress inetAddress = s.getInetAddress();
                System.out.println("客户端"+inetAddress+"发来的字符串内容时:" + s1);
                // 如果客户端发来的内容为bye时,结束对话
                if ("bye".equalsIgnoreCase(s1)) {
                    System.out.println("客户端已下线!");
                    break;
                }
                // 实现服务器向客户端回发字符串的内容"I received!"
                ps.println("I received!");
                System.out.println("服务器发送数据成功!");
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (null != ps) {
                ps.close();
            }
            if (null != br) {
                try {
                    br.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(null != s){
                try {
                    s.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

三、基于udp协议的编程模型(熟悉)

3.1 编程模型

  • 接收方:
    (1)创建DatagramSocket类型的对象并提供端口号;
    (2)创建DatagramPacket类型的对象并提供缓冲区;
    (3)通过Socket接收数据内容存放到Packet中,调用receive方法;
    (4)关闭Socket;
  • 发送方:
    (1)创建DatagramSocket类型的对象;
    (2)创建DatagramPacket类型的对象并提供接收方的通信地址;
    (3)通过Socket将Packet中的数据内容发送出去,调用send方法;
    (4)关闭Socket;

3.2 相关类和方法的解析

(1)DatagramSocket类

  • java.net.DatagramSocket类主要用于描述发送和接收数据报的套接字(邮局)。
    换句话说,该类就是包裹投递服务的发送或接收点。
  • 常用的方法如下:
方法声明功能介绍
DatagramSocket()使用无参的方式构造对象
DatagramSocket(int port)根据参数指定的端口号来构造对象
void receive(DatagramPacket p)用于接收数据报存放到参数指定的位置
void send(DatagramPacket p)用于将参数指定的数据报发送出去
void close()关闭Socket并释放相关资源

(2)DatagramPacket类

  • java.net.DatagramPacket类主要用于描述数据报,数据报用来实现无连接包裹投递服务。
  • 常用的方法如下:
方法声明功能介绍
DatagramPacket(byte[] buf, int length)根据参数指定的数组来构造对象,用于接收长度为length的数据报
DatagramPacket(byte[] buf, int length,InetAddress address, int port)根据参数指定数组来构造对象,将数据报发送到指定地址和端口
InetAddress getAddress()用于获取发送方或接收方的通信地址
int getPort()用于获取发送方或接收方的端口号
int getLength()用于获取发送数据或接收数据的长度

(3)InetAddress类

  • java.net.InetAddress类主要用于描述互联网通信地址信息。
  • 常用的方法如下:
方法声明功能介绍
static InetAddress getLocalHost()用于获取当前主机的通信地址
static InetAddress getByName(String host)根据参数指定的主机名获取通信地址
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;

public class ReceiveTest {

    public static void main(String[] args) {
        DatagramSocket ds = null;

        try {
            // 1.创建DatagramSocket类型的对象并提供端口号
            ds = new DatagramSocket(8888);

            // 2.创建DatagramPacket类型的对象并提供缓冲区
            byte[] bArr = new byte[20];
            DatagramPacket dp = new DatagramPacket(bArr, bArr.length);
            // 3.通过Socket接收数据内容存放到Packet里面,调用receive方法
            System.out.println("等待数据的到来...");
            ds.receive(dp);
            System.out.println("接收到的数据内容是:" + new String(bArr, 0, dp.getLength()) + "!");

            // 实现将字符串内容"I received!"回发过去
            byte[] bArr2 = "I received!".getBytes();
            DatagramPacket dp2 = new DatagramPacket(bArr2, bArr2.length, dp.getAddress(), dp.getPort());
            ds.send(dp2);
            System.out.println("回发数据成功!");

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 4.关闭Socket并释放有关的资源
            if (null != ds) {
                ds.close();
            }
        }
    }
}
public class SendTest {

    public static void main(String[] args) {
        DatagramSocket ds = null;

        try {
            // 1.创建DatagramSocket类型的对象
            ds = new DatagramSocket();
            // 2.创建DatagramPacket类型的对象并提供接收方的通信地址和端口号
            byte[] bArr = "hello".getBytes();
            DatagramPacket dp = new DatagramPacket(bArr, bArr.length, InetAddress.getLocalHost(), 8888);
            // 3.通过Socket发送Packet,调用send方法
            ds.send(dp);
            System.out.println("发送数据成功!");

            // 接收回发的数据内容
            byte[] bArr2 = new byte[20];
            DatagramPacket dp2 = new DatagramPacket(bArr2, bArr2.length);
            ds.receive(dp2);
            System.out.println("接收到的回发消息是:" + new String(bArr2, 0, dp2.getLength()));

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 4.关闭Socket并释放有关的资源
            if (null != ds) {
                ds.close();
            }
        }
    }
}

四、URL类(熟悉)

4.1 基本概念

  • java.net.URL(Uniform Resource Identifier)类主要用于表示统一的资源定位器,也就是指向万维网上“资源”的指针。这个资源可以是简单的文件或目录,也可以是对复杂对象的引用,例如对数据库或搜索引擎的查询等。
  • 通过URL可以访问万维网上的网络资源,最常见的就是www和ftp站点,浏览器通过解析给定的URL可以在网络上查找相应的资源。
  • URL的基本结构如下:
    <传输协议>://<主机名>:<端口号>/<资源地址>

4.2 常用的方法

方法声明功能介绍
URL(String spec)根据参数指定的字符串信息构造对象
String getProtocol()获取协议名称
String getHost()获取主机名称
int getPort()获取端口号
String getPath()获取路径信息
String getFile()获取文件名
URLConnection openConnection()获取URLConnection类的实例

4.3 URLConnection类
(1)基本概念

  • java.net.URLConnection类是个抽象类,该类表示应用程序和URL之间的通信链接的所有类的超类,主要实现类有支持HTTP特有功能的HttpURLConnection类。

(2)HttpURLConnection类的常用方法

方法声明功能介绍
InputStream getInputStream()获取输入流
void disconnect()断开连接
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;

public class URLTest {

    public static void main(String[] args) {

        try {
            // 1.使用参数指定的字符串来构造对象
            URL url = new URL("https://www.lagou.com/");
            // 2.获取相关信息并打印出来
            System.out.println("获取到的协议名称是:" + url.getProtocol());
            System.out.println("获取到的主机名称是:" + url.getHost());
            System.out.println("获取到的端口号是:" + url.getPort());

            // 3.建立连接并读取相关信息打印出来
            HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
            InputStream inputStream = urlConnection.getInputStream();
            BufferedReader br = new BufferedReader(new InputStreamReader(inputStream));
            String str = null;
            while ((str = br.readLine()) != null) {
                System.out.println(str);
            }
            br.close();
            // 断开连接
            urlConnection.disconnect();

        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

标签:Java,Socket,编程,System,println,new,null,十九,客户端
来源: https://blog.csdn.net/GUO_NULL192/article/details/114189332

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有