Java 网络编程

网络编程

狂神JAVA网络编程视频笔记

1.计算机网络概念

计算机网络是指将地理位置不同的具有独立功能的多台计算机及其外部设备,通过通信线路连接(有线性、无线)起来,在网络操作系统,网络管理软件及网络通信协议的管理和协调下,实现资源共享和信息传递的计算机系统。

网络编程的目的
  传播交流信息
  数据交换、通信。

想要达到这个效果,需要什么
  1.如何准确的定位网络上的一台主机 192.168.1.100: 端口,定位到这个计算机上的某个资源。
  2.找到了这个主机,如何传输数据呢?

JavaWeb : 网页编程 B/S架构
网络编程: TCP/IP C/S架构

网络通信要素
  如何实现网络的通信?
  通信双方的地址:
    IP、端口号。
    192.168.1.100:8080

TCP/IP参考模型
在这里插入图片描述

小结:
  1.网络编程中两个主要问题
    如何准确定位到网络上的一台或多台主机
    找到主机之后如何进行通信

2.网络编程中的要素
    IP 和 端口号
    网络通信协议

3.Java 万物皆对象


2.IP

ip地址:InetAddress

1.唯一定位一台网络上计算机
  2.127.0.0.1: 本机localhost
  3.ip地址的分类
    IPV4/IPv6
        IPV4 127.0.0.1 4个字节组成,0-255 42亿个 30亿都在北美,亚洲4亿。2011年就用尽
        IPV6 ;128位。8个无符号整数!

公网-私网

/**
 * InetAddress的应用
 */
public class Demo01 {
    public static void main(String[] args) throws UnknownHostException {
        InetAddress localhost=InetAddress.getByName("localhost");//"127.0.0.1"或者getLocalHost方法也可以获取
        System.out.println(localhost);

        //查询网络IP地址
        InetAddress inetAddress1=InetAddress.getByName("www.baidu.com");
        System.out.println(inetAddress1);

        System.out.println(localhost.getAddress());//byte类型。基本不会使用
        System.out.println(inetAddress1.getCanonicalHostName());//获取规范的主机ip地址
        System.out.println(inetAddress1.getHostAddress());//IP
        System.out.println(inetAddress1.getHostName());//获取域名
    }
}

3.端口

端口表示计算机上的一个程序的进程。

1.一栋楼表示一个ip ,这栋楼里面的 门牌号 就是端口号。
  2.不同的进程有不同的端口号!用来区分软件的。
  3.端口被规定为:0-65535
  4.TCP ,UDP: 每个都有 0-65535 * 2 ,单个协议下,端口号不能冲突。
  5.端口分类
    共有端口0-1023
      HTTP : 80
      HTTPS :443
      FTP : 21
      Telet : 23
    程序注册端口:1024-49151,分配给用户或者程序
        Tomcat:8080
        Mysql:3306
        Oracle:1521
    动态、私有:49152-65535

netstat -ano #查看所有端口
netstat -ano  | findstr "5900" #查看指定的端口
tasklist | findstr "8696" #查看指定端口的进程
Ctrl + Shift + ESC
/**
 * InetSocketAddress的使用
 */
public class Demo02 {
    public static void main(String[] args) {
        InetSocketAddress inetSocketAddress1=new InetSocketAddress("127.0.0.1",8080);
        InetSocketAddress inetSocketAddress2=new InetSocketAddress("localhost",9999);
        System.out.println(inetSocketAddress1);
        System.out.println(inetSocketAddress2);
        System.out.println("---------------");
        System.out.println(inetSocketAddress1.getHostName());
        System.out.println(inetSocketAddress1.getPort());
    }
}

4.通信协议

协议:约定,就好比我们现在说的是普通话。

网络通信协议:
    1.速率
    2.传输码率
    3.代码结构
    4.传输控制

问题:非常的复杂

TCP/IP协议簇:实际上是一组协议
  重要:
    TCP:用户传输协议
    UDP:用户数据报协议

出名的协议:
    TCP
    IP

TCP和UDP 对比:

TCP:打电话
    连接: 稳定
    三次握手
    四次挥手

客户端,服务端
    传输完成,释放连接、效率低


三次握手与四次挥手参考

1.TCP“三次握手”的详解

所谓的三次握手即TCP连接的建立。这个连接必须是一方主动打开,另一方被动打开的。以下为客户端主动发起连接的图解:

img

握手之前主动打开连接的客户端结束CLOSED阶段,被动打开的服务器端也结束CLOSED阶段,并进入LISTEN阶段。随后开始“三次握手”:

(1)首先客户端向服务器端发送一段TCP报文,其中:

标记位为SYN,表示“请求建立新连接”;序号为Seq=X(X一般为1);随后客户端进入SYN-SENT阶段。(2)服务器端接收到来自客户端的TCP报文之后,结束LISTEN阶段。并返回一段TCP报文,其中:

标志位为SYN和ACK,表示“确认客户端的报文Seq序号有效,服务器能正常接收客户端发送的数据,并同意创建新连接”(即告诉客户端,服务器收到了你的数据);序号为Seq=y;确认号为Ack=x+1,表示收到客户端的序号Seq并将其值加1作为自己确认号Ack的值;随后服务器端进入SYN-RCVD阶段。(3)客户端接收到来自服务器端的确认收到数据的TCP报文之后,明确了从客户端到服务器的数据传输是正常的,结束SYN-SENT阶段。并返回最后一段TCP报文。其中:

标志位为ACK,表示“确认收到服务器端同意连接的信号”(即告诉服务器,我知道你收到我发的数据了);序号为Seq=x+1,表示收到服务器端的确认号Ack,并将其值作为自己的序号值;确认号为Ack=y+1,表示收到服务器端序号Seq,并将其值加1作为自己的确认号Ack的值;随后客户端进入ESTABLISHED阶段。服务器收到来自客户端的“确认收到服务器数据”的TCP报文之后,明确了从服务器到客户端的数据传输是正常的。结束SYN-SENT阶段,进入ESTABLISHED阶段。

在客户端与服务器端传输的TCP报文中,双方的确认号Ack和序号Seq的值,都是在彼此Ack和Seq值的基础上进行计算的,这样做保证了TCP报文传输的连贯性。一旦出现某一方发出的TCP报文丢失,便无法继续"握手",以此确保了"三次握手"的顺利完成。

此后客户端和服务器端进行正常的数据传输。这就是“三次握手”的过程。

2.“三次握手”的动态过程

img

3.“三次握手”的通俗理解

img

举个栗子:把客户端比作男孩,服务器比作女孩。用他们的交往来说明“三次握手”过程:

(1)男孩喜欢女孩,于是写了一封信告诉女孩:我爱你,请和我交往吧!;写完信之后,男孩焦急地等待,因为不知道信能否顺利传达给女孩。

(2)女孩收到男孩的情书后,心花怒放,原来我们是两情相悦呀!于是给男孩写了一封回信:我收到你的情书了,也明白了你的心意,其实,我也喜欢你!我愿意和你交往!;

写完信之后,女孩也焦急地等待,因为不知道回信能否能顺利传达给男孩。

(3)男孩收到回信之后很开心,因为发出的情书女孩收到了,并且从回信中知道了女孩喜欢自己,并且愿意和自己交往。然后男孩又写了一封信告诉女孩:你的心意和信我都收到了,谢谢你,还有我爱你!

女孩收到男孩的回信之后,也很开心,因为发出的情书男孩收到了。由此男孩女孩双方都知道了彼此的心意,之后就快乐地交流起来了~~

这就是通俗版的“三次握手”,期间一共往来了三封信也就是“三次握手”,以此确认两个方向上的数据传输通道是否正常。

2.TCP“四次挥手”的详解

所谓的四次挥手即TCP连接的释放(解除)。连接的释放必须是一方主动释放,另一方被动释放。以下为客户端主动发起释放连接的图解:

img

挥手之前主动释放连接的客户端结束ESTABLISHED阶段。随后开始“四次挥手”:

(1)首先客户端想要释放连接,向服务器端发送一段TCP报文,其中:

标记位为FIN,表示“请求释放连接“;序号为Seq=U;随后客户端进入FIN-WAIT-1阶段,即半关闭阶段。并且停止在客户端到服务器端方向上发送数据,但是客户端仍然能接收从服务器端传输过来的数据。注意:这里不发送的是正常连接时传输的数据(非确认报文),而不是一切数据,所以客户端仍然能发送ACK确认报文。

(2)服务器端接收到从客户端发出的TCP报文之后,确认了客户端想要释放连接,随后服务器端结束ESTABLISHED阶段,进入CLOSE-WAIT阶段(半关闭状态)并返回一段TCP报文,其中:

标记位为ACK,表示“接收到客户端发送的释放连接的请求”;序号为Seq=V;确认号为Ack=U+1,表示是在收到客户端报文的基础上,将其序号Seq值加1作为本段报文确认号Ack的值;随后服务器端开始准备释放服务器端到客户端方向上的连接。客户端收到从服务器端发出的TCP报文之后,确认了服务器收到了客户端发出的释放连接请求,随后客户端结束FIN-WAIT-1阶段,进入FIN-WAIT-2阶段

前"两次挥手"既让服务器端知道了客户端想要释放连接,也让客户端知道了服务器端了解了自己想要释放连接的请求。于是,可以确认关闭客户端到服务器端方向上的连接了

(3)服务器端自从发出ACK确认报文之后,经过CLOSED-WAIT阶段,做好了释放服务器端到客户端方向上的连接准备,再次向客户端发出一段TCP报文,其中:

标记位为FIN,ACK,表示“已经准备好释放连接了”。注意:这里的ACK并不是确认收到服务器端报文的确认报文。序号为Seq=W;确认号为Ack=U+1;表示是在收到客户端报文的基础上,将其序号Seq值加1作为本段报文确认号Ack的值。随后服务器端结束CLOSE-WAIT阶段,进入LAST-ACK阶段。并且停止在服务器端到客户端的方向上发送数据,但是服务器端仍然能够接收从客户端传输过来的数据。

(4)客户端收到从服务器端发出的TCP报文,确认了服务器端已做好释放连接的准备,结束FIN-WAIT-2阶段,进入TIME-WAIT阶段,并向服务器端发送一段报文,其中:

标记位为ACK,表示“接收到服务器准备好释放连接的信号”。序号为Seq=U+1;表示是在收到了服务器端报文的基础上,将其确认号Ack值作为本段报文序号的值。确认号为Ack=W+1;表示是在收到了服务器端报文的基础上,将其序号Seq值作为本段报文确认号的值。随后客户端开始在TIME-WAIT阶段等待2MSL

3、“四次挥手”的通俗理解

img

举个栗子:把客户端比作男孩,服务器比作女孩。通过他们的分手来说明“四次挥手”过程。

"第一次挥手":日久见人心,男孩发现女孩变成了自己讨厌的样子,忍无可忍,于是决定分手,随即写了一封信告诉女孩。“第二次挥手”:女孩收到信之后,知道了男孩要和自己分手,怒火中烧,心中暗骂:你算什么东西,当初你可不是这个样子的!于是立马给男孩写了一封回信:分手就分手,给我点时间,我要把你的东西整理好,全部还给你!男孩收到女孩的第一封信之后,明白了女孩知道自己要和她分手。随后等待女孩把自己的东西收拾好。“第三次挥手”:过了几天,女孩把男孩送的东西都整理好了,于是再次写信给男孩:你的东西我整理好了,快把它们拿走,从此你我恩断义绝!“第四次挥手”:男孩收到女孩第二封信之后,知道了女孩收拾好东西了,可以正式分手了,于是再次写信告诉女孩:我知道了,这就去拿回来!这里双方都有各自的坚持。女孩自发出第二封信开始,限定一天内收不到男孩回信,就会再发一封信催促男孩来取东西!男孩自发出第二封信开始,限定两天内没有再次收到女孩的信就认为,女孩收到了自己的第二封信;若两天内再次收到女孩的来信,就认为自己的第二封信女孩没收到,需要再写一封信,再等两天…..

倘若双方信都能正常收到,最少只用四封信就能彻底分手!这就是“四次挥手”。

3.常见问题

1.为什么“握手”是三次,“挥手”却要四次?

​ TCP建立连接时之所以只需要"三次握手",是因为在第二次"握手"过程中,服务器端发送给客户端的TCP报文是以SYN与ACK作为标志位的。SYN是请求连接标志,表示服务器端同意建立连接;ACK是确认报文,表示告诉客户端,服务器端收到了它的请求报文。

​ 即SYN建立连接报文与ACK确认接收报文是在同一次"握手"当中传输的,所以"三次握手"不多也不少,正好让双方明确彼此信息互通。

​ TCP释放连接时之所以需要“四次挥手”,是因为FIN释放连接报文与ACK确认接收报文是分别由第二次和第三次"握手"传输的。为何建立连接时一起传输,释放连接时却要分开传输?

​ 建立连接时,被动方服务器端结束CLOSED阶段进入“握手”阶段并不需要任何准备,可以直接返回SYN和ACK报文,开始建立连接。释放连接时,被动方服务器,突然收到主动方客户端释放连接的请求时并不能立即释放连接,因为还有必要的数据需要处理,所以服务器先返回ACK确认收到报文,经过CLOSE-WAIT阶段准备好释放连接之后,才能返回FIN释放连接报文。

​ 所以是“三次握手”,“四次挥手”。

2.为什么客户端在TIME-WAIT阶段要等2MSL?

为的是确认服务器端是否收到客户端发出的ACK确认报文

当客户端发出最后的ACK确认报文时,并不能确定服务器端能够收到该段报文。所以客户端在发送完ACK确认报文之后,会设置一个时长为2MSL的计时器。MSL指的是Maximum Segment Lifetime:一段TCP报文在传输过程中的最大生命周期。2MSL即是服务器端发出为FIN报文和客户端发出的ACK确认报文所能保持有效的最大时长。

服务器端在1MSL内没有收到客户端发出的ACK确认报文,就会再次向客户端发出FIN报文;

如果客户端在2MSL内,再次收到了来自服务器端的FIN报文,说明服务器端由于各种原因没有接收到客户端发出的ACK确认报文。客户端再次向服务器端发出ACK确认报文,计时器重置,重新开始2MSL的计时;否则客户端在2MSL内没有再次收到来自服务器端的FIN报文,说明服务器端正常接收了ACK确认报文,客户端可以进入CLOSED阶段,完成“四次挥手”。

所以,客户端要经历时长为2SML的TIME-WAIT阶段;这也是为什么客户端比服务器端晚进入CLOSED阶段的原因

3.为什么要进行第三次握手?

为了防止服务器端开启一些无用的连接增加服务器开销以及防止已失效的连接请求报文段突然又传送到了服务端,因而产生错误。

由于网络传输是有延时的(要通过网络光纤和各种中间代理服务器),在传输的过程中,比如客户端发起了SYN=1创建连接的请求(第一次握手)。

如果服务器端就直接创建了这个连接并返回包含SYN、ACK和Seq等内容的数据包给客户端,这个数据包因为网络传输的原因丢失了,丢失之后客户端就一直没有接收到服务器返回的数据包。

客户端可能设置了一个超时时间,时间到了就关闭了连接创建的请求。再重新发出创建连接的请求,而服务器端是不知道的,如果没有第三次握手告诉服务器端客户端收的到服务器端传输的数据的话,

服务器端是不知道客户端有没有接收到服务器端返回的信息的。

这个过程可理解为:

img

这样没有给服务器端一个创建还是关闭连接端口的请求,服务器端的端口就一直开着,等到客户端因超时重新发出请求时,服务器就会重新开启一个端口连接。那么服务器端上没有接收到请求数据的上一个端口就一直开着,长此以往,这样的端口多了,就会造成服务器端开销的严重浪费。

还有一种情况是已经失效的客户端发出的请求信息,由于某种原因传输到了服务器端,服务器端以为是客户端发出的有效请求,接收后产生错误。

所以我们需要“第三次握手”来确认这个过程,让客户端和服务器端能够及时地察觉到因为网络等一些问题导致的连接创建失败,这样服务器端的端口就可以关闭了不用一直等待。

也可以这样理解:“第三次握手”是客户端向服务器端发送数据,这个数据就是要告诉服务器,客户端有没有收到服务器“第二次握手”时传过去的数据。若发送的这个数据是“收到了”的信息,接收后服务器就正常建立TCP连接,否则建立TCP连接失败,服务器关闭连接端口。由此减少服务器开销和接收到失效请求发生的错误。

UDP:发短信
  1.不连接,不稳定
  2.客户端、服务端:没有明确的界限
  3.不管有没有准备好,都可以发给你…
  4.导弹
  5.DDOS:洪水攻击!(饱和攻击)


5.TCP网络编程示例

1.TCP实现聊天

/**
 * 客户端
 */
public class TcpClient {
    public static void main(String[] args) throws IOException {
        Socket socket = new Socket("127.0.0.1", 9999);

        OutputStream outputStream = socket.getOutputStream();
        outputStream.write("已建立连接".getBytes());

        outputStream.close();
        socket.close();
    }
}
/**
 * 服务器
 */
public class TcpServer {
    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = new ServerSocket(9999);
        InputStream inputStream = serverSocket.accept().getInputStream();

//        //缺点:可能出现乱码
//        byte[] buffer=new byte[1024];
//        int len;
//        while ((len=inputStream.read(buffer))!=-1) {
//            String s = new String(buffer, 0, len);
//            System.out.println(s);
//        }

        //管道流:字节数组输出流在内存中创建一个字节数组缓冲区,所有发送到输出流的数据保存在该字节数组缓冲区中。
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        byte[] bytes = new byte[1024];
        int len=-1;
        while ((len=inputStream.read(bytes))!=-1)
        {
            baos.write(bytes,0,len);
        }
        System.out.println(baos.toString());

        //先建立的后关闭
        baos.close();
        inputStream.close();
        serverSocket.close();
    }
}

2.TCP实现文件上传

/**
 * 客户端实现文件上传
 */
public class TcpClient {
    public static void main(String[] args) throws Exception{
        Socket socket = new Socket("127.0.0.1", 9999);
        OutputStream outputStream = socket.getOutputStream();
        FileInputStream fileInputStream = new FileInputStream(new File("D:\\kuangshen\\src\\com\\kuang\\net\\demo03\\1.jpg"));
        byte[] buff = new byte[1024];
        int len;
        while ((len = fileInputStream.read(buff)) != -1) {
            outputStream.write(buff, 0, len);
        }
        fileInputStream.close();
        outputStream.close();
        socket.close();
    }
}
/**
 * 服务器
 */
public class TcpServer {
    public static void main(String[] args) throws Exception{
        ServerSocket serverSocket = new ServerSocket(9999);
        Socket accept = serverSocket.accept();
        InputStream inputStream = accept.getInputStream();
        //4.文件输出
        FileOutputStream fos = new FileOutputStream(new File("receive.jpg"));//接收文件就要用文件的管道流
        byte[] buff = new byte[1024];
        int len;
        while ((len = inputStream.read(buff)) != -1){
            fos.write(buff,0,len);
        }

        fos.close();
        inputStream.close();
        accept.close();
        serverSocket.close();
    }
}

6.UDP网络编程实例

1.UDP消息发送

/**
 * 客户端
 * 发送消息
 */
public class UdpClient {
    public static void main(String[] args) throws Exception {
        DatagramSocket datagramSocket = new DatagramSocket();
        String s="你好,服务器";
        InetAddress localhost = InetAddress.getByName("localhost");
        DatagramPacket datagramPacket = new DatagramPacket(s.getBytes(), 0, s.getBytes().length, localhost, 9999);
        datagramSocket.send(datagramPacket);
        datagramSocket.close();
    }
}
/** * 服务器 * 接收消息 */public class UdpServer {    public static void main(String[] args) throws Exception {        DatagramSocket datagramSocket = new DatagramSocket(9999);        byte[] bytes = new byte[1024];        DatagramPacket datagramPacket = new DatagramPacket(bytes, 0, bytes.length);        datagramSocket.receive(datagramPacket);        System.out.println(new String(datagramPacket.getData(),0,datagramPacket.getData().length));        datagramSocket.close();    }}

2.UDP聊天实现

/** * 发送端 */public class UdpSenderDemo01 {    public static void main(String[] args) throws Exception {        //获取连接        DatagramSocket socket = new DatagramSocket(8080);        while (true) {            //准备数据            BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));            String data = reader.readLine();            byte[] datas = data.getBytes();            DatagramPacket packet = new DatagramPacket                    (datas, 0,datas.length, new InetSocketAddress("localhost", 6666));            //发送数据            socket.send(packet);            if (data.equals("bye")) {                break;            }        }        socket.close();    }}
/** * 接收端 */public class UdpReceiveDemo01 {    public static void main(String[] args) throws Exception {        DatagramSocket socket = new DatagramSocket(6666);        while (true) {            //准备接收包裹            byte[] container = new byte[1024];            DatagramPacket packet = new DatagramPacket(container, 0, container.length);            socket.receive(packet);//阻塞式接收包裹            byte[] data = packet.getData();            String receiveData = new String(data, 0, data.length);            System.out.println(receiveData);            //断开连接 bye            if (receiveData.equals("bye")){                break;            }        }        socket.close();    }}

3.UDP多线程在线咨询

/*** 发送端线程*/public class Sent implements Runnable{    DatagramSocket datagramSocket;    DatagramPacket datagramPacket;    private int fromPort;    private String toIP;    private int Toport;    BufferedReader bufferedReader;    String buf;public Sent(int fromPort, String toIP, int Toport) {    this.fromPort = fromPort;    this.toIP = toIP;    this.Toport = Toport;    try {        this.datagramSocket = new DatagramSocket(fromPort);    } catch (Exception e) {        e.printStackTrace();    }}@Overridepublic void run() {    while(true){        try {            bufferedReader=new BufferedReader(new InputStreamReader(System.in));            buf=bufferedReader.readLine();            byte[] buff=buf.getBytes();            datagramPacket= new DatagramPacket(buff,0,buff.length,new InetSocketAddress(this.toIP,this.Toport));            datagramSocket.send(datagramPacket);            if (buf.equals("bye")){                break;            }        } catch (IOException e) {            e.printStackTrace();        }    }    datagramSocket.close();}}
/*** 接收端线程*/public class Receive implements Runnable{    DatagramSocket datagramSocket;DatagramPacket datagramPacket;private String FromID;    private int Port;public Receive(String fromID, int port) {    this.FromID = fromID;    this.Port = port;    try {        datagramSocket=new DatagramSocket(port);    } catch (SocketException e) {        e.printStackTrace();    }}@Overridepublic void run() {    while (true) {        try {            byte[] buff = new byte[1024];           datagramPacket = new DatagramPacket(buff, 0, buff.length);            datagramSocket.receive(datagramPacket);            byte[] data=datagramPacket.getData();            String msg=new String(data,0,data.length);            System.out.println(FromID + ":" +msg);            if (datagramPacket.equals("bye")) {                break;            }        } catch (IOException e) {            e.printStackTrace();        }    }    datagramSocket.close();}}

学生端:

public class Student {public static void main(String[] args) {    new Thread(new Sent(7754,"localhost",9999)).start();    new Thread(new Receive("老师:",7787)).start();}}

老师端:

public class Teacher {public static void main(String[] args) {    new Thread(new Sent(7755,"localhost",7787)).start();    new Thread(new Receive("学生:",9999)).start();}}

7.URL

public class Url {public static void main(String[] args) throws IOException {    URL url=new URL("https://m801.music.126.net/20201123231609/7a60a829f966abbdfc75ee0756ed0ff9/jdyyaac/obj/w5rDlsOJwrLDjj7CmsOj/4916388353/e2fe/8be5/e1e6/c7614d21f4afc33120723d86e09d330d.m4a");    HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();    InputStream in=urlConnection.getInputStream();    FileOutputStream Fout=new FileOutputStream("d.m4a");    byte[] buff=new byte[1024];    int len;    while ((len=in.read(buff))!=-1){        Fout.write(buff,0,len);    }    Fout.close();    in.close();    urlConnection.disconnect();}}
posted @ 2021-10-22 23:25  慕贞贞贞  阅读(66)  评论(0)    收藏  举报