学静思语
Published on 2025-02-15 / 0 Visits
0
0

网络编程-Socket

网络编程

一、网络编程的了解

1.要想实现网络通信,需要解决的三个问题:

  • 问题1:如何准确地定位网络上一台或多台主机
  • 问题2:如何定位主机上的特定的应用
  • 问题3:找到主机后,如何可靠、高效地进行数据传输

2.实现网络传输的三个要素:(对应解决三个问题)

  • 使用IP地址(准确地定位网络上一台或多台主机)
  • 使用端口号(定位主机上的特定的应用)
  • 规范网络通信协议(可靠、高效地进行数据传输)

3.通信要素1:IP地址

3.1 作用
  IP地址用来给网络中的一台计算机设备做唯一的编号

3.2 IP地址分类

    IP地址分类方式1
       IPv4 (占用4个字节)
       IPv6 (占用16个字节)

    IP地址分类方式2
        公网地址( 万维网使用)和 私有地址( 局域网使用。以192.168开头)

3.3 本地回路地址:127.0.0.1

3.4 域名:便捷的记录ip地址
    www.baidu.com  www.atguigu.com  www.bilibili.com
    www.jd.com  www.mi.com  www.vip.com

4. 通信要素2:端口号

  • 可以唯一标识主机中的进程(应用程序)
  • 不同的进程分配不同的端口号
  • 范围:0~65535
  1. InetAddress的使用

  • 5.1 作用

  • InetAddress类的一个实例就代表一个具体的ip地址。

  • 5.2 实例化方式

  • InetAddress getByName(String host):获取指定ip对应的InetAddress的实例

  • InetAddress getLocalHost():获取本地ip对应的InetAddress的实例

  • 5.3 常用方法

  • getHostName()

  • getHostAddress()

6.通信要素3:通信协议

  • 6.1 网络通信协议的目的

  • 为了实现可靠而高效的数据传输。

  • 6.2 网络参考模型

  • OSI参考模型:将网络分为7层,过于理想化,没有实施起来。

  • TCP/IP参考模型:将网络分为4层:应用层、传输层、网络层、物理+数据链路层。事实上使用的标准。

二、TCP\UDP

1. TCP与UDP网络编程对比(熟悉)

1.1TCP

package com.leon.net_tcpandudp;

import org.junit.Test;

import java.io.*;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * ClassName:ConnectTest3
 * Package:com.leon.net_tcpandudp
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2023/10/6 10:42
 * @Version: 1.0
 */
public class ConnectTest3 {
    @Test
    public void client(){
        Socket socket = null;
        OutputStream os = null;
        FileInputStream fis = null;
        InputStream is = null ;
        ByteArrayOutputStream baos = null ;
        try {
            //创建请求连接
            int port = 8989;
            InetAddress byName = InetAddress.getByName("127.0.0.1");

            socket = new Socket(byName ,port);

            //实现数据输出

            os = socket.getOutputStream();

            //获取本地数据
            File file = new File("01.jpg");
            fis = new FileInputStream(file);

            //将数据写出

            byte[] buffer = new byte[1024];

            int len ;

            while ((len = fis.read(buffer)) != -1){
                os.write(buffer,0,len);
            }
            System.out.println("数据传输完毕");
            //告诉服务端结束输出
            socket.shutdownOutput();

            //读取服务端的数据
             is = socket.getInputStream();

             baos = new ByteArrayOutputStream();

            byte[] buffer1 = new byte[5];

            int len1 ;

            while ((len1 = is.read(buffer1)) != -1){

                baos.write(buffer1,0,len1);

            }

            System.out.println(baos.toString());



        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            //关闭资源
            try {
                if(socket != null)
                    socket.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
            try {
                if(os != null)
                    os.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
            try {
                if(fis != null)
                    fis.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
            try {
                if(is != null)
                is.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
            try {
                if(baos != null)
                baos.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }

        }

    }

    @Test
    public void server(){
        ServerSocket serverSocket = null;
        Socket accept = null;
        InputStream inputStream = null;
        FileOutputStream fos = null;
        try {
            //创建服务端
            int port = 8989;
            serverSocket = new ServerSocket(port);
            //获取客户端请求
            accept = serverSocket.accept();
            //读取数据
            inputStream = accept.getInputStream();

            //读取的数据写入本地

            File file = new File("01_copy.jpg");

            fos = new FileOutputStream(file);

            byte[] buffer = new byte[1024];

            int len ;

            while ((len = inputStream.read(buffer)) != -1){
                fos.write(buffer,0,len);
            }
            System.out.println("数据接收完毕");
            //向客户端输出信息
            OutputStream os = accept.getOutputStream();
            os.write("图片我已经收到".getBytes());

        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            //关闭资源

            try {
                if(serverSocket != null)
                    serverSocket.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
            try {
                if(accept != null)
                    accept.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
            try {
                if(inputStream != null)
                    inputStream.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
            try {
                if(fos != null)
                    fos.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }

    }

}

1.2UDP

package com.leon.net_tcpandudp;

import org.junit.Test;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;

/**
 * ClassName:UDPTest
 * Package:com.leon.net_tcpandudp
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2023/10/6 20:47
 * @Version: 1.0
 */
public class UDPTest {


    @Test
    public void sender(){
        //创建发送端
        DatagramSocket ds = null;
        try {
            ds = new DatagramSocket();
            //创建响应端的IP地址
            InetAddress inetAddress = InetAddress.getByName("127.0.0.1");

            int port = 8989;
            //创建存储数据的数组
            byte[] bytes = "我是发送端".getBytes();
            //创建数据包
            DatagramPacket packet = new DatagramPacket(bytes,0,bytes.length,inetAddress,port);

            //发送数据包
            ds.send(packet);
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {

            //关闭资源
            if(ds != null)
            ds.close();
        }


    }

    @Test
    public void receiver(){
        DatagramSocket ds = null;
        try {
            //创建接收端对象
            //端口
            int port = 8989;
            ds = new DatagramSocket(port);
            //接收数据
            byte[] buffer = new byte[1024*64];

            DatagramPacket dp = new DatagramPacket(buffer,0,buffer.length);

            //接收数据包
            ds.receive(dp);

            //将数据输出
            System.out.println(new String(dp.getData(),0,dp.getLength()));
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            //关闭资源
            if(ds != null)
            ds.close();
        }

    }
}

2.TCP三次握手 与 TCP四次挥手(熟悉)

2.1 三次握手

TCP 协议中,在发送数据的准备阶段,客户端与服务器之间的三次交互,以保

证连接的可靠。

  • 第一次握手,客户端向服务器端发起 TCP 连接的请求

  • 第二次握手,服务器端发送针对客户端 TCP 连接请求的确认

  • 第三次握手,客户端发送确认的确认

    image-20231008100104768

1、客户端会随机一个初始序列号 seq=x,设置 SYN=1 ,表示这是SYN 握手报文。然后就可以把这个 SYN 报文发送给服务端了,表示向服务端发起连接,之后客户端处于同步已发送状态。

2、服务端收到客户端的 SYN 报文后,也随机一个初始序列号(seq=y),设置 ack=x+1,表示收到了客户端的 x 之前的数据,希望客户端下次发送的数据从 x+1 开始。 设置 SYN=1 和 ACK=1。表示这是一个 SYN 握手和 ACK 确认应答报文。最后把该报文发给客户端,该报文也不包含应用层数据,之后服务端处于同步已接收状态。

3、客户端收到服务端报文后,还要向服务端回应最后一个应答报文,将 ACK 置为 1 ,表示这是一个应答报文 ack=y+1 ,表示收到了服务器的 y 之前的数据,希望服务器下次发送的数据从 y+1 开始。 最后把报文发送给服务端,这次报文可以携带数据,之后客户端处于 连接已建立 状态。服务器收到客户端的应答报文后,也进入连接已建立状态。

完成三次握手,连接建立后,客户端和服务器就可以开始进行数据传输了。由

于这种面向连接的特性,TCP 协议可以保证传输数据的安全,所以应用十分广

泛,例如下载文件、浏览网页等。

2.2 四次挥手

TCP 协议中,在发送数据结束后,释放连接时需要经过四次挥手。

  • 第一次挥手:客户端向服务器端提出结束连接,让服务器做最后的准备工作。此时,客户端处于半关闭状态,即表示不再向服务器发送数据了,但是还可以接受数据。

  • 第二次挥手:服务器接收到客户端释放连接的请求后,会将最后的数据发给客户端。并告知上层的应用进程不再接收数据。

  • 第三次挥手:服务器发送完数据后,会给客户端发送一个释放连接的报文。那么客户端接收后就知道可以正式释放连接了。

  • 第四次挥手:客户端接收到服务器最后的释放连接报文后,要回复一个彻底断开的报文。这样服务器收到后才会彻底释放连接。这里客户端,发送完最后的报文后,会等待 2MSL,因为有可能服务器没有收到最后的报文,那么服务器迟迟没收到,就会再次给客户端发送释放连接的报文,此时客户端在等待时间范围内接收到,会重新发送最后的报文,并重新计时。如果等待 2MSL 后,没有收到,那么彻底断开。

    image-20231008100140699

1、客户端打算断开连接,向服务器发送 FIN 报文(FIN 标记位被设置为1,1 表示为 FIN,0 表示不是),FIN 报文中会指定一个序列号,之后客户端进入 FINWAIT1 状态。也就是客户端发出连接释放报文段(FIN报文),指定序列号 seq = u,主动关闭 TCP 连接,等待服务器的确认。

2、服务器收到连接释放报文段(FIN 报文)后,就向客户端发送 ACK 应答报文,以客户端的 FIN 报文的序列号 seq+1 作为 ACK 应答报文段的确认序列号 ack = seq+1 = u + 1。接着服务器进入 CLOSEWAIT(等待关闭)状态,此时的 TCP 处于半关闭状态(下面会说什么是半关闭状态),客户端到服务器的连接释放。客户端收到来自服务器的 ACK 应答报文段后,进入 FINWAIT_2 状态。

3、服务器也打算断开连接,向客户端发送连接释放(FIN)报文段,之后服务器进入 LASK_ACK(最后确认)状态,等待客户端的确认。服务器的连接释放(FIN)报文段的 FIN=1,ACK=1,序列号 seq=m,确认序列号

ack=u+1。

4、客户端收到来自服务器的连接释放(FIN)报文段后,会向服务器发送一个 ACK 应答报文段,以连接释放(FIN)报文段的确认序号 ack 作为ACK 应答报文段的序列号 seq,以连接释放(FIN)报文段的序列号seq+1 作为确认序号 ack。之后客户端进入 TIMEWAIT(时间等待)状态,服务器收到 ACK 应答报文段后,服务器就进入 CLOSE(关闭)状态,到此服务器的连接已经完成关闭。客户端处于 TIMEWAIT 状态时,此时的 TCP 还未释放掉,需要等待 2MSL 后,客户端才进入 CLOSE 状态。


Comment