网络编程 
更新: 2025/4/9 字数: 0 字 时长: 0 分钟
一、初始网络编程 
更新: 2025/4/9 字数: 0 字 时长: 0 分钟
1、概念 

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



(1)BS架构-优缺点 

(2)CS架构-优缺点 

3、小结 

二、网络编程三要素 
更新: 2025/4/9 字数: 0 字 时长: 0 分钟


1、📌==IP== 
(1)概念 

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

IPv4(Internet Protocol version 4)
采用 32 位地址,由四组十进制数字(0~255)组成,每组 8 位(1 个字节),例如:
192.168.1.1地址数量:IPv4 理论上能提供 约 42 亿 个 $$ 2^{32} $$ 地址,但由于地址分配不均等问题,可用地址远少于此。
特点:
- 由于地址资源紧缺,使用 NAT(网络地址转换)等技术延缓了 IPv4 地址枯竭问题。
 - 广泛使用于现有网络设备。
 
①IPv4的地址分类形式-局域网 


(3)IPv6 
IPv6(Internet Protocol version 6)
采用 128 位地址,由 8 组 16 进制数(每组 16 位,2 个字节)组成,例如:
makefile2001:0db8:85a3:0000:0000:8a2e:0370:7334地址数量:IPv6 理论上能提供 约 2的128次方 个地址,几乎可以给地球上的每一粒沙子都分配一个 IP。
特点:
- 解决 IPv4 地址枯竭问题。
 - 提供更高的安全性(内置 IPsec)。
 - 去除了 NAT,支持端到端通信。
 - 在 5G 时代及物联网(IoT)设备中应用越来越广泛。
 


(4)局域网-LAN 
局域网(LAN, Local Area Network)
局域网(LAN)是指在有限的地理范围内(如家庭、公司、学校)连接的计算机网络。它主要用于设备之间的通信,而不需要连接到广域网(互联网)。
特点:
- 设备之间数据传输速率较快(通常 100Mbps ~ 10Gbps)。
 - 主要使用 私有 IP 地址(如 192.168.1.0/24)。
 - 通过路由器或交换机连接多个设备。
 
常见局域网 IP 地址范围(私有 IP 地址段):
| 地址范围 | 子网掩码 | 
|---|---|
| 10.0.0.0 ~ 10.255.255.255 | 255.0.0.0 | 
| 172.16.0.0 ~ 172.31.255.255 | 255.240.0.0 | 
| 192.168.0.0 ~ 192.168.255.255 | 255.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 



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

(10)小结 


(11)InetAddress类的使用 

①示例代码 
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:如 
 
组成:
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。 
 - IP地址:
 
 - 例子: 
 
 - 通过 IP地址 和 子网掩码 可以计算网段范围。 
 - 特点: 
- 网段内设备共享同一个网络号。
 - 广播地址是网段的最后一个地址(如 
192.168.1.255)。 
 
3. 网关 
- 定义: 
- 网关是一个设备(通常是路由器),充当不同网段之间通信的桥梁。
 - 它是一个网络的默认出口,连接到外部网络(例如互联网)。
 
 - 特点: 
- 网关的IP地址通常是一个网段内的首地址或末地址(如 
192.168.1.1)。 - 如果目标地址不在当前网段,设备会将数据包发送到网关,由网关转发到目标网段。
 
 - 网关的IP地址通常是一个网段内的首地址或末地址(如 
 - 配置: 
- 设备必须配置网关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、端口号 


3、📌==协议== 

(1)OSI参考模型 

(2)TCP/IP参考模型 

(3)UDP协议 
①概念 

②UDP通信程序-发送数据 

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通信程序-接收数据 

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();
    }
}结果

④UDP练习-聊天室 

示例代码-发送数据
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(); 
    }
}示例代码-接收数据
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));
        }
    }
}测试结果


(4)UDP的三种通讯方式 

①单播-DatagramSocket 

代码实现 

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 

代码实现 

发送端
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
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
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 

代码实现 

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协议 
①概念 

②TCP通信程序 


1)Socket-客户端发送数据 
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-服务器接收数据 
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-服务器接收数据时,使用转换流,将字符流转化为字符流
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通信的细节 

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

1.为什么 TCP 需要三次握手? 
TCP(Transmission Control Protocol)是面向连接的协议,三次握手(Three-Way Handshake)是 TCP 建立可靠连接的关键步骤,主要目的是确保通信双方能够可靠地发送和接收数据,并同步状态信息。
2.三次握手的具体过程 
- 第一次握手(SYN)
- 客户端发送一个 SYN(同步)报文到服务器,表示请求建立连接,同时指明客户端的初始序列号 
Seq = X。 - 此时,客户端进入 SYN_SENT 状态,等待服务器响应。
 
 - 客户端发送一个 SYN(同步)报文到服务器,表示请求建立连接,同时指明客户端的初始序列号 
 - 第二次握手(SYN + ACK)
- 服务器收到 SYN 报文后,向客户端返回一个 SYN + ACK 报文,表示同意建立连接。
 - 服务器在这个报文中包含自己的初始序列号 
Seq = Y,同时确认客户端的序列号Ack = X + 1。 - 此时,服务器进入 SYN_RCVD 状态。
 
 - 第三次握手(ACK)
- 客户端收到 SYN + ACK 后,再发送一个 ACK 报文,表示收到并确认服务器的序列号 
Ack = Y + 1。 - 同时,客户端进入 ESTABLISHED 状态,表示连接已建立。
 - 服务器收到 ACK 后,也进入 ESTABLISHED 状态。
 
 - 客户端收到 SYN + ACK 后,再发送一个 ACK 报文,表示收到并确认服务器的序列号 
 
3.为什么需要三次,而不是两次或更多? 
- 一次握手不足:缺少确认机制 如果只有一次握手,客户端发送了 SYN 报文,但无法确认服务器是否收到该请求,也无法确保服务器是否已做好接收数据的准备。
 - 两次握手不足:存在假连接风险 假设使用两次握手: 
- 客户端发送 SYN 报文,服务器返回 SYN + ACK,此时服务器认为连接已经建立。
 - 如果客户端的初始 SYN 报文是由于网络延迟导致的陈旧数据包(而客户端并未真正请求连接),服务器会错误地认为连接已建立并占用资源(产生半开连接)。
 - 使用三次握手时,只有客户端对服务器的 SYN + ACK 再次确认后,连接才真正建立,可以避免这种问题。
 
 - 三次握手足够:确保双方同步通信能力
- 客户端和服务器通过三次交互,双方都能确认对方具备发送和接收数据的能力,并同步初始序列号,保证通信的可靠性。
 
 - 四次或更多:无必要
- 三次握手已经能确保通信双方可靠连接,更多步骤会增加额外的通信开销,影响效率。
 
 
4.总结 
TCP 三次握手的设计是在可靠性与效率之间的权衡:
- 通过三次交互,确保通信双方具备正常通信能力,避免陈旧连接的问题。
 - 减少不必要的握手步骤,提高连接建立的效率。
 
这是 TCP 保证数据传输可靠性的核心机制之一。
②四次挥手 

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. 为什么是四次,而不是三次或更多? 
- 不能三次挥手的原因
- 服务器可能还有数据要发送,所以第二次挥手时,不能立刻发送 
FIN,否则数据可能丢失。 - 服务器需要等到数据发送完毕后,才能发送 
FIN,所以需要分开两步。 
 - 服务器可能还有数据要发送,所以第二次挥手时,不能立刻发送 
 - 为什么不需要更多次?
- 四次刚好保证了双方都能确认数据传输已经结束。
 ACK只是简单的确认,不需要单独的ACK确认ACK,否则会无限增加握手次数。
 
5. 总结 
✅ 四次挥手用于安全可靠地关闭 TCP 连接,确保数据完整性。 ✅ 客户端和服务器都需要各自确认“发送完毕”和“接收完毕”,因此需要四次交互。 ✅ 客户端 TIME_WAIT 状态防止服务器未收到 ACK,保证连接完全关闭。 ✅ 相比三次挥手,四次挥手确保了服务器能在发送完所有数据后再断开连接。
📌 重点记忆:四次挥手 = 两个方向的 FIN + ACK,保证双方都正确关闭连接! 🚀
4、其他协议 
(1)什么是 FTP? 
📌 是什么? 
FTP(File Transfer Protocol,文件传输协议)是一种用于在网络上进行文件传输的协议,基于 TCP/IP 协议工作,支持上传、下载、删除、重命名等文件操作。
📌 为什么需要? 
- 早期互联网需要一个标准的方式在计算机之间传输文件,FTP 解决了这个问题。
 - 支持用户身份认证,可进行权限管理。
 - 适用于大文件传输,支持断点续传。
 
📌 具体过程 
FTP 采用 客户端-服务器(C/S)模式,使用两个端口进行通信:
- 控制连接(端口 21):用于发送命令,如 
USER(登录)、LIST(列出文件)、RETR(下载文件)。 - 数据连接(端口 20):用于传输文件数据。
 
两种模式:
- 主动模式(PORT):服务器主动连接客户端的数据端口。
 - 被动模式(PASV):客户端请求服务器提供可用端口,客户端再连接该端口。
 
📌 使用场景 
- 服务器之间大文件传输。
 - 网站部署时上传代码、下载日志。
 - 远程存储备份数据。
 
📌 总结 
✅ FTP 是一种文件传输协议,使用 TCP 端口 21 控制连接,端口 20 传输数据。 ✅ 主动模式和被动模式两种连接方式,保证数据顺利传输。 ✅ 适用于文件服务器、大文件共享、网站管理等场景。
(2)什么是 Telnet? 
📌 是什么? 
Telnet(TELecommunication NETwork,远程登录协议)是一种基于 TCP 的协议,用于远程管理和控制计算机。
📌 为什么需要? 
- 允许用户在本地设备上远程登录到服务器并执行命令。
 - 适用于远程管理设备,如服务器、交换机、路由器。
 - 但 Telnet 传输的数据(包括密码)是明文的,不安全,常被 SSH 取代。
 
📌 具体过程 
- 客户端发送连接请求,服务器监听端口 23,建立 TCP 连接。
 - 服务器验证用户身份(用户名+密码)。
 - 成功登录后,用户可以像在本地一样操作远程计算机。
 - 退出登录,关闭连接。
 
📌 使用场景 
- 远程管理 Linux/Unix 服务器(现多用 SSH)。
 - 测试网络连通性(如 
telnet IP 端口)。 - 配置网络设备(交换机、路由器)。
 
📌 总结 
✅ Telnet 是一种远程登录协议,使用 端口 23,可在远程服务器上执行命令。 ✅ 但不安全(明文传输),现在已被 SSH(Secure Shell)取代。 ✅ 仍可用于调试网络连接,但不建议用于正式环境。
(3)什么是 DNS? 
📌 是什么? 
DNS(Domain Name System,域名系统)是用于将域名解析为 IP 地址的系统,类似于互联网的“电话簿”。
📌 为什么需要? 
- 方便用户访问网站,不用记住复杂的 IP 地址(如 
8.8.8.8)。 - 允许域名与 IP 地址动态映射,提高灵活性。
 - 支持负载均衡,分配流量到不同服务器。
 
📌 具体过程 
- 用户输入 
www.example.com。 - 本地 DNS 服务器查询缓存,若无,则向根 DNS 服务器请求解析。
 - 逐级查询 
.com顶级域名服务器(TLD)、权威 DNS 服务器,最终返回93.184.216.34(IP 地址)。 - 用户设备使用该 IP 访问目标服务器。
 
📌 使用场景 
- 网站访问(浏览器输入网址)。
 - CDN 加速(动态解析到最近的服务器)。
 - 邮件服务器查找(MX 记录)。
 
📌 总结 
✅ DNS 解析域名到 IP,简化网络访问。 ✅ 采用分层架构(根 DNS、TLD、权威 DNS),提高查询效率。 ✅ CDN、负载均衡、邮件解析等都依赖 DNS。
(4)什么是 ICMP? 
📌 是什么? 
ICMP(Internet Control Message Protocol,互联网控制报文协议)是用于发送网络错误报告和状态信息的协议,通常用于检测网络状态(如 ping)。
📌 为什么需要? 
- 用于网络故障排查(检测连通性、故障原因)。
 - 通知网络错误(如主机不可达、超时)。
 - 提供网络调试工具,如 ping、traceroute。
 
📌 具体过程(以 ping 为例) 
- 客户端发送 ICMP 请求(Echo Request)。
 - 目标主机收到后返回 ICMP 响应(Echo Reply)。
 - 客户端计算 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 映射问题,确保数据能正确送达。
 
📌 具体过程 
- 主机 A 发送 ARP 请求(广播),询问“
192.168.1.2的 MAC 地址是多少?” - 主机 B(目标 IP)回复 ARP 响应,提供其 MAC 地址。
 - 主机 A 记录 B 的 MAC 地址,存入 ARP 缓存,加速后续通信。
 
📌 使用场景 
- 局域网通信(如 PC 访问网关)。
 - 交换机、路由器设备自动解析 MAC。
 - 网络攻击(ARP 欺骗,用于中间人攻击 MITM)。
 
📌 总结 
✅ ARP 解析 IP 地址到 MAC 地址,用于局域网通信。 ✅ 基于广播请求-单播响应机制,提高效率。 ✅ 可能被滥用(ARP 欺骗),可用 ARP 绑定防范攻击。 🚀
三、综合练习 
更新: 2025/4/9 字数: 0 字 时长: 0 分钟
1、TCP通信练习1-多发多收 

Client
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
客户端没有关闭,也就是没有断开连接,所以服务端也不会断开,四次挥手不会触发,服务器就可以一直接收数据
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-接收并反馈 

Client
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
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-上传文件 

Client
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
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-上传文件(文件名重复问题) 

(1)UUID类 


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
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
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-上传文件(多线程版) 

自定义线程
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
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
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-上传文件(线程池优化) 

自定义线程
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
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
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(接收浏览器的消息) 

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();
    }
}
8、TCP通信练习8-聊天室 

(1)完整需求 
项目名称 
 利用TCP协议,做一个带有登录,注册的无界面,控制版的多人聊天室。
使用到的知识点 
 循环,判断,集合,IO,多线程,网络编程等
准备工作 
在当前模块下新建txt文件,文件中保存正确的用户名和密码
文件内容如下:
//左边是用户名
//右边是密码
zhangsan=123
lisi=1234
wangwu=12345需求描述 
① 客户端启动之后,需要连接服务端,并出现以下提示:
服务器已经连接成功
==============欢迎来到黑马聊天室================
1登录
2注册
请输入您的选择:②选择登录之后,出现以下提示:
服务器已经连接成功
==============欢迎来到黑马聊天室================
1登录
2注册
请输入您的选择:
1
请输入用户名③需要输入用户名和密码,输入完毕,没有按回车时,效果如下:
服务器已经连接成功
==============欢迎来到黑马聊天室================
1登录
2注册
请输入您的选择:
1
请输入用户名
zhangsan
请输入密码
123④按下回车,提交给服务器验证
服务器会结合txt文件中的用户名和密码进行判断
根据不同情况,服务器回写三种判断提示:
服务器回写第一种提示:登录成功
服务器回写第二种提示:密码有误  
服务器回写第三种提示:用户名不存在⑤客户端接收服务端回写的数据,根据三种情况进行不同的处理方案
登录成功的情况, 可以开始聊天,出现以下提示:
服务器已经连接成功
==============欢迎来到黑马聊天室================
1登录
2注册
请输入您的选择:
1
请输入用户名
zhangsan
请输入密码
123
1
登录成功,开始聊天
请输入您要说的话密码错误的情况,需要重新输入,出现以下提示:
服务器已经连接成功
==============欢迎来到黑马聊天室================
1登录
2注册
请输入您的选择:
1
请输入用户名
zhangsan
请输入密码
aaa
密码输入错误
==============欢迎来到黑马聊天室================
1登录
2注册
请输入您的选择:用户名不存在的情况,需要重新输入,出现以下提示:
服务器已经连接成功
==============欢迎来到黑马聊天室================
1登录
2注册
请输入您的选择:
1
请输入用户名
zhaoliu
请输入密码
123456
用户名不存在
==============欢迎来到黑马聊天室================
1登录
2注册
请输入您的选择:⑥如果成功登录,就可以开始聊天,此时的聊天是群聊,一个人发消息给服务端,服务端接收到之后需要群发给所有人
提示:
 此时不能用广播地址,因为广播是UDP独有的
 服务端可以将所有用户的Socket对象存储到一个集合中
 当需要群发消息时,可以遍历集合发给所有的用户
 此时的服务端,相当于做了一个消息的转发
转发核心思想如下图所示:

其他要求: 
用户名和密码要求:
要求1:用户名要唯一,长度:6~18位,纯字母,不能有数字或其他符号。
要求2:密码长度3~8位。第一位必须是小写或者大小的字母,后面必须是纯数字。
客户端:
拥有登录、注册、聊天功能。
① 当客户端启动之后,要求让用户选择是登录操作还是注册操作,需要循环。
如果是登录操作,就输入用户名和密码,以下面的格式发送给服务端
username=zhangsan&password=123
如果是注册操作,就输入用户名和密码,以下面的格式发送给服务端
username=zhangsan&password=123
② 登录成功之后,直接开始聊天。
服务端:
① 先读取本地文件中所有的正确用户信息。
② 当有客户端来链接的时候,就开启一条线程。
③ 在线程里面判断当前用户是登录操作还是注册操作。
④ 登录,校验用户名和密码是否正确
⑤ 注册,校验用户名是否唯一,校验用户名和密码的格式是否正确
⑥ 如果登录成功,开始聊天
⑦ 如果注册成功,将用户信息写到本地,开始聊天
(2)代码实现 
Client 
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 
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 
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 
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();
    }
}