Skip to content
小鹏的Notes
Main Navigation 首页 笔记汇总
📱 3D翻盖手机
🎬 Siena Film
✨ 设计动效
🎲 疯狂 3D 游戏库
Github仓库 留言板 说说
黑马八股
小林Coding
导航网站
电脑截屏工具Snipaste
PDF工具
编辑图片
ip解析
电脑屏幕检测
文件格式转换

深浅模式

Sidebar Navigation

自我介绍

👨「代码炼金术士」

🌍「诗的浪漫极客」

Java基础学习

Java基础知识

Java集合

IO流

多线程

File文件

异常

网络编程

注解

Stream流

log日志

XML

反射和动态代理

方法引用

单元测试

类加载器

Java后端

JavaWeb SpringBoot 学习

苍穹外卖

微服务

SpringCloud学习

RabbitMQ

Nacos源码分析

Sentinel源码分析

Linux学习

Linux学习

Git学习

Git学习

Docker学习

Docker学习

中间件

ElasticSearch

RabbitMQ

数据库学习

Mysql学习-汇总

Mysql学习-基础篇

Mysql学习-高级篇

Mysql学习-运维篇

中间件学习

MybatisPlus学习

待续...

前端学习

Vue学习

HTML+CSS+移动端

目录

网络编程 ​

更新: 2025/4/9 字数: 0 字 时长: 0 分钟

一、初始网络编程 ​

更新: 2025/4/9 字数: 0 字 时长: 0 分钟

1、概念 ​

image-20250320224643234

2、常见的软件架构-CS/BS ​

image-20250320224752237

image-20250320224822243

image-20250320225006300

(1)BS架构-优缺点 ​

image-20250320224922982

(2)CS架构-优缺点 ​

image-20250320224944060

3、小结 ​

image-20250320225035376

二、网络编程三要素 ​

更新: 2025/4/9 字数: 0 字 时长: 0 分钟

image-20250320225213758

image-20250320225306014

1、📌==IP== ​

(1)概念 ​

image-20250320225428794

  • **IP 地址(Internet Protocol Address)**是设备在计算机网络中的唯一标识,就像家庭住址一样,用于标识和定位网络上的设备,以便数据能够准确地传输到目的地。

(2)IPv4 ​

image-20250320225737619

IPv4(Internet Protocol version 4)

  • 采用 32 位地址,由四组十进制数字(0~255)组成,每组 8 位(1 个字节),例如:

    192.168.1.1
  • 地址数量:IPv4 理论上能提供 约 42 亿 个 $$ 2^{32} $$ 地址,但由于地址分配不均等问题,可用地址远少于此。

  • 特点:

    • 由于地址资源紧缺,使用 NAT(网络地址转换)等技术延缓了 IPv4 地址枯竭问题。
    • 广泛使用于现有网络设备。

①IPv4的地址分类形式-局域网 ​

image-20250320230543067

image-20250320230603947

(3)IPv6 ​

IPv6(Internet Protocol version 6)

  • 采用 128 位地址,由 8 组 16 进制数(每组 16 位,2 个字节)组成,例如:

    makefile
    2001:0db8:85a3:0000:0000:8a2e:0370:7334
  • 地址数量:IPv6 理论上能提供 约 2的128次方 个地址,几乎可以给地球上的每一粒沙子都分配一个 IP。

  • 特点:

    • 解决 IPv4 地址枯竭问题。
    • 提供更高的安全性(内置 IPsec)。
    • 去除了 NAT,支持端到端通信。
    • 在 5G 时代及物联网(IoT)设备中应用越来越广泛。

image-20250320230009353

image-20250320225951539

(4)局域网-LAN ​

局域网(LAN, Local Area Network)

局域网(LAN)是指在有限的地理范围内(如家庭、公司、学校)连接的计算机网络。它主要用于设备之间的通信,而不需要连接到广域网(互联网)。

特点:

  • 设备之间数据传输速率较快(通常 100Mbps ~ 10Gbps)。
  • 主要使用 私有 IP 地址(如 192.168.1.0/24)。
  • 通过路由器或交换机连接多个设备。

常见局域网 IP 地址范围(私有 IP 地址段):

地址范围子网掩码
10.0.0.0 ~ 10.255.255.255255.0.0.0
172.16.0.0 ~ 172.31.255.255255.240.0.0
192.168.0.0 ~ 192.168.255.255255.255.0.0

(5)子网掩码 ​

子网掩码(Subnet Mask) ​

子网掩码用于划分子网,它与 IP 地址结合使用,决定了==网络部分和主机部分==。

  • 网络部分:标识设备所在的网段。
  • 主机部分:标识网段内的设备。

示例:

  • IP 地址: 192.168.1.10

  • 子网掩码:255.255.255.0

    • 网络部分:192.168.1

    • 主机部分:10(即该子网内的第 10 个设备)

    • 该子网最多可容纳 $$ 2^{(32-24)} - 2 =254 $$ 台设备。

子网掩码的作用 ​
  • 划分子网:减少广播流量,提高网络性能。
  • 决定网络边界:同一子网的设备可以直接通信,不同子网的设备需要路由器转发。

常见子网掩码示例

子网掩码对应网络地址数适用场景
255.0.0.0 (/8)约 1677 万个超大规模网络(A 类网络)
255.255.0.0 (/16)约 6.5 万个大型网络(B 类网络)
255.255.255.0 (/24)254 个小型企业、家庭网络(C 类网络)

(7)各自的使用场景 ​

概念作用使用场景
IPv4设备间通信,当前仍然是主流互联网、局域网
IPv6解决 IPv4 地址枯竭问题,提升安全性和效率物联网(IoT)、5G 网络、大型数据中心
局域网(LAN)连接同一地理区域的设备,方便资源共享家庭网络、公司内部网络、校园网
子网掩码划分子网,控制 IP 地址范围,提高网络性能运营商网络、大型企业网络

(8)特殊的IP地址-localhost ​

image-20250320230716695

image-20250320231110238

image-20250320231236610

(9)常见的CMD命令-ipconfig/ping ​

image-20250320231308978

(10)小结 ​

image-20250320230045638

image-20250320231321883

(11)InetAddress类的使用 ​

image-20250321143402293

①示例代码 ​

java
package com.itheima.a01InetAddressdemo;


import java.net.InetAddress;
import java.net.UnknownHostException;

public class MyInetAddressDemo1 {
    public static void main(String[] args) throws UnknownHostException {
/*
        static InetAddress getByName(String host)  确定主机名称的IP地址。主机名称可以是机器名称,也可以是IP地址
        String getHostName()                        获取此IP地址的主机名
        String getHostAddress()                     返回文本显示中的IP地址字符串
*/

      //1.获取InetAddress的对象
        //IP的对象 一台电脑的对象
        InetAddress address = InetAddress.getByName("DESKTOP-5OJJSAM");
        System.out.println(address);

        String name = address.getHostName();
        System.out.println(name);//DESKTOP-5OJJSAM

        String ip = address.getHostAddress();
        System.out.println(ip);//192.168.1.100
    }
}

(12)IP地址、网段、网关的关系 ​

在计算机网络中,网段、网关和IP地址是网络配置和通信的重要概念,它们之间紧密相关,用于确保设备在网络中的通信顺畅。以下是对这些概念的详细介绍及其联系:


1. IP地址 ​
  • 定义:

    • IP地址是分配给每个联网设备的唯一标识,用于在网络中定位和通信。
    • 常见的格式:
      • IPv4:如 192.168.1.1(点分十进制)。
      • IPv6:如 2001:0db8:85a3:0000:0000:8a2e:0370:7334(冒号十六进制)。
  • 组成:

    • IPv4由 网络部分 和 主机部分组成。

      • 网络部分:标识设备所在的网段。
      • 主机部分:标识网段内的设备。
    • 子网掩码(如 255.255.255.0)用于区分网络部分和主机部分。

  • 分类:

    • 公有IP:用于互联网。
    • 私有IP:用于局域网(如 192.168.x.x,10.x.x.x)。

2.网段 ​
  • 定义:
    • 网段是一个逻辑子网,由 IP地址和子网掩码定义。
    • 每个网段中的设备可以直接通信,无需通过网关。
  • 计算:
    • 通过 IP地址 和 子网掩码 可以计算网段范围。
      • 例子:
        • IP地址:192.168.1.100
        • 子网掩码:255.255.255.0
        • 网段:192.168.1.0/24,范围是 192.168.1.1 到 192.168.1.254。
  • 特点:
    • 网段内设备共享同一个网络号。
    • 广播地址是网段的最后一个地址(如 192.168.1.255)。

3. 网关 ​
  • 定义:
    • 网关是一个设备(通常是路由器),充当不同网段之间通信的桥梁。
    • 它是一个网络的默认出口,连接到外部网络(例如互联网)。
  • 特点:
    • 网关的IP地址通常是一个网段内的首地址或末地址(如 192.168.1.1)。
    • 如果目标地址不在当前网段,设备会将数据包发送到网关,由网关转发到目标网段。
  • 配置:
    • 设备必须配置网关IP地址才能与其他网段通信。

4. 联系 ​
概念作用联系
IP地址标识网络设备的唯一地址属于某个网段,通过子网掩码判断是否与目标地址在同一网段。
网段定义一组可以直接通信的设备IP地址决定设备所在的网段;同一网段设备无需网关即可通信。
网关连接不同网段的桥梁如果通信目标在其他网段,设备会将数据转发给网关。

5. 实际应用示例 ​

假设一个公司有以下网络配置:

  • 子网掩码:255.255.255.0(/24)。
  • 网段:192.168.1.0/24。
  • 网关:192.168.1.1。
  • 主机IP地址:192.168.1.100。
在同一网段中通信: ​
  • 如果主机

    192.168.1.100

    想与

    192.168.1.200

    通信:

    • 判断网段:
      • 192.168.1.100 和 192.168.1.200 都在 192.168.1.0/24 网段。
    • 直接通信:数据包通过交换机(或直连)发送到目标主机。
跨网段通信: ​
  • 如果主机

    192.168.1.100

    想与

    192.168.2.50

    通信:

    • 判断网段:
      • 192.168.1.100 属于 192.168.1.0/24。
      • 192.168.2.50 属于 192.168.2.0/24。
    • 跨网段通信:主机将数据包发送给网关(192.168.1.1),由网关转发到目标网段。

6. 关键点总结 ​
  • IP地址是设备在网络中的唯一标识,必须正确配置以确保通信。
  • 网段决定设备是否可以直接通信。
  • 网关是跨网段通信的必需设备。
  • 配置时需确保:
    • IP地址、子网掩码和网关在同一个网段内。
    • 不同网段的通信需要正确设置网关和路由规则。

2、端口号 ​

image-20250321143708525

image-20250321143747718

3、📌==协议== ​

image-20250321144257885

(1)OSI参考模型 ​

image-20250321144402213

(2)TCP/IP参考模型 ​

image-20250321144450755

(3)UDP协议 ​

①概念 ​

image-20250321144712761

②UDP通信程序-发送数据 ​

image-20250321145927429

java
package com.itheima.a02udpdemo1;


import java.io.IOException;
import java.net.*;

public class SendMessageDemo {
    public static void main(String[] args) throws IOException {
        //发送数据


        //1.创建DatagramSocket对象(快递公司)
        //细节:
        //绑定端口,以后我们就是通过这个端口往外发送
        //空参:所有可用的端口中随机一个进行使用
        //有参:指定端口号进行绑定
        DatagramSocket ds = new DatagramSocket(); //使用随机的端口进行发送

        //2.打包数据
        String str = "你好威啊!!!";
        byte[] bytes = str.getBytes();
        InetAddress address = InetAddress.getByName("127.0.0.1");
        int port = 10086;
        
        DatagramPacket dp = new DatagramPacket(bytes,bytes.length,address,port);

        //3.发送数据
        ds.send(dp);

        //4.释放资源
        ds.close();
    }
}

③UDP通信程序-接收数据 ​

image-20250321150310928

java
package com.itheima.a02udpdemo1;

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

public class ReceiveMessageDemo {
    public static void main(String[] args) throws IOException {
        //接收数据



        //1.创建DatagramSocket对象(快递公司)
        //细节:
        //在接收的时候,一定要绑定端口
        //而且绑定的端口一定要跟发送的端口保持一致
        DatagramSocket ds = new DatagramSocket(10086);


        //2.接收数据包
        byte[] bytes = new byte[1024];
        DatagramPacket dp = new DatagramPacket(bytes,bytes.length);

        //receive,该方法是阻塞的
        //程序执行到这一步的时候,会在这里死等
        //等发送端发送消息
        System.out.println(11111);
        ds.receive(dp);
        System.out.println(2222);

       //3.解析数据包
        byte[] data = dp.getData();
        int len = dp.getLength();
        InetAddress address = dp.getAddress(); //获取发送数据报的地址
        int port = dp.getPort(); //获取发送的端口位置

        System.out.println("接收到数据" + new String(data,0,len));
        System.out.println("该数据是从" + address + "这台电脑中的" + port + "这个端口发出的");

        //4.释放资源
        ds.close();
    }
}

结果

image-20250321150640995

④UDP练习-聊天室 ​

image-20250321150905172

示例代码-发送数据

java
package com.itheima.a03udpdemo2;


import java.io.IOException;
import java.net.*;
import java.util.Scanner;

public class SendMessageDemo {
    public static void main(String[] args) throws IOException {
         /*
            按照下面的要求实现程序
                UDP发送数据:数据来自于键盘录入,直到输入的数据是886,发送数据结束
                UDP接收数据:因为接收端不知道发送端什么时候停止发送,故采用死循环接收
        */

        //1.创建对象DatagramSocket的对象
        DatagramSocket ds = new DatagramSocket();

        //2.打包数据
        Scanner sc = new Scanner(System.in);
        while (true) {
            System.out.println("请输入您要说的话:");
            String str = sc.nextLine();
            if("886".equals(str)){
                break;
            }
            byte[] bytes = str.getBytes();
            //InetAddress address = InetAddress.getByName("255.255.255.255"); //广播
            InetAddress address = InetAddress.getByName("127.0.0.1");
            int port = 10086;
            DatagramPacket dp = new DatagramPacket(bytes,bytes.length,address,port);
            //3.发送数据
            ds.send(dp);
        }    
        //4.释放资源
        ds.close(); 
    }
}

示例代码-接收数据

java
package com.itheima.a03udpdemo2;

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

public class ReceiveMessageDemo {
    public static void main(String[] args) throws IOException {
        /*
            按照下面的要求实现程序
                UDP发送数据:数据来自于键盘录入,直到输入的数据是886,发送数据结束
                UDP接收数据:因为接收端不知道发送端什么时候停止发送,故采用死循环接收
        */

        //1.创建对象DatagramSocket的对象
        DatagramSocket ds = new DatagramSocket(10086); //指定接受数据报的端口

        //2.接收数据包
        byte[] bytes = new byte[1024];
        DatagramPacket dp = new DatagramPacket(bytes,bytes.length);

        while (true) {
            ds.receive(dp);

            //3.解析数据包
            byte[] data = dp.getData();
            int len = dp.getLength();
            String ip = dp.getAddress().getHostAddress();
            String name = dp.getAddress().getHostName();

            //4.打印数据
            System.out.println("ip为:" + ip + ",主机名为:" + name + "的人,发送了数据:" + new String(data,0,len));
        }
    }
}

测试结果

image-20250321151725803

image-20250321151843189

(4)UDP的三种通讯方式 ​

image-20250321152345594

①单播-DatagramSocket ​

image-20250321152434615

代码实现 ​

image-20250321152519335

java
package com.itheima.a03udpdemo2;


import java.io.IOException;
import java.net.*;
import java.util.Scanner;

public class SendMessageDemo {
    public static void main(String[] args) throws IOException {
         /*
            按照下面的要求实现程序
                UDP发送数据:数据来自于键盘录入,直到输入的数据是886,发送数据结束
                UDP接收数据:因为接收端不知道发送端什么时候停止发送,故采用死循环接收
        */

        //1.创建对象DatagramSocket的对象
        DatagramSocket ds = new DatagramSocket();

        //2.打包数据
        Scanner sc = new Scanner(System.in);
        while (true) {
            System.out.println("请输入您要说的话:");
            String str = sc.nextLine();
            if("886".equals(str)){
                break;
            }
            byte[] bytes = str.getBytes();
            //InetAddress address = InetAddress.getByName("255.255.255.255"); //广播
            InetAddress address = InetAddress.getByName("127.0.0.1");
            int port = 10086;
            DatagramPacket dp = new DatagramPacket(bytes,bytes.length,address,port);
            //3.发送数据
            ds.send(dp);
        }    
        //4.释放资源
        ds.close(); 
    }
}

②组播-MulticastSocket ​

image-20250321152445622

代码实现 ​

image-20250321152648829

发送端

java
package com.itheima.a04udpdemo3;


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

public class SendMessageDemo {
    public static void main(String[] args) throws IOException {
         /*
            组播发送端代码
        */

        //创建MulticastSocket对象
        MulticastSocket ms = new MulticastSocket() ;

        // 创建DatagramPacket对象
        String s = "你好,你好!" ;
        byte[] bytes = s.getBytes();
        InetAddress address = InetAddress.getByName("224.0.0.1");  //组播地址
        int port = 10000;

        DatagramPacket datagramPacket = new DatagramPacket(bytes, bytes.length, address, port) ;

        // 调用MulticastSocket发送数据方法发送数据
        ms.send(datagramPacket);

        // 释放资源
        ms.close();
    }
}

接收端-1

java
package com.itheima.a04udpdemo3;

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

public class ReceiveMessageDemo1 {
    public static void main(String[] args) throws IOException {
        /*
            组播接收端代码
        */


        //1. 创建MulticastSocket对象
        MulticastSocket ms = new MulticastSocket(10000);

        //2. 将将当前本机,添加到224.0.0.1的这一组当中
        InetAddress address = InetAddress.getByName("224.0.0.1");
        ms.joinGroup(address);

        //3. 创建DatagramPacket数据包对象
        byte[] bytes = new byte[1024];
        DatagramPacket dp = new DatagramPacket(bytes, bytes.length);

        //4. 接收数据
        ms.receive(dp);

        //5. 解析数据
        byte[] data = dp.getData();
        int len = dp.getLength();
        String ip = dp.getAddress().getHostAddress();
        String name = dp.getAddress().getHostName();

        System.out.println("ip为:" + ip + ",主机名为:" + name + "的人,发送了数据:" + new String(data,0,len));

        //6. 释放资源
        ms.close();
    }
}

接收端-2

java
package com.itheima.a04udpdemo3;

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

public class ReceiveMessageDemo2 {
    public static void main(String[] args) throws IOException {
        /*
            组播接收端代码
        */


        //1. 创建MulticastSocket对象
        MulticastSocket ms = new MulticastSocket(10000);

        //2. 将将当前本机,添加到224.0.0.1的这一组当中
        InetAddress address = InetAddress.getByName("224.0.0.1");
        ms.joinGroup(address);

        //3. 创建DatagramPacket数据包对象
        byte[] bytes = new byte[1024];
        DatagramPacket dp = new DatagramPacket(bytes, bytes.length);

        //4. 接收数据
        ms.receive(dp);

        //5. 解析数据
        byte[] data = dp.getData();
        int len = dp.getLength();
        String ip = dp.getAddress().getHostAddress();
        String name = dp.getAddress().getHostName();

        System.out.println("ip为:" + ip + ",主机名为:" + name + "的人,发送了数据:" + new String(data,0,len));

        //6. 释放资源
        ms.close();
    }
}

③广播-DatagramSocket ​

image-20250321152456750

代码实现 ​

image-20250321152605435

java
package com.itheima.a03udpdemo2;


import java.io.IOException;
import java.net.*;
import java.util.Scanner;

public class SendMessageDemo {
    public static void main(String[] args) throws IOException {
         /*
            按照下面的要求实现程序
                UDP发送数据:数据来自于键盘录入,直到输入的数据是886,发送数据结束
                UDP接收数据:因为接收端不知道发送端什么时候停止发送,故采用死循环接收
        */

        //1.创建对象DatagramSocket的对象
        DatagramSocket ds = new DatagramSocket();

        //2.打包数据
        Scanner sc = new Scanner(System.in);
        while (true) {
            System.out.println("请输入您要说的话:");
            String str = sc.nextLine();
            if("886".equals(str)){
                break;
            }
            byte[] bytes = str.getBytes();
            InetAddress address = InetAddress.getByName("255.255.255.255"); //广播
            int port = 10086;
            DatagramPacket dp = new DatagramPacket(bytes,bytes.length,address,port);
            //3.发送数据
            ds.send(dp);
        }    
        //4.释放资源
        ds.close(); 
    }
}

(5)TCP协议 ​

①概念 ​

image-20250321144714361

②TCP通信程序 ​

image-20250321153512509

image-20250321153601713

1)Socket-客户端发送数据 ​
java
package com.itheima.a05tcpdemo1;


import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;

public class Client {
    public static void main(String[] args) throws IOException {
        //TCP协议,发送数据

        //1.创建Socket对象
        //细节:在创建对象的同时会连接服务端
        //      如果连接不上,代码会报错
        Socket socket = new Socket("127.0.0.1",10000); //和服务器的端口对应

        //2.可以从连接通道中获取输出流
        OutputStream os = socket.getOutputStream();
        //写出数据
        os.write("aaa".getBytes());

        //3.释放资源
        os.close();
        socket.close();
    }
}
2)ServerSocket-服务器接收数据 ​
java
package com.itheima.a05tcpdemo1;

import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class Server {
    public static void main(String[] args) throws IOException {
        //TCP协议,接收数据

        //1.创建对象ServerSocker
        ServerSocket ss = new ServerSocket(10000); //和客户端的端口对应

        //2.监听客户端的链接
        Socket socket = ss.accept();

        //3.从连接通道中获取输入流读取数据
        InputStream is = socket.getInputStream();
        int b;
        //细节:
        //read方法会从连接通道中读取数据
        //但是,需要有一个结束标记(客户端发送的,socket.shutdownOutput()),此处的循环才会停止
        //否则,程序就会一直停在read方法这里,等待读取下面的数据
        while ((b = is.read()) != -1){
            System.out.println((char) b);
        }

        //4.释放资源
        socket.close();
        ss.close();
    }
}
3)解决中文字符乱码问题-转换流 ​

ServerSocket-服务器接收数据时,使用转换流,将字符流转化为字符流

java
package com.itheima.a06tcpdemo2;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;

public class Server {
    public static void main(String[] args) throws IOException {
        //TCP协议,接收数据

        //1.创建对象ServerSocker
        ServerSocket ss = new ServerSocket(10000);

        //2.监听客户端的链接
        Socket socket = ss.accept();

        //3.从连接通道中获取输入流读取数据
        /*InputStream is = socket.getInputStream();
        InputStreamReader isr = new InputStreamReader(is);
        BufferedReader br = new BufferedReader(isr);*/

        BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        
        int b;
        while ((b = br.read()) != -1){
            System.out.print((char) b);
        }
        //4.释放资源
        socket.close();
        ss.close();
    }
}

③TCP通信的细节 ​

image-20250321155056536

(6)TCP协议的==三次握手和四次挥手== ​

①三次握手 ​

image-20250321155507555

1.为什么 TCP 需要三次握手? ​

TCP(Transmission Control Protocol)是面向连接的协议,三次握手(Three-Way Handshake)是 TCP 建立可靠连接的关键步骤,主要目的是确保通信双方能够可靠地发送和接收数据,并同步状态信息。


2.三次握手的具体过程 ​
  1. 第一次握手(SYN)
    • 客户端发送一个 SYN(同步)报文到服务器,表示请求建立连接,同时指明客户端的初始序列号 Seq = X。
    • 此时,客户端进入 SYN_SENT 状态,等待服务器响应。
  2. 第二次握手(SYN + ACK)
    • 服务器收到 SYN 报文后,向客户端返回一个 SYN + ACK 报文,表示同意建立连接。
    • 服务器在这个报文中包含自己的初始序列号 Seq = Y,同时确认客户端的序列号 Ack = X + 1。
    • 此时,服务器进入 SYN_RCVD 状态。
  3. 第三次握手(ACK)
    • 客户端收到 SYN + ACK 后,再发送一个 ACK 报文,表示收到并确认服务器的序列号 Ack = Y + 1。
    • 同时,客户端进入 ESTABLISHED 状态,表示连接已建立。
    • 服务器收到 ACK 后,也进入 ESTABLISHED 状态。

3.为什么需要三次,而不是两次或更多? ​
  1. 一次握手不足:缺少确认机制 如果只有一次握手,客户端发送了 SYN 报文,但无法确认服务器是否收到该请求,也无法确保服务器是否已做好接收数据的准备。
  2. 两次握手不足:存在假连接风险 假设使用两次握手:
    • 客户端发送 SYN 报文,服务器返回 SYN + ACK,此时服务器认为连接已经建立。
    • 如果客户端的初始 SYN 报文是由于网络延迟导致的陈旧数据包(而客户端并未真正请求连接),服务器会错误地认为连接已建立并占用资源(产生半开连接)。
    • 使用三次握手时,只有客户端对服务器的 SYN + ACK 再次确认后,连接才真正建立,可以避免这种问题。
  3. 三次握手足够:确保双方同步通信能力
    • 客户端和服务器通过三次交互,双方都能确认对方具备发送和接收数据的能力,并同步初始序列号,保证通信的可靠性。
  4. 四次或更多:无必要
    • 三次握手已经能确保通信双方可靠连接,更多步骤会增加额外的通信开销,影响效率。

4.总结 ​

TCP 三次握手的设计是在可靠性与效率之间的权衡:

  • 通过三次交互,确保通信双方具备正常通信能力,避免陈旧连接的问题。
  • 减少不必要的握手步骤,提高连接建立的效率。

这是 TCP 保证数据传输可靠性的核心机制之一。

②四次挥手 ​

image-20250321160113300

1. 什么是 TCP 四次挥手? ​

TCP(Transmission Control Protocol)是一个面向连接的、可靠的传输协议,当通信结束时,TCP 需要通过四次挥手(Four-Way Handshake)来断开连接,确保双方都正确释放资源。


2. 为什么需要四次挥手? ​

TCP 是全双工通信,即数据可以同时在两个方向传输。因此,关闭连接时,每一方都需要独立地向对方发送 FIN(Finish)标志,表示“我不再发送数据了,但还能接收数据”。 四次挥手保证了数据的完整性,防止因某一方突然断开导致数据丢失


3. TCP 四次挥手的具体过程 ​

① 第一次挥手(客户端 → 服务器)

  • 客户端 发送 FIN(Finish)请求,告诉 服务器:“我不再发送数据了,但仍然可以接收数据。”
  • 客户端进入 FIN_WAIT_1 状态。

② 第二次挥手(服务器 → 客户端)

  • 服务器 回复 ACK(确认),表示收到了 FIN,但可能还有数据需要发送。
  • 服务器进入 CLOSE_WAIT 状态,客户端进入 FIN_WAIT_2 状态。

③ 第三次挥手(服务器 → 客户端)

  • 服务器 发送 FIN,告诉客户端:“我也不再发送数据了。”
  • 服务器进入 LAST_ACK 状态。

④ 第四次挥手(客户端 → 服务器)

  • 客户端 回复 ACK,表示“我收到了你的 FIN,连接可以断开了。”
  • 客户端进入 TIME_WAIT 状态,等待 2 * MSL(最大报文生存时间)后释放资源,防止服务器未收到 ACK。
  • 服务器收到 ACK 后,进入 CLOSED 状态,连接关闭。

4. 为什么是四次,而不是三次或更多? ​
  1. 不能三次挥手的原因
    • 服务器可能还有数据要发送,所以第二次挥手时,不能立刻发送 FIN,否则数据可能丢失。
    • 服务器需要等到数据发送完毕后,才能发送 FIN,所以需要分开两步。
  2. 为什么不需要更多次?
    • 四次刚好保证了双方都能确认数据传输已经结束。
    • ACK 只是简单的确认,不需要单独的 ACK 确认 ACK,否则会无限增加握手次数。

5. 总结 ​

✅ 四次挥手用于安全可靠地关闭 TCP 连接,确保数据完整性。 ✅ 客户端和服务器都需要各自确认“发送完毕”和“接收完毕”,因此需要四次交互。 ✅ 客户端 TIME_WAIT 状态防止服务器未收到 ACK,保证连接完全关闭。 ✅ 相比三次挥手,四次挥手确保了服务器能在发送完所有数据后再断开连接。

📌 重点记忆:四次挥手 = 两个方向的 FIN + ACK,保证双方都正确关闭连接! 🚀

4、其他协议 ​

(1)什么是 FTP? ​

📌 是什么? ​

FTP(File Transfer Protocol,文件传输协议)是一种用于在网络上进行文件传输的协议,基于 TCP/IP 协议工作,支持上传、下载、删除、重命名等文件操作。

📌 为什么需要? ​

  • 早期互联网需要一个标准的方式在计算机之间传输文件,FTP 解决了这个问题。
  • 支持用户身份认证,可进行权限管理。
  • 适用于大文件传输,支持断点续传。

📌 具体过程 ​

FTP 采用 客户端-服务器(C/S)模式,使用两个端口进行通信:

  1. 控制连接(端口 21):用于发送命令,如 USER(登录)、LIST(列出文件)、RETR(下载文件)。
  2. 数据连接(端口 20):用于传输文件数据。

两种模式:

  • 主动模式(PORT):服务器主动连接客户端的数据端口。
  • 被动模式(PASV):客户端请求服务器提供可用端口,客户端再连接该端口。

📌 使用场景 ​

  • 服务器之间大文件传输。
  • 网站部署时上传代码、下载日志。
  • 远程存储备份数据。

📌 总结 ​

✅ FTP 是一种文件传输协议,使用 TCP 端口 21 控制连接,端口 20 传输数据。 ✅ 主动模式和被动模式两种连接方式,保证数据顺利传输。 ✅ 适用于文件服务器、大文件共享、网站管理等场景。


(2)什么是 Telnet? ​

📌 是什么? ​

Telnet(TELecommunication NETwork,远程登录协议)是一种基于 TCP 的协议,用于远程管理和控制计算机。

📌 为什么需要? ​

  • 允许用户在本地设备上远程登录到服务器并执行命令。
  • 适用于远程管理设备,如服务器、交换机、路由器。
  • 但 Telnet 传输的数据(包括密码)是明文的,不安全,常被 SSH 取代。

📌 具体过程 ​

  1. 客户端发送连接请求,服务器监听端口 23,建立 TCP 连接。
  2. 服务器验证用户身份(用户名+密码)。
  3. 成功登录后,用户可以像在本地一样操作远程计算机。
  4. 退出登录,关闭连接。

📌 使用场景 ​

  • 远程管理 Linux/Unix 服务器(现多用 SSH)。
  • 测试网络连通性(如 telnet IP 端口)。
  • 配置网络设备(交换机、路由器)。

📌 总结 ​

✅ Telnet 是一种远程登录协议,使用 端口 23,可在远程服务器上执行命令。 ✅ 但不安全(明文传输),现在已被 SSH(Secure Shell)取代。 ✅ 仍可用于调试网络连接,但不建议用于正式环境。


(3)什么是 DNS? ​

📌 是什么? ​

DNS(Domain Name System,域名系统)是用于将域名解析为 IP 地址的系统,类似于互联网的“电话簿”。

📌 为什么需要? ​

  • 方便用户访问网站,不用记住复杂的 IP 地址(如 8.8.8.8)。
  • 允许域名与 IP 地址动态映射,提高灵活性。
  • 支持负载均衡,分配流量到不同服务器。

📌 具体过程 ​

  1. 用户输入 www.example.com。
  2. 本地 DNS 服务器查询缓存,若无,则向根 DNS 服务器请求解析。
  3. 逐级查询 .com 顶级域名服务器(TLD)、权威 DNS 服务器,最终返回 93.184.216.34(IP 地址)。
  4. 用户设备使用该 IP 访问目标服务器。

📌 使用场景 ​

  • 网站访问(浏览器输入网址)。
  • CDN 加速(动态解析到最近的服务器)。
  • 邮件服务器查找(MX 记录)。

📌 总结 ​

✅ DNS 解析域名到 IP,简化网络访问。 ✅ 采用分层架构(根 DNS、TLD、权威 DNS),提高查询效率。 ✅ CDN、负载均衡、邮件解析等都依赖 DNS。


(4)什么是 ICMP? ​

📌 是什么? ​

ICMP(Internet Control Message Protocol,互联网控制报文协议)是用于发送网络错误报告和状态信息的协议,通常用于检测网络状态(如 ping)。

📌 为什么需要? ​

  • 用于网络故障排查(检测连通性、故障原因)。
  • 通知网络错误(如主机不可达、超时)。
  • 提供网络调试工具,如 ping、traceroute。

📌 具体过程(以 ping 为例) ​

  1. 客户端发送 ICMP 请求(Echo Request)。
  2. 目标主机收到后返回 ICMP 响应(Echo Reply)。
  3. 客户端计算 RTT(往返时间),判断网络状况。

📌 使用场景 ​

  • ping 测试网络连通性。
  • traceroute 检测数据包经过的路径。
  • 网络设备检测(如路由器 ICMP 超时通知)。

📌 总结 ​

✅ ICMP 主要用于网络状态检测和故障排查。 ✅ ping、traceroute 等工具基于 ICMP。 ✅ 但 ICMP 易被滥用(DDoS 攻击),很多服务器会禁用 ICMP。


(5)什么是 ARP? ​

📌 是什么? ​

ARP(Address Resolution Protocol,地址解析协议)用于将 IP 地址解析为 MAC 地址,确保数据包能在局域网(LAN)内正确传输。

📌 为什么需要? ​

  • IP 地址用于网络层通信,但数据链路层(如以太网)依赖 MAC 地址传输数据。
  • ARP 解决了 IP → MAC 映射问题,确保数据能正确送达。

📌 具体过程 ​

  1. 主机 A 发送 ARP 请求(广播),询问“192.168.1.2 的 MAC 地址是多少?”
  2. 主机 B(目标 IP)回复 ARP 响应,提供其 MAC 地址。
  3. 主机 A 记录 B 的 MAC 地址,存入 ARP 缓存,加速后续通信。

📌 使用场景 ​

  • 局域网通信(如 PC 访问网关)。
  • 交换机、路由器设备自动解析 MAC。
  • 网络攻击(ARP 欺骗,用于中间人攻击 MITM)。

📌 总结 ​

✅ ARP 解析 IP 地址到 MAC 地址,用于局域网通信。 ✅ 基于广播请求-单播响应机制,提高效率。 ✅ 可能被滥用(ARP 欺骗),可用 ARP 绑定防范攻击。 🚀

三、综合练习 ​

更新: 2025/4/9 字数: 0 字 时长: 0 分钟

1、TCP通信练习1-多发多收 ​

image-20250321161220339

Client

java
package com.itheima.a07test1;


import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Scanner;

public class Client {
    public static void main(String[] args) throws IOException {
        //客户端:多次发送数据
        //服务器:接收多次接收数据,并打印

        //1. 创建Socket对象并连接服务端
        Socket socket = new Socket("127.0.0.1",10000);

        //2.写出数据
        Scanner sc = new Scanner(System.in);
        OutputStream os = socket.getOutputStream();

        while (true) {
            System.out.println("请输入您要发送的信息");
            String str = sc.nextLine();
            if("886".equals(str)){
                break;
            }
            os.write(str.getBytes());
        }
        //3.释放资源
        socket.close();
    }
}

Server

客户端没有关闭,也就是没有断开连接,所以服务端也不会断开,四次挥手不会触发,服务器就可以一直接收数据

java
package com.itheima.a07test1;

import java.io.IOException;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;

public class Server {
    public static void main(String[] args) throws IOException {
        //客户端:多次发送数据
        //服务器:接收多次接收数据,并打印

        //1.创建对象绑定10000端口
        ServerSocket ss = new ServerSocket(10000);

        //2.等待客户端来连接
        Socket socket = ss.accept();

        //3.读取数据
        InputStreamReader isr = new InputStreamReader(socket.getInputStream());
        int b;
        while ((b = isr.read()) != -1){
            System.out.print((char)b);
        }
        //4.释放资源
        socket.close();
        ss.close();
    }
}

2、TCP通信练习2-接收并反馈 ​

image-20250321161306200

Client

java
package com.itheima.a08test2;


import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;

public class Client {
    public static void main(String[] args) throws IOException {
        //客户端:发送一条数据,接收服务端反馈的消息并打印
        //服务器:接收数据并打印,再给客户端反馈消息

        //1.创建Socket对象并连接服务端
        Socket socket = new Socket("127.0.0.1",10000);

        //2.写出数据
        String str = "见到你很高兴!";
        OutputStream os = socket.getOutputStream();
        os.write(str.getBytes());

        //写出一个结束标记
        socket.shutdownOutput();

        //3.接收服务端回写的数据
        InputStream is = socket.getInputStream();
        InputStreamReader isr = new InputStreamReader(is);
        int b;
        while ((b = isr.read()) != -1){
            System.out.print((char)b);
        }

        //释放资源
        socket.close();
    }
}

Server

java
package com.itheima.a08test2;


import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class Server {
    public static void main(String[] args) throws IOException {
        //客户端:发送一条数据,接收服务端反馈的消息并打印
        //服务器:接收数据并打印,再给客户端反馈消息

        //1.创建对象并绑定10000端口
        ServerSocket ss = new ServerSocket(10000);

        //2.等待客户端连接
        Socket socket = ss.accept();

        //3.socket中获取输入流读取数据
        InputStream is = socket.getInputStream();
        InputStreamReader isr = new InputStreamReader(is);
        int b;
        //细节:
        //read方法会从连接通道中读取数据
        //但是,需要有一个结束标记(客户端发送的,socket.shutdownOutput()),此处的循环才会停止
        //否则,程序就会一直停在read方法这里,等待读取下面的数据
        while ((b = isr.read()) != -1){
            System.out.println((char)b);
        }

        //4.回写数据
        String str = "到底有多开心?";
        OutputStream os = socket.getOutputStream();
        os.write(str.getBytes());

        //释放资源
        socket.close();
        ss.close();
    }
}

3、TCP通信练习3-上传文件 ​

image-20250321161450903

Client

java
package com.itheima.a09test3;


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

public class Client {
    public static void main(String[] args) throws IOException {
        //客户端:将本地文件上传到服务器。接收服务器的反馈。
        //服务器:接收客户端上传的文件,上传完毕之后给出反馈。


        //1. 创建Socket对象,并连接服务器
        Socket socket = new Socket("127.0.0.1",10000);

        //2.读取本地文件中的数据,并写到服务器当中
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream("mysocketnet\\clientdir\\a.jpg"));
        BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());
        byte[] bytes = new byte[1024];
        int len;
        while ((len = bis.read(bytes)) != -1){
            bos.write(bytes,0,len);
        }
        
        bos.flush();
        bis.close();

        //往服务器写出结束标记
        socket.shutdownOutput();

        //3.接收服务器的回写数据
        BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        String line = br.readLine();
        System.out.println(line);
        
        //4.释放资源
        socket.close();
    }
}

Server

java
package com.itheima.a09test3;


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

public class Server {
    public static void main(String[] args) throws IOException {
        //客户端:将本地文件上传到服务器。接收服务器的反馈。
        //服务器:接收客户端上传的文件,上传完毕之后给出反馈。


        //1.创建对象并绑定端口
        ServerSocket ss = new ServerSocket(10000);

        //2.等待客户端来连接
        Socket socket = ss.accept();

        //3.读取数据并保存到本地文件中
        BufferedInputStream bis = new BufferedInputStream(socket.getInputStream());
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("mysocketnet\\serverdir\\a.jpg"));
        int len;
        byte[] bytes = new byte[1024];
        while ((len = bis.read(bytes)) != -1){
            bos.write(bytes,0,len);
        }
        bos.close(); //关闭本地文件输出流
        
        //4.回写数据
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
        bw.write("上传成功");
        bw.newLine();
        bw.flush();

        //5.释放资源
        socket.close();
        ss.close();
    }
}

4、TCP通信练习4-上传文件(文件名重复问题) ​

image-20250321161530425

(1)UUID类 ​

image-20250321164535859

image-20250321164555435

java
package com.itheima.a10test4;

import java.util.UUID;

public class UUIDTest {
    public static void main(String[] args) {
        String str = UUID.randomUUID().toString().replace("-", "");
        System.out.println(str);//9f15b8c356c54f55bfcb0ee3023fce8a
    }
}

(2)修改代码 ​

Client

java
package com.itheima.a10test4;


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

public class Client {
    public static void main(String[] args) throws IOException {
        //客户端:将本地文件上传到服务器。接收服务器的反馈。
        //服务器:接收客户端上传的文件,上传完毕之后给出反馈。


        //1. 创建Socket对象,并连接服务器
        Socket socket = new Socket("127.0.0.1",10000);

        //2.读取本地文件中的数据,并写到服务器当中
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream("mysocketnet\\clientdir\\a.jpg"));
        BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());
        byte[] bytes = new byte[1024];
        int len;
        while ((len = bis.read(bytes)) != -1){
            bos.write(bytes,0,len);
        }

        //往服务器写出结束标记
        socket.shutdownOutput();

        //3.接收服务器的回写数据
        BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        String line = br.readLine();
        System.out.println(line);

        //4.释放资源
        socket.close();

    }
}

Server

java
package com.itheima.a10test4;


import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.UUID;

public class Server {
    public static void main(String[] args) throws IOException {
        //客户端:将本地文件上传到服务器。接收服务器的反馈。
        //服务器:接收客户端上传的文件,上传完毕之后给出反馈。


        //1.创建对象并绑定端口
        ServerSocket ss = new ServerSocket(10000);

        //2.等待客户端来连接
        Socket socket = ss.accept();

        //3.读取数据并保存到本地文件中
        BufferedInputStream bis = new BufferedInputStream(socket.getInputStream());
        String name = UUID.randomUUID().toString().replace("-", "");
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("mysocketnet\\serverdir\\" + name + ".jpg"));  //使用UUID解决重复问题  
        int len;
        byte[] bytes = new byte[1024];
        while ((len = bis.read(bytes)) != -1) {
            bos.write(bytes, 0, len);
        }
        bos.close();
        //4.回写数据
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
        bw.write("上传成功");
        bw.newLine();
        bw.flush();

        //5.释放资源
        socket.close();
        ss.close();
    }
}

5、TCP通信练习5-上传文件(多线程版) ​

image-20250321161557459

自定义线程

java
package com.itheima.a11test5;

import java.io.*;
import java.net.Socket;
import java.util.UUID;

public class MyRunnable implements Runnable{

    Socket socket;

    public MyRunnable(Socket socket){
        this.socket = socket;
    }

    @Override
    public void run() {
        try {
            //3.读取数据并保存到本地文件中
            BufferedInputStream bis = new BufferedInputStream(socket.getInputStream());
            String name = UUID.randomUUID().toString().replace("-", "");
            BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("mysocketnet\\serverdir\\" + name + ".jpg"));
            int len;
            byte[] bytes = new byte[1024];
            while ((len = bis.read(bytes)) != -1) {
                bos.write(bytes, 0, len);
            }
            bos.close();
            //4.回写数据
            BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
            bw.write("上传成功");
            bw.newLine();
            bw.flush();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //5.释放资源
           if(socket != null){
               try {
                   socket.close();
               } catch (IOException e) {
                   e.printStackTrace();
               }
           }
        }
    }
}

Client

java
package com.itheima.a11test5;


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

public class Client {
    public static void main(String[] args) throws IOException {
        //客户端:将本地文件上传到服务器。接收服务器的反馈。
        //服务器:接收客户端上传的文件,上传完毕之后给出反馈。


        //1. 创建Socket对象,并连接服务器
        Socket socket = new Socket("127.0.0.1",10000);

        //2.读取本地文件中的数据,并写到服务器当中
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream("mysocketnet\\clientdir\\a.jpg"));
        BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());
        byte[] bytes = new byte[1024];
        int len;
        while ((len = bis.read(bytes)) != -1){
            bos.write(bytes,0,len);
        }

        //往服务器写出结束标记
        socket.shutdownOutput();

        //3.接收服务器的回写数据
        BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        String line = br.readLine();
        System.out.println(line);
        
        //4.释放资源
        socket.close();
    }
}

Server

java
package com.itheima.a11test5;


import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

public class Server {
    public static void main(String[] args) throws IOException {
        //客户端:将本地文件上传到服务器。接收服务器的反馈。
        //服务器:接收客户端上传的文件,上传完毕之后给出反馈。


        //1.创建对象并绑定端口
        ServerSocket ss = new ServerSocket(10000);

        while (true) {
            //2.等待客户端来连接
            Socket socket = ss.accept();

            //开启一条线程
            //一个用户就对应服务端的一条线程
            new Thread(new MyRunnable(socket)).start();
        }
    }
}

6、TCP通信练习6-上传文件(线程池优化) ​

image-20250321161704516

自定义线程

java
package com.itheima.a12test6;

import java.io.*;
import java.net.Socket;
import java.util.UUID;

public class MyRunnable implements Runnable{

    Socket socket;

    public MyRunnable(Socket socket){
        this.socket = socket;
    }

    @Override
    public void run() {
        try {
            //3.读取数据并保存到本地文件中
            BufferedInputStream bis = new BufferedInputStream(socket.getInputStream());
            String name = UUID.randomUUID().toString().replace("-", "");
            BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("mysocketnet\\serverdir\\" + name + ".jpg"));
            int len;
            byte[] bytes = new byte[1024];
            while ((len = bis.read(bytes)) != -1) {
                bos.write(bytes, 0, len);
            }
            bos.close();
            //4.回写数据
            BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
            bw.write("上传成功");
            bw.newLine();
            bw.flush();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //5.释放资源
           if(socket != null){
               try {
                   socket.close();
               } catch (IOException e) {
                   e.printStackTrace();
               }
           }
        }
    }
}

Client

java
package com.itheima.a12test6;


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

public class Client {
    public static void main(String[] args) throws IOException {
        //客户端:将本地文件上传到服务器。接收服务器的反馈。
        //服务器:接收客户端上传的文件,上传完毕之后给出反馈。


        //1. 创建Socket对象,并连接服务器
        Socket socket = new Socket("127.0.0.1",10000);

        //2.读取本地文件中的数据,并写到服务器当中
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream("mysocketnet\\clientdir\\a.jpg"));
        BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());
        byte[] bytes = new byte[1024];
        int len;
        while ((len = bis.read(bytes)) != -1){
            bos.write(bytes,0,len);
        }

        //往服务器写出结束标记
        socket.shutdownOutput();

        //3.接收服务器的回写数据
        BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        String line = br.readLine();
        System.out.println(line);
        
        //4.释放资源
        socket.close();
    }
}

Server

java
package com.itheima.a12test6;


import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class Server {
    public static void main(String[] args) throws IOException {
        //客户端:将本地文件上传到服务器。接收服务器的反馈。
        //服务器:接收客户端上传的文件,上传完毕之后给出反馈。

        //创建线程池对象
        ThreadPoolExecutor pool = new ThreadPoolExecutor(
                3,//核心线程数量
                16,//线程池总大小
                60,//空闲时间
                TimeUnit.SECONDS,//空闲时间(单位)
                new ArrayBlockingQueue<>(2),//阻塞队列
                Executors.defaultThreadFactory(),//线程工厂,让线程池如何创建线程对象
                new ThreadPoolExecutor.AbortPolicy()//任务拒绝策略
        );

        //1.创建对象并绑定端口
        ServerSocket ss = new ServerSocket(10000);

        while (true) {
            //2.等待客户端来连接
            Socket socket = ss.accept();

            //开启一条线程
            //一个用户就对应服务端的一条线程
            //new Thread(new MyRunnable(socket)).start();
            pool.submit(new MyRunnable(socket));
        }

    }
}

7、TCP通信练习7-BS(接收浏览器的消息) ​

image-20250321161806951

java
package com.itheima.a07test1;

import java.io.IOException;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;

public class Server {
    public static void main(String[] args) throws IOException {
        //客户端:多次发送数据
        //服务器:接收多次接收数据,并打印

        //1.创建对象绑定10000端口
        ServerSocket ss = new ServerSocket(10000);

        //2.等待客户端来连接
        Socket socket = ss.accept();

        //3.读取数据
        InputStreamReader isr = new InputStreamReader(socket.getInputStream());
        int b;
        while ((b = isr.read()) != -1){
            System.out.print((char)b);
        }
        //4.释放资源
        socket.close();
        ss.close();
    }
}

image-20250321170401497

8、TCP通信练习8-聊天室 ​

image-20250321161851696

(1)完整需求 ​

项目名称 ​

​ 利用TCP协议,做一个带有登录,注册的无界面,控制版的多人聊天室。

使用到的知识点 ​

​ 循环,判断,集合,IO,多线程,网络编程等

准备工作 ​

在当前模块下新建txt文件,文件中保存正确的用户名和密码

文件内容如下:

java
//左边是用户名
//右边是密码
zhangsan=123
lisi=1234
wangwu=12345

需求描述 ​

① 客户端启动之后,需要连接服务端,并出现以下提示:

java
服务器已经连接成功
==============欢迎来到黑马聊天室================
1登录
2注册
请输入您的选择:

②选择登录之后,出现以下提示:

java
服务器已经连接成功
==============欢迎来到黑马聊天室================
1登录
2注册
请输入您的选择:
1
请输入用户名

③需要输入用户名和密码,输入完毕,没有按回车时,效果如下:

java
服务器已经连接成功
==============欢迎来到黑马聊天室================
1登录
2注册
请输入您的选择:
1
请输入用户名
zhangsan
请输入密码
123

④按下回车,提交给服务器验证

服务器会结合txt文件中的用户名和密码进行判断

根据不同情况,服务器回写三种判断提示:

java
服务器回写第一种提示:登录成功
服务器回写第二种提示:密码有误  
服务器回写第三种提示:用户名不存在

⑤客户端接收服务端回写的数据,根据三种情况进行不同的处理方案

登录成功的情况, 可以开始聊天,出现以下提示:

java
服务器已经连接成功
==============欢迎来到黑马聊天室================
1登录
2注册
请输入您的选择:
1
请输入用户名
zhangsan
请输入密码
123
1
登录成功,开始聊天
请输入您要说的话

密码错误的情况,需要重新输入,出现以下提示:

java
服务器已经连接成功
==============欢迎来到黑马聊天室================
1登录
2注册
请输入您的选择:
1
请输入用户名
zhangsan
请输入密码
aaa
密码输入错误
==============欢迎来到黑马聊天室================
1登录
2注册
请输入您的选择:

用户名不存在的情况,需要重新输入,出现以下提示:

java
服务器已经连接成功
==============欢迎来到黑马聊天室================
1登录
2注册
请输入您的选择:
1
请输入用户名
zhaoliu
请输入密码
123456
用户名不存在
==============欢迎来到黑马聊天室================
1登录
2注册
请输入您的选择:

⑥如果成功登录,就可以开始聊天,此时的聊天是群聊,一个人发消息给服务端,服务端接收到之后需要群发给所有人

提示:

​ 此时不能用广播地址,因为广播是UDP独有的

​ 服务端可以将所有用户的Socket对象存储到一个集合中

​ 当需要群发消息时,可以遍历集合发给所有的用户

​ 此时的服务端,相当于做了一个消息的转发

转发核心思想如下图所示:

1

其他要求: ​

用户名和密码要求:

要求1:用户名要唯一,长度:6~18位,纯字母,不能有数字或其他符号。

要求2:密码长度3~8位。第一位必须是小写或者大小的字母,后面必须是纯数字。

客户端:

拥有登录、注册、聊天功能。

① 当客户端启动之后,要求让用户选择是登录操作还是注册操作,需要循环。

  • 如果是登录操作,就输入用户名和密码,以下面的格式发送给服务端

    username=zhangsan&password=123

  • 如果是注册操作,就输入用户名和密码,以下面的格式发送给服务端

    username=zhangsan&password=123

② 登录成功之后,直接开始聊天。

​

服务端:

① 先读取本地文件中所有的正确用户信息。

② 当有客户端来链接的时候,就开启一条线程。

③ 在线程里面判断当前用户是登录操作还是注册操作。

④ 登录,校验用户名和密码是否正确

⑤ 注册,校验用户名是否唯一,校验用户名和密码的格式是否正确

⑥ 如果登录成功,开始聊天

⑦ 如果注册成功,将用户信息写到本地,开始聊天

(2)代码实现 ​

Client ​

java
package com.itheima.client;

import java.io.*;
import java.net.Socket;
import java.util.Scanner;

public class Client {
    public static void main(String[] args) throws IOException {
        Socket socket = new Socket("127.0.0.1", 10001);
        System.out.println("服务器已经连接成功");


        while (true) {
            System.out.println("==============欢迎来到黑马聊天室================");
            System.out.println("1登录");
            System.out.println("2注册");
            System.out.println("请输入您的选择:");
            Scanner sc = new Scanner(System.in);
            String choose = sc.nextLine();
            switch (choose) {
                case "1" -> login(socket);
                case "2" -> System.out.println("用户选择了注册");
                default -> System.out.println("没有这个选项");
            }
        }
    }

    public static void login(Socket socket) throws IOException {
        //获取输出流
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
        //键盘录入
        Scanner sc = new Scanner(System.in);
        System.out.println("请输入用户名");
        String username = sc.nextLine();
        System.out.println("请输入密码");
        String password = sc.nextLine();

        //拼接
        StringBuilder sb = new StringBuilder();
        //username=zhangsan&password=123
        sb.append("username=").append(username).append("&password=").append(password);

        //第一次写的是执行登录操作
        bw.write("login");
        bw.newLine();
        bw.flush();

        //第二次写的是用户名和密码的信息
        //往服务器写出用户名和密码
        bw.write(sb.toString());
        bw.newLine();
        bw.flush();

        //接收数据
        //获取输入流
        BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        String message = br.readLine();
        System.out.println(message);
        //1:登录成功  2 密码有误   3 用户名不存在
        if ("1".equals(message)) {
            System.out.println("登录成功,开始聊天");
            //开一条单独的线程,专门用来接收服务端发送过来的聊天记录
            new Thread(new ClientMyRunnable(socket)).start();
            //开始聊天
            talk2All(bw);
        } else if ("2".equals(message)) {
            System.out.println("密码输入错误");
        } else if ("3".equals(message)) {
            System.out.println("用户名不存在");
        }

    }

    //往服务器写出消息
    private static void talk2All(BufferedWriter bw) throws IOException {
        Scanner sc = new Scanner(System.in);
        while (true) {
            System.out.println("请输入您要说的话");
            String str = sc.nextLine();
            //把聊天内容写给服务器
            bw.write(str);
            bw.newLine();
            bw.flush();
        }
    }
}

ClientMyRunable ​

java
package com.itheima.client;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;

class ClientMyRunnable implements Runnable{
    Socket socket;
    public ClientMyRunnable(Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run() {

        //循环,重复的接受
        while (true) {
            try {
                //接收服务器发送过来的聊天记录
                BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                String msg = br.readLine();
                System.out.println(msg);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

Server ​

java
package com.itheima.server;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Properties;

public class Server {

    static ArrayList<Socket> list = new ArrayList<>();

    public static void main(String[] args) throws IOException {
        ServerSocket ss = new ServerSocket(10001);

        //1.把本地文件中正确的用户名和密码获取到
        Properties prop = new Properties();
        FileInputStream fis = new FileInputStream("sockethomework\\servicedir\\userinfo.txt");
        prop.load(fis);
        fis.close();

        //2.只要来了一个客户端,就开一条线程处理
        while (true) {
            Socket socket = ss.accept();
            System.out.println("有客户端来链接");
            new Thread(new MyRunnable(socket, prop)).start();
        }
    }
}

MyRunable ​


java
package com.itheima.server;

import java.io.*;
import java.net.Socket;
import java.util.Properties;

class MyRunnable implements Runnable {
    Socket socket;
    Properties prop;

    public MyRunnable(Socket socket, Properties prop) {
        this.prop = prop;
        this.socket = socket;
    }

    @Override
    public void run() {
        try {
            BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));

            while (true) {
                String choose = br.readLine();
                switch (choose) {
                    case "login" -> login(br);
                    case "register" -> System.out.println("用户选择了注册操作");
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    //获取用户登录时,传递过来的信息。
    //并进行判断
    public void login(BufferedReader br) throws IOException {
        System.out.println("用户选择了登录操作");
        String userinfo = br.readLine();
        //username=zhangsan&password=123
        String[] userInfoArr = userinfo.split("&");
        String usernameInput = userInfoArr[0].split("=")[1];
        String passwordInput = userInfoArr[1].split("=")[1];
        System.out.println("用户输入的用户名为:" + usernameInput);
        System.out.println("用户输入的密码为:" + passwordInput);

        if (prop.containsKey(usernameInput)) {
            //如果用户名存在,继续判断密码
            String rightPassword = prop.get(usernameInput) + "";
            if (rightPassword.equals(passwordInput)) {
                //提示用户登录成功,可以开始聊天
                writeMessage2Client("1");
                //登录成功的时候,就需要把客户端的连接对象Socket保存起来
                Server.list.add(socket);
                //写一个while(){}表示正在聊天
                //接收客户端发送过来的消息,并打印在控制台
                talk2All(br, usernameInput);
            } else {
                //密码输入有误
                writeMessage2Client("2");
            }
        } else {
            //如果用户名不存在,直接回写
            writeMessage2Client("3");
        }
    }

    private void talk2All(BufferedReader br, String username) throws IOException {
        while (true) {
            String message = br.readLine();
            System.out.println(username + "发送过来消息:" + message);

            //群发
            for (Socket s : Server.list) {
                //s依次表示每一个客户端的连接对象
                writeMessage2Client(s, username + "发送过来消息:" + message);
            }
        }
    }

    public void writeMessage2Client(String message) throws IOException {
        //获取输出流
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
        bw.write(message);
        bw.newLine();
        bw.flush();
    }

    public void writeMessage2Client(Socket s, String message) throws IOException {
        //获取输出流
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
        bw.write(message);
        bw.newLine();
        bw.flush();
    }
}

最后更新于:

Pager
上一篇 异常
下一篇 注解

Released under the MIT License.

Copyright © 2025 渝ICP备2025054279号