[Java EE] 网络编程套接字

一.网络编程

1.什么是网络编程

网络编程指的是 网络上的主机 , 通过不同的进程 , 以编程的方式实现网络通信

2.网络编程中的概念

① 发送端和接收端

发送端 : 数据的 发送方 进程 ; 发送端主机为网络通信中的源主机

接收端 : 数据的 接收方 进程 ; 接收端主机位网络通信中的目的主机

收发端 : 发送端和接收端两端

注意 : 发送端和接收端只是相对的 , 只是一次网络数据传输产生数据流向 后的概念

② 请求和响应

一般情况下 , 获取一个网络资源 , 涉及到两次网络数据传输

第一次 : 请求数据的发送

第二次 : 响应数据的发送

③ 客户端和服务端

在常见的网络数据传输的场景下

服务端 : 提供服务的一方进程

客户端 : 获取服务的一方进程

④ 常见的客户端服务端模型

客户端是指给用户使用的程序 , 服务端是提供用户服务的程序 :

  1. 用户端先发送请求到服务端
  2. 服务端根据请求数据 , 执行相应的业务处理
  3. 服务端返回响应 : 发送业务处理结果
  4. 客户端根据响应数据 , 展示处理结果

二.Socket 套接字

Socket 是由系统提供用于网络通信的技术 , 是基于 TCP/IP 协议的网络通信的基本操作单元 ; 基于 Socket 套接字的网络程序开发就是网络编程

1. 分类

① 数据报套接字(使用传输层 UDP 协议)

UDP 协议特点 : 无连接(直接发数据) , 不可靠(丢包不重传) , 面向数据报(以一个数据报为单位) , 效率较高 , 有接收缓冲区但无发送缓冲区 , 由于以数据报为单位 边界清晰

简单理解 : 数据包以独立数据块为单位传输 , 一块数据需一次性发送 , 接收端也需完整接收该数据块 , 不能拆分接收

② 流套接字(使用传输层 TCP 协议)

TCP 协议特点 : 面向连接 , 可靠(重传丢失的数据包) , 面向字节流(以字节为单位) , 效率较低 , 有接收缓冲区和发送缓冲区 , 由于是流式数据(大小不限) 需要 去处理粘包问题

简单理解 : 字节流基于 IO 流传输 , 在 IO 流未被关闭时 , 数据无固定边界 , 可以分多次接收发送

3.UDP 数据报套接字编程模型(无连接,有边界)

① 概念

  • 对于 UDP 来说 , 具有无连接 , 面向数据报的特征 , 即每次都没有建立连接 , 并且一次发送全部数据报 , 一次接收全部数据报
  • Java 中使用 UDP 协议通信 , 主要基于 DatagramSocket 类来创建数据报套接字 , 并使用 DatagramPacket 作为发送或接收的 UDP 数据报

② 流程如下:

③ 核心 API

DatagramSocket

        用于发送和接收 UDP 数据报

构造方法

方法

说明

DatagramSocket()

创建一个 UDP 数据报套接字的 Socket , 绑定到本机的任意一个端口(客户端)

DatagramSocket(int port)

创建一个 UDP 数据报套接字的 Socket , 绑定到本机指定的端口(服务器)


方法

方法

说明

void receive(DatagramPacket p)

从此套接字接收数据报(若没有,则会阻塞等待)

void send(DatagramPacket p)

从此套接字发送数据报(不会阻塞 , 直接发送)

void close()

关闭此数据报套接字


DatagramPacket

        是 UDPSocket 发送和接收的数据报

构造方法

方法

说明

DatagramPacket(byte[] buf , int length)

构造一个 DatagramPacket 以用来接收数据报 , 接收的数据保存在字节数组中(buf) ,接收指定长度(length)

DatagramPacket(byte[] buf , int offset , int length , SocketAddress address)

构造一个 DatagramPacket 以用户来发哦是那个数据报 , 发送数据为字节数组(buf) ,从 offect 到指定长度 length ; address 指的是目的主句的 IP 和端口号


方法

方法

说明

InetAddress getAddress()

从接收的数据报中 , 获取发送端主句 IP 和地址 ; 或从发送的数据报中 , 获取接收端主机 IP 地址

int getPort()

从接收的数据报中 , 获取发送端主机的端口号 ; 或从发送的数据报中 , 获取接收端主机端口号

byte[] getData()

获取数据报中的数据

④ 代码示例 :

😶‍🌫️ UDP Echo Server
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
import java.nio.charset.StandardCharsets;

public class UdpEchoServer {
    private DatagramSocket socket = null;//代表一个UDP服务端
    public UdpEchoServer(int port) throws SocketException {
        socket = new DatagramSocket(port);//处理异常
    }
    public void start() throws IOException{
        System.out.println("服务器启动..");
        while(true){
            //1.读取请求并解析
            //  创建一个UDP数据报,用于封装请求
            DatagramPacket requestPacket = new DatagramPacket(new byte[4096], 4096);
            socket.receive(requestPacket);
            //  把数据报转换为字符串
            String request = new String(requestPacket.getData(),0,requestPacket.getLength());

            //2.根据请求计算响应(关键步骤)
            //此处是一个回显服务器
            String response = process(request);

            //3.把响应写入一个数据报,并返回给客户端
            //  此处不能使用response.length(),因为response.length()返回的是字符串的长度,而不是字节的长度
            DatagramPacket responsePacket = new DatagramPacket(response.getBytes(), response.getBytes().length,requestPacket.getSocketAddress());

            socket.send(responsePacket);//发送响应

            System.out.printf("req: %s; resp: %s\n", request, response);
        }
    }

    private String process(String request) {
        return request;
    }
    public static void main(String[] args) throws IOException {
        UdpEchoServer server = new UdpEchoServer(9090);
        server.start();
    }

}
😶‍🌫️ UDP Echo Client
import java.io.IOException;
import java.net.*;
import java.util.Scanner;

public class UdpEchoClient {
    private DatagramSocket socket = null;
    private String serverIP;
    private int serverPort;

    //与服务器不同的是,客户端的构造函数需要指定服务器的IP和端口号
    public UdpEchoClient(String serverIP,int serverPort) throws SocketException {
        this.serverIP = serverIP;
        this.serverPort = serverPort;
        socket = new DatagramSocket();
    }

    public void start() throws IOException {
        System.out.println("客户端启动..");
        Scanner sc = new Scanner(System.in);
        while(true){
            //1.从控制台读取请求
            System.out.println("请输入要发送的请求:");
            if (!sc.hasNext()) {//以空白符为分隔符
                break;
            }
            String request = sc.next();

            //2.将请求封装成DatagramPacket对象
            //  构造过程中需要指定数据,数据长度,目标IP,目标端口
            DatagramPacket requestPacket = new DatagramPacket(request.getBytes(),request.getBytes().length,
                                                              InetAddress.getByName(serverIP),serverPort);

            //3.发送请求
            socket.send(requestPacket);

            //4.接收服务器响应
            DatagramPacket responsePacket = new DatagramPacket(new byte[4096],4096);
            socket.receive(responsePacket);

            //5.将响应转换为字符串
            String response = new String(responsePacket.getData(),0,responsePacket.getLength());
            System.out.println(response);
        }
    }
    public static void main(String[] args) throws IOException {
        UdpEchoClient client = new UdpEchoClient("127.0.0.1",9090);
        client.start();
    }

}
核心组件拆解

阶段

接收端核心对象

发送端核心对象

数据传输载体

初始化

DatagramSocket(bindPort)

DatagramSocket()

数据封装

DatagramPacket(buffer, len)

DatagramPacket(data, len, ip, port)

数据报(有边界)

数据收发

receive (packet)(阻塞)send(packet)

send(packet)receive (packet)(可选)

整包数据报

数据解析

packet.getData()packet.getLength()packet.getAddress()

字节数组

资源释放

close() DatagramSocket

close() DatagramSocket

4.TCP 字节流套接字通信模型(有连接 , 无边界)

① 概念

  • TCP 是基于面向连接,可靠的字节流的传输层协议 , 流套接字编程依赖于 Java 的 ServerSocket(服务端) 和 Socket(客户端) 两个类
  • 核心可概括为 : 「服务端监听连接 → 客户端发起连接 → 双向字节流通信 → 关闭资源」

② 流程如下:

③ 核心 API

ServerSocket

        创建 TCP 服务端 Socket 的 API

构造方法

方法

说明

ServerSocket(int port)

创建一个服务端流套接字 Socket , 并绑定到指定端口


方法

方法

说明

Socket accept()

开始监听指定端口(创建时绑定的端口) , 有客户端连接后 , 返回一个服务端 Socket 对象 , 并基于该 Socket 建立于客户端的连接 , 否则阻塞等待

void close()

关闭此套接字


Socket

        主动连接服务端 , 于服务端建立双向字节流通信

构造方法

方法

说明

Socket(String host , int port)

创建一个客户端流套接字 Socket , 并与对应 IP 的主机的端口进程建立连接


方法

方法

说明

InetAddress getInetAddress()

返回套接字所连接的地址

InputStream getInputStream()

返回此套接字的输入流

OutputStream getOutStream()

返回此套接字的输出流

④ 代码示例

😶‍🌫️TcpEchoServer
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;

public class TcpEchoServer {
    private ServerSocket serverSocket = null;
    public TcpEchoServer(int port) throws IOException {
        serverSocket = new ServerSocket(port);
    }
    public void start() throws IOException {
        System.out.println("服务器启动...");
        while(true){
            Socket clientSocket = serverSocket.accept();
            processConnection(clientSocket);
        }
    }

    private void processConnection(Socket clientSocket) throws IOException {
        System.out.printf("[%s:%d] 客户端上线!\n", clientSocket.getInetAddress().toString(),clientSocket.getPort());
        try(InputStream inputStream = clientSocket.getInputStream();
            OutputStream outputStream = clientSocket.getOutputStream()) {
            //针对 InputStream 套了一个 Scanner
            Scanner scanner = new Scanner(inputStream);
            //针对 OutputStream 套了一个 PrintWriter
            PrintWriter writer = new PrintWriter(outputStream);

            //1.读取请求并解析
            while(true){
                if (!scanner.hasNext()) {
                    System.out.printf("[%s:%d] 客户端下线!\n", clientSocket.getInetAddress().toString(),clientSocket.getPort());
                    break;
                }
                String request = scanner.next();

                //2.根据请求计算响应(关键步骤)
                String response = process(request);

                //3.返回响应到客户端
                writer.println(response);
                writer.flush();

                System.out.printf("req: %s; resp: %s\n", request, response);
            }

        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            clientSocket.close();//需要处理异常
        }
    }

    private String process(String request) {
        return request;
    }
    public static void main(String[] args) throws IOException {
        TcpEchoServer server = new TcpEchoServer(9090);
        server.start();
    }
}

😶‍🌫️TcpEchoClient
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;

public class TcpEchoClient {
    private Socket socket = null;
    public TcpEchoClient(String serverIP,int serverPoint) throws IOException {
        socket = new Socket(serverIP,serverPoint);
    }
    public void start() throws IOException {
        Scanner scanner = new Scanner(System.in);
        try(OutputStream outputStream = socket.getOutputStream();
            InputStream inputStream = socket.getInputStream()){
            Scanner sc = new Scanner(inputStream);
            PrintWriter writer = new PrintWriter(outputStream);
            while(true){
                //1.从控制台读取请求
                System.out.println("请输入要发送的请求:");
                String request = scanner.next();

                //2.将请求写入Socket并发送
                writer.println(request);
                writer.flush();

                //3.读取响应
                String response = sc.next();

                //4..将响应打印到控制台
                System.out.println(response);

            }
        }
    }

    public static void main(String[] args) throws IOException {
        TcpEchoClient client = new TcpEchoClient("127.0.0.1",9090);
        client.start();
    }
}
核心组件拆解

阶段

服务端核心对象

客户端核心对象

数据传输载体

连接建立

ServerSocket(port)

Socket(ip, port)

阻塞等待

accept() → Socket

三次握手

数据传输

InputStream(读)OutputStream(写)

OutputStream(写)InputStream(读)

字节流(无边界)

资源释放

close () 流 / Socket/ServerSocket

close () 流 / Socket

评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值