网站Logo 苏叶的belog

java网络编程

wdadwa
3
2025-12-20

一,网络编程基础

网络模型

  • OSI 七层模型

    层级名称功能例子
    7应用层用户接口,应用程序访问网络服务HTTP、FTP、SMTP
    6表示层数据格式转换、加密解密JPEG、ASCII、加密
    5会话层建立、管理、终止会话RPC、NetBIOS
    4传输层端到端连接,可靠传输TCP、UDP
    3网络层逻辑寻址,路由选择IP、ICMP、路由器
    2数据链路层物理寻址,帧传输MAC地址、交换机
    1物理层物理介质,比特流传输网线、光纤、集线器
  • TCP/IP 四层模型

    层级名称对应OSI层协议
    4应用层应用层+表示层+会话层HTTP、FTP、DNS、SMTP
    3传输层传输层TCP、UDP
    2网络层网络层IP、ICMP、ARP
    1网络接口层数据链路层+物理层Ethernet、WiFi
  • Java 主要工作在传输层(TCP/UDP)和应用层

核心包

import java.net.*;     // 主要网络类
import java.io.*;      // IO流支持

网络编程就是在网络通信协议下,不同计算机上运行的程序,进行的数据传送,本质就是不同计算机之间通过网络进行数据传送。

常见的软件架构:

  1. B/S 架构:Browser/Server,即浏览器 / 服务器的架构

    只需要一个浏览器,用户通过不同的网址,客户访问不同的服务器。

    优点:

    1. 不需要开发客户端,只需要页面+服务的
    2. 用户不需要下载,打开浏览器即可使用

    缺点:如果应用过大,用户体验会受到影响

  2. C/S 架构:Client/Server,即客户端 / 服务的架构

    在用户本地需要下载并安装客户端程序,在远程有一个服务器端程序。

    优点:画面可做的非常精美,用户体验很好

    缺点: 需要开发客户端,也需要开发服务的,维护开发部署很麻烦,用户需要下载和更新的时候很麻烦。

网络编程三要素:ip,端口,协议

常用框架

  1. Netty - 高性能NIO框架
  2. Apache HttpClient - HTTP客户端库
  3. OkHttp - 现代HTTP客户端
  4. Spring WebFlux - 响应式Web框架

二,IP地址核心类

2.1 InetAddress基本使用

import java.net.InetAddress;

// 获取本地主机
InetAddress local = InetAddress.getLocalHost();
System.out.println("本地主机: " + local);
// 输出: 本地主机: DESKTOP-ABC/192.168.1.100

// 通过域名获取
InetAddress google = InetAddress.getByName("www.google.com");
System.out.println("Google IP: " + google.getHostAddress());
// 输出: Google IP: 142.250.74.68

// 获取所有IP(可能有多个)
InetAddress[] allIps = InetAddress.getAllByName("www.google.com");
for (InetAddress ip : allIps) {
    System.out.println(ip.getHostAddress());
}

2.2 InetAddress常用方法

InetAddress address = InetAddress.getByName("192.168.1.1");

// 基本信息
String ip = address.getHostAddress();      // "192.168.1.1"
String hostname = address.getHostName();   // 主机名或IP字符串
String canonicalName = address.getCanonicalHostName(); // 规范主机名

// 类型判断
boolean isLoopback = address.isLoopbackAddress();  // 是否是回环地址(127.x.x.x)
boolean isSiteLocal = address.isSiteLocalAddress(); // 是否是内网地址(192.168.x.x, 10.x.x.x, 172.16-31.x.x)
boolean isLinkLocal = address.isLinkLocalAddress(); // 是否是链路本地地址(169.254.x.x)
boolean isAnyLocal = address.isAnyLocalAddress();   // 通配地址(0.0.0.0)
boolean isMulticast = address.isMulticastAddress(); // 是否是组播地址

// 可达性测试(ping)
boolean reachable = address.isReachable(5000);  // 5秒超时

2.3 Inet4Address - IPv4专门类

// 实际上你很少需要直接创建它
Inet4Address ipv4 = (Inet4Address) InetAddress.getByName("192.168.1.1");

// 判断是否是IPv4
if (address instanceof Inet4Address) {
    System.out.println("这是IPv4地址");
}

2.4 Inet6Address - IPv6专门类

Inet6Address ipv6 = (Inet6Address) InetAddress.getByName("2001:0db8:85a3:0000:0000:8a2e:0370:7334");

// IPv6特有方法
byte[] ipv6Bytes = ipv6.getAddress();  // 16字节数组
boolean isIPv4Compatible = ipv6.isIPv4CompatibleAddress(); // 是否IPv4兼容
String scopeId = ipv6.getScopedInterface().getName(); // 作用域ID

2.5 InetSocketAddress - IP+端口组合

用于 Socket 编程,包含 IP 地址和端口号

// 创建方式
InetSocketAddress socketAddr1 = new InetSocketAddress("localhost", 8080);
InetSocketAddress socketAddr2 = new InetSocketAddress(InetAddress.getLocalHost(), 8080);
InetSocketAddress socketAddr3 = new InetSocketAddress(8080);  // 通配地址: 0.0.0.0:8080

// 获取信息
String hostname = socketAddr1.getHostName();    // 解析主机名
String hostString = socketAddr1.getHostString(); // 原始字符串
InetAddress address = socketAddr1.getAddress();  // InetAddress对象
int port = socketAddr1.getPort();                // 端口号
boolean unresolved = socketAddr1.isUnresolved(); // 是否未解析

// 用在Socket中
ServerSocket server = new ServerSocket();
server.bind(socketAddr1);  // 绑定到指定地址和端口

2.6 NetworkInterface - 网络接口类

代表网卡、网络接口

import java.net.NetworkInterface;

// 获取所有网络接口
Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
while (interfaces.hasMoreElements()) {
    NetworkInterface ni = interfaces.nextElement();
    
    System.out.println("接口名称: " + ni.getName());           // eth0, wlan0
    System.out.println("显示名称: " + ni.getDisplayName());   // 以太网适配器, WiFi
    System.out.println("是否启动: " + ni.isUp());             // true/false
    System.out.println("是否回环: " + ni.isLoopback());       // 是否是回环接口
    System.out.println("是否虚拟: " + ni.isVirtual());        // 是否是虚拟接口
    System.out.println("MTU: " + ni.getMTU());               // 最大传输单元
    
    // 获取MAC地址
    byte[] mac = ni.getHardwareAddress();
    if (mac != null) {
        System.out.print("MAC地址: ");
        for (byte b : mac) {
            System.out.printf("%02X:", b);
        }
        System.out.println();
    }
    
    // 获取绑定的IP地址
    Enumeration<InetAddress> inetAddresses = ni.getInetAddresses();
    while (inetAddresses.hasMoreElements()) {
        InetAddress addr = inetAddresses.nextElement();
        System.out.println("  IP: " + addr.getHostAddress());
    }
    
    System.out.println("---");
}

// 按名称获取特定接口
NetworkInterface eth0 = NetworkInterface.getByName("eth0");
NetworkInterface byIp = NetworkInterface.getByInetAddress(
    InetAddress.getByName("192.168.1.100")
);

2.7 总结

类名用途常用场景
InetAddressIP地址表示所有IP相关操作
Inet4AddressIPv4地址明确IPv4处理
Inet6AddressIPv6地址IPv6网络
InetSocketAddressIP+端口Socket绑定、连接
NetworkInterface网络接口多网卡选择、MAC获取

三,常用协议

3.1 UDP协议

  • UDP 协议:用户数据报协议(User Datagram Protocol)

  • UDP 是面向无连接通信协议

    速度快,有大小限制一次最多 64k,数据不安全,易丢失

面向无连接:数据直接发送,不确认是否连接成功。

应用场景:在线视频,语音通话,网络会议。

特点

  • 无连接:不用先握手
  • 不可靠:可能丢包、重复、乱序
  • 简单快速:适合实时应用
  • 支持广播/组播

3.2 TCP协议

  • TCP 协议:传输控制协议 TCP(Transmission Control Protocol)

  • TCP 协议是面向连接的通信协议

    速度慢,没有大小限制,数据安全。

面向连接:数据发送之前会确定是否连接成功再发送数据

应用场景:下载软件,文字聊天,发送邮件。

特点

  • 面向连接:三次握手建立连接
  • 可靠传输:保证不丢包、不重复、按顺序
  • 流量控制:防止发送过快
  • 拥塞控制:防止网络拥堵

3.3 HTTP协议

HTTP(超文本传输协议)

特点

  • 应用层协议:基于TCP
  • 无状态:每次请求独立
  • 明文传输:内容不加密
  • 请求-响应模型

HTTP 消息格式

# 请求
GET /index.html HTTP/1.1       ← 请求行(方法 + URI + 版本)
Host: www.example.com           ← 请求头(键值对)
User-Agent: Mozilla/5.0
Accept: text/html
(空行)                          ← 空行分隔
请求体(GET通常没有)            ← 请求数据

# 响应
HTTP/1.1 200 OK                ← 状态行(版本 + 状态码 + 描述)
Content-Type: text/html        ← 响应头
Content-Length: 1234
(空行)                          ← 空行分隔
<html>...</html>               ← 响应体

HTTP 方法

  • GET:获取资源(读)
  • POST:提交数据(写)
  • PUT:更新资源(全量更新)
  • DELETE:删除资源
  • PATCH:部分更新

HTTP 状态码

  • 1xx:信息提示(很少见)
  • 2xx:成功(200 OK,201 Created)
  • 3xx:重定向(301 永久,302 临时)
  • 4xx:客户端错误(404 找不到,403 禁止访问)
  • 5xx:服务器错误(500 内部错误,502 网关错误)

3.4 HTTPS协议

是什么?

  • HTTP + SSL/TLS加密
  • 端口443(HTTP是80)

工作原理

1. 客户端发起HTTPS请求
2. 服务器返回证书(包含公钥)
3. 客户端验证证书
4. 客户端生成随机密钥,用公钥加密后发给服务器
5. 服务器用私钥解密得到密钥
6. 双方用这个密钥对称加密通信

3.5 总结

特性UDPTCPHTTPHTTPS
连接无连接面向连接基于TCP基于TCP+SSL
可靠性不可靠可靠可靠可靠
速度中等最慢
头部开销8字节20字节不定不定+加密开销
数据顺序不保证保证保证保证
端口自定义自定义80443
适用场景实时媒体文件传输Web API安全Web API
Java类DatagramSocketSocket/ServerSocketHttpClientHttpClient+SSL

一句话区分

  • UDP:发微信(不管对方收没收到)
  • TCP:打电话(确保对方听到)
  • HTTP:快递寄信(有固定格式)
  • HTTPS:加密快递寄信(安全版)

四,UDP通信程序

  • 特点:无连接、不可靠通信。
  • 不事先建立连接;发送端每次把要发送的数据(限制在64KB内)、接收端IP、等信息封装成一个数据包,发出去就不管了。
  • Java提供了一个java.net.Datagramsocket类来实现UDP通信:

DatagramSocket:用于创建客户端、服务端

构造器说明
public DatagramSocket()创建客户端的Socket对象,系统会随机分配一个端口号
public DatagramSocket(int port)创建服务端的Socket对象,并指定端口号

常用方法:

方法说明
public void send(DatagramPacket dp)发送数据包
public void receive(DatagramPacket p)使用数据包接受数据

DatagramPacket:创建数据包

构造器说明
public DatagramPacket(byte[] buf,int length, InetAddress address, int port)创建发送出去的数据包对象
public DatagramPacket(byte[] buf,int length)创建用来接受数据的数据包

常用方法

方法说明
public int getLength()获取数据包,实际接收到的字节个数
public InetAddress getAddress()获取到发送方的ip对象
public int getPort()获取到发送方的端口

举例:

//接收方
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
public class ReceiveMessage {
    public static void main(String[] args) {
        //1.创建接受数据的服务端
        try(DatagramSocket ds=new DatagramSocket(8888)){
            //2.创建接受数据的数据包
            byte[] bytes=new byte[1024];
            while(true){
                DatagramPacket dp=new DatagramPacket(bytes,bytes.length);
                ds.receive(dp);
                //3.将接收到的数据转换为字符串并输出
                //获取到发送方的ip对象
                InetAddress address = dp.getAddress();
                //获取到发送方的端口
                int port = dp.getPort();
                //4.输出接收到的数据
                System.out.println("从"+address+":"+port+"接收到的数据是:"+new String(dp.getData(),0,dp.getLength()));
            }
        }catch(Exception e){
            e.printStackTrace();
        }
    }
}

//发送方
import java.net.*;
import java.util.Scanner;
import static java.lang.Thread.sleep;
public class SendMessage {
    public static void main(String[] args) {
        //1.创建发送数据的客户端
        try(DatagramSocket ds=new DatagramSocket()){
            //2.创建服务端的InetAddress对象
            InetAddress byName = InetAddress.getByName("127.0.0.1");
            //3.创建要发送数据的数据包
            while(true){
                Scanner sc=new Scanner(System.in);
                String dataStr = sc.nextLine();
                //4.创建要发送给服务端的数据包
                DatagramPacket dp=new DatagramPacket(dataStr.getBytes(),dataStr.getBytes().length,byName,8888);
                //5.发送数据
                ds.send(dp);
            }
        }catch(Exception e){
            e.printStackTrace();
        }

    }
}

UDP 的三种通信方式

  1. 单播:发送端只给一台设备发送数据(一对一)

    上述写的代码就是单波形式

  2. 组播:发送端给一组设备发送数据(一对多)

    组波地址:224.0.0.0 到239.255.255.255,其中 224.0.0.0 到 224.0.0.255 为预留的组播地址

    我们只能用这部分预留的组播地址。

    组播和 IP 的区别是,ip 只能表示一台设备,组播可以表示多台设备。

  3. 广播:发送端给局域网所有的设备发送数据(一对所有)

    广播地址:255.255.255.255

组波代码实现:

组播和单波代码实现的区别:

  1. 发送端和接收端使用MulticastSocket对象来代替DatagramSocket
  2. 发送端使用组播地址代替ip地址
  3. 接收端需要额外的将当前本机加入指定组播地址当中
  • 发送数据

    package Text2;
    import java.io.IOException;
    import java.net.DatagramPacket;
    import java.net.InetAddress;
    import java.net.MulticastSocket;
    public class Send {
        public static void main(String[] args) throws IOException {
            //1.创建MulticastSocket对象
            //MulticastSocket和DatagramSocket使用方式一样
            MulticastSocket ms=new MulticastSocket(1010);
            //创建DatagramPacket对象
            String s="hello world";
            byte[] bytes = s.getBytes();
            InetAddress address = InetAddress.getByName("224.0.0.1");//指定的是组播地址
            int port=1000;
            DatagramPacket dp=new DatagramPacket(bytes,bytes.length,address,port);
            ms.send(dp);
            ms.close();
        }
    }
    
    
  • 接收数据

    package Text2;
    import javax.xml.crypto.Data;
    import java.net.DatagramPacket;
    import java.net.DatagramSocket;
    import java.net.InetAddress;
    import java.net.MulticastSocket;
    public class Receive {
        public static void main(String[] args) throws IOException {
            //创建MulticastSocket对象
            MulticastSocket ms=new MulticastSocket(1000);
            //将当前本机,添加到224.0.0.1这一组
            InetAddress address=InetAddress.getByName("224.0.0.1");
            ms.joinGroup(address);
            //创建DatagramPacket数据包对象
            byte[] bytes = new byte[1024];
            DatagramPacket dp=new DatagramPacket(bytes,bytes.length);
            //接收数据
            ms.receive(dp);
            //解析数据
            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,len));
            //释放资源
            ms.close();
        }
    }
    

** 广播的代码实现:** 只需要在单波的代码中发送端额发送地址修改为255.255.255.255即可实现广播

五,TCP通信程序

5.1 TCP的三次握手和四次挥手

  • 三次握手:目的是保证连接的建立

2953321-20230415220215305-615366090.png

  • 四次挥手:目的是确保连接端口,且通道内数据处理完毕。

2953321-20230415220225912-876760066.png

5.2 TCP通信快速入门

  • 特点:面向连接、可靠通信,
  • 通信双方事先会采用“三次握手”方式建立可靠连接,实现端到端的通信;底层能保证数据成功传给服务端
  • Java提供了一个java.net.Socket类来实现TCP通信。

2953321-20240606143622409-157441921.png

5.2.1 客户端

构造器说明
public Socket(String host,int port)根据指定的服务器ip,端口号请求与服务端建立连接,连接通过就获得了客户端socket

方法:

方法说明
public OutputStream getOutputStream()获得字节输出流对象
public InputStream getInputStream()获得字节输入流对象
public InetAddress getInetAddress()获取客户端的ip对象

代码实现:

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

public class Client {
    public static void main(String[] args) {
        //1.创建Socket对象,同时请求与服务器程序的连接
        try(Socket socket=new Socket("127.0.0.1",8888)){
            //2.从socket通道中获取输出流
            OutputStream outputStream = socket.getOutputStream();
            //3.包装
            DataOutputStream dataOutputStream = new DataOutputStream(outputStream);
            Scanner sc=new Scanner(System.in);
            //4.输入数据
            while(true){
                System.out.print("请输入内容:");
                String msg=sc.nextLine();
                if("exit".equals(msg)){
                    break;
                }
                dataOutputStream.writeUTF(msg);
                //刷新缓存
                dataOutputStream.flush();
            }
        }catch(Exception e){
            System.out.println("服务端访问失败!");
        }

    }
}

5.2.2 服务端

服务端是通过java.net包下的Serversocket类来实现的

ServerSocket:

构造器说明
public ServerSocket(int port)为服务端程序注册端口

常用方法:

方法说明
public Socket accept()阻塞等待客户端的连接请求,一旦与某个客户端成功连接,则返回服务端这边的Socket对象

代码:

import java.io.DataInputStream;
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) {
        System.out.println("服务器启动");
        //1.创建ServerSocket对象,同时为服务器注册端口
        try(ServerSocket serverSocket = new ServerSocket(8888)){
            //2.使用serverSocket调用accept方法获取到客户端Socket
            Socket accept = serverSocket.accept();
            //3.从客户端socket中获得输入流
            InputStream inputStream = accept.getInputStream();
            //4.包装成数据输入流
            DataInputStream ds = new DataInputStream(inputStream);
            //5.读取数据
            System.out.println("客户端:"+accept.getRemoteSocketAddress()+"上线!");
            while(true){
                try {
                    String msg = ds.readUTF();
                    System.out.println("客户端说:"+msg);
                } catch (Exception e) {
                    //捕获到异常代表客户端断开连接
                    System.out.println("客户端:"+accept.getRemoteSocketAddress()+"客户端断开连接!");
                    ds.close();
                    break;
                }
            }
        }catch(Exception e){
            e.printStackTrace();
        }finally{
            System.out.println("服务器关闭");
        }
    }
}

当我们客户端直接退出了,服务端就抛出异常

5.3 与多个客户端同时通信

上述快速入门案例只能实现了一对一通信,通过多线程来实现与多个客户端同时通信

** 代码实现:** 只需将服务端接受 socket 改成循环即然后配套使用多线程即可

import java.net.ServerSocket;
import java.net.Socket;

public class Server {
    public static void main(String[] args) {
        System.out.println("服务器启动");
        //1.创建ServerSocket对象,同时为服务器注册端口
        try(ServerSocket serverSocket = new ServerSocket(8888)){
            //2.使用循环来接受客户端请求
            while(true){
                Socket accept = serverSocket.accept();
                ServerReadThread serverReadThread = new ServerReadThread(accept);
                serverReadThread.start();
            }
        }catch(Exception e){
            e.printStackTrace();
        }finally{
            System.out.println("服务器关闭");
        }
    }
}
import java.io.DataInputStream;
import java.io.InputStream;
import java.net.Socket;
public class ServerReadThread extends Thread{
    private Socket accept;

    public ServerReadThread(Socket accept) {
        this.accept = accept;
    }

    @Override
    public void run() {
        try{
            //1.从客户端socket中获得输入流
            InputStream inputStream = accept.getInputStream();
            //2.包装成数据输入流
            DataInputStream ds = new DataInputStream(inputStream);
            //3.读取数据
            System.out.println("客户端:"+accept.getRemoteSocketAddress()+"上线!");
            while(true){
                try {
                    String msg = ds.readUTF();
                    System.out.println("客户端说:"+msg);
                } catch (Exception e) {
                    //捕获到异常代表客户端断开连接
                    System.out.println("客户端:"+accept.getRemoteSocketAddress()+"客户端断开连接!");
                    ds.close();
                    break;
                }
            }
        }catch(Exception e){
            e.printStackTrace();
        }
    }
}

5.4 实现群聊

2953321-20240606161520183-716155539.png

代码实现:

服务端:

import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;

public class Server {
    //代表所有在线的客户端socket
    public static List<Socket> onlineSocketList=new ArrayList<Socket>();
    public static void main(String[] args) {
        System.out.println("服务器启动");
        //1.创建ServerSocket对象,同时为服务器注册端口
        try(ServerSocket serverSocket = new ServerSocket(8888)){
            //2.使用循环来接受客户端请求
            while(true){
                Socket accept = serverSocket.accept();
                onlineSocketList.add(accept);
                ServerReadThread serverReadThread = new ServerReadThread(accept);
                serverReadThread.start();
            }
        }catch(Exception e){
            e.printStackTrace();
        }finally{
            System.out.println("服务器关闭");
        }
    }
}
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.net.SocketAddress;

public class ServerReadThread extends Thread{

    private Socket accept;

    public ServerReadThread(Socket accept) {
        this.accept = accept;
    }

    @Override
    public void run() {
        try{
            //1.从客户端socket中获得输入流
            InputStream inputStream = accept.getInputStream();
            //2.包装成数据输入流
            DataInputStream ds = new DataInputStream(inputStream);
            //3.读取数据
            System.out.println("客户端:"+accept.getRemoteSocketAddress()+"上线!");
            while(true){
                try {
                    String msg = ds.readUTF();
                    sendMsgAll(msg);
                    // System.out.println("客户端说:"+msg);
                } catch (Exception e) {
                    //捕获到异常代表客户端断开连接
                    String msg = "客户端:"+accept.getRemoteSocketAddress()+"客户端断开连接!";
                    System.out.println(msg);
                    //从集合中删除掉该客户端
                    Server.onlineSocketList.remove(accept);
                    ds.close();
                    break;
                }
            }
        }catch(Exception e){
            e.printStackTrace();
        }
    }

    /**
     * 把消息发送给所有在线客户端
     * @param msg
     */
    private void sendMsgAll(String msg) {
        for (Socket socket : Server.onlineSocketList) {
            try {
                //1.从客户端socket中获得输出流
                OutputStream outputStream = socket.getOutputStream();
                //2.包装成数据输出流
                DataOutputStream ds = new DataOutputStream(outputStream);
                //3.发送数据
                ds.writeUTF(msg);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

客户端:

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

public class Client {
    public static void main(String[] args) {
        //1.创建Socket对象,同时请求与服务器程序的连接
        try(Socket socket=new Socket("127.0.0.1",8888)){
            //2.从socket通道中获取输出流
            OutputStream outputStream = socket.getOutputStream();
            //3.包装
            DataOutputStream dataOutputStream = new DataOutputStream(outputStream);
            Scanner sc=new Scanner(System.in);

            //创建一个独立的线程随时从服务端接受消息
            new ClientReadThread(socket).start();

            //4.输入数据
            while(true){
                String msg=sc.nextLine();
                if("exit".equals(msg)){
                    break;
                }
                dataOutputStream.writeUTF(msg);
                //刷新缓存
                dataOutputStream.flush();
            }
        }catch(Exception e){
            System.out.println("服务端访问失败!");
        }

    }
}
import java.io.DataInputStream;
import java.io.InputStream;
import java.net.Socket;

public class ClientReadThread extends Thread{
    private Socket socket;
    public ClientReadThread(Socket socket) {
        this.socket = socket;
    }
    @Override
    public void run() {
        try{
            //1.从客户端socket中获得输入流
            InputStream inputStream = socket.getInputStream();
            //2.包装成数据输入流
            DataInputStream ds = new DataInputStream(inputStream);
            //3.读取数据
            while(true){
                try {
                    String msg = ds.readUTF();
                    System.out.println(msg);
                } catch (Exception e) {
                    //捕获到异常代表客户端断开连接
                    String msg = "客户端:"+ socket.getRemoteSocketAddress()+"客户端断开连接!";
                    System.out.println(msg);
                    ds.close();
                    break;
                }
            }
        }catch(Exception e){
            e.printStackTrace();
        }
    }
}

5.5 实现简易的HTTP协议交互

2953321-20240606203555879-62506590.png

代码举例:

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

public class BsServer {
    public static void main(String[] args) {

        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                10,
                20,
                10,
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(10),
                new ThreadPoolExecutor.CallerRunsPolicy()
        );
        try {
            ServerSocket serverSocket = new ServerSocket(8080);
            while (true) {
                Socket accept = serverSocket.accept();
                threadPoolExecutor.execute(new BsServerReadThread(accept));
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.SocketAddress;

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

    @Override
    public void run() {
        SocketAddress remoteSocketAddress = socket.getRemoteSocketAddress();
        System.out.println("客户端"+remoteSocketAddress+"已连接");
        try {
            // 这里用PrintWriter是为了格式化输出用的,读取数据用DataInputStream来读取协议
            OutputStream outputStream = socket.getOutputStream();
            PrintWriter writer = new PrintWriter(outputStream);
            //输出http协议
            writer.println("HTTP/1.1 200 OK");//响应行
            writer.println("Content-Type:text/html;charset=utf-8");//响应头
            writer.println("<html><head><title>bs</title></head><body><h1>测试内容</h1></body></html>");//响应体
            writer.flush();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}
动物装饰