详细讲解 —— 网络编程套接字(Java EE初阶)

1 认识网络编程套接字

网络编程套接字,是操作系统给应用程序提供的一组 API (叫做 socket API)。
这里的 socket 可以认为是应用层 和 传输层之间的通信桥梁。
传输层的核心协议有两种,一种是 TCP, 另一种是 UDP,socket API也对应有两组。由于UDP 和 TCP 协议差异很大,所以这两组 API 的差异也大。

两种套接字的直接区别:
TCP —— 有链接,可靠传输,面向字节流,全双工。
UDP —— 无连接,不可靠传输,面向字节报,全双工。

有链接 和 无连接
有链接 —— 就像打电话,我们需要先接通,然后,我们才能进行通话。
无连接 —— 就像我们微信发消息,不需要先接通,可以直接发送过去。

可靠传输 和 不可靠传输
可靠传输 —— 知道自己发的信息,对方有没有收到,就像打电话,我们知道对方有没有收到信息。
不可靠传输 —— 不知道自己发的信息,对方有没有接收到,就像是发消息,虽然我们的消息发出去了,但是我们并不知道对方有没有接收到

面向字节流 和 面向字节报
面向字节流 —— 一个字节一个字节的传输数据。
面向数据报 —— 以数据报为单位传输(其中的数据报长度由我们自己定义)。

全双工 和 半双工
全双工 —— 一条链路双向通信。
半双工 —— 一条链路单向通信。

下面我们主要介绍 UDP 和 TCP 这两个协议。

2 UDP 数据报套接字编程

UDP 有两个核心的类,一个是 DatagramSocket ,另一个是 DatagramPacket 。

DatagramSocket

这是 UDP 版本的 Socket 对象,代表着操作系统中的一个 socket 文件,是网卡硬件设备抽象体现。
其中有几个方法:receive()接收数据,send()发送数据,close()释放资源。

DatagramPacket

表示一个 UDP 数据报(用来封装数据)。
每次发送/接收数据,都是在传输一个 DatagramPacket 对象。

举例:实现一个最简单的客户端服务器程序,回显服务(请求是什么,响应就是什么)。

2.1 UPD服务端

  1. 创建一个 DatagramSocket 对象,并手动定义一个服务器端口号。
  2. 接收(receive 函数)客户端传来的数据报,创建一个空的数据报来接收。
  3. 根据客户端的请求,服务器做出响应。
  4. 把服务器的响应,返回(send 函数)到客户端。
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;

//源 IP:本机 IP
//源端口:服务器绑定的端口
//目的 IP:客户端的 IP
//目的端口:客户端的端口号
//协议类型:UDP
public class UdpEchoServer {
    //网络编程的基础要有一个socket对象。
    private DatagramSocket socket = null;

    //在初始化的时候,要给服务器定义一个端口号,我们只有知道了这个端口号,才能从客户端发来请求。
    public UdpEchoServer(int port) throws SocketException {
        //创建一个带有端口号的socket对象。
        socket = new DatagramSocket(port);
    }

    public void start() throws IOException {
        System.out.println("启动服务器!!!");
        while(true){
            //因为UDP接收的是一个数据报,所以我们要先定义一个空的数据报,来接收数据。
            DatagramPacket requestPacket = new DatagramPacket(new byte[1024], 1024);
            //其中的requestPacket是一个输出型参数,这个参数可以带出来数据,这个数据就是客户端传来的数据
            socket.receive(requestPacket);
            //先把数据报转换成字符串,为了方便处理
            String request = new String(requestPacket.getData(), 0,requestPacket.getLength(), "UTF-8");
            //使用process函数来实现服务器根据请求,来生成响应。
            String response = process(request);
            //因为UDP传输的是一个数据报,所以要生成一个要传输出去的数据报,其中包含了响应的IP和地端口号。
            DatagramPacket responsePacket = new DatagramPacket(request.getBytes(), request.getBytes().length,
                    requestPacket.getSocketAddress());
            //响应客户端的请求。
            socket.send(responsePacket);
            System.out.printf("[%s,%d], request:%s, response:%s\n",
                    requestPacket.getAddress(), requestPacket.getPort(),request,response);
        }
    }

    //这里我们实现的是一个回显效果,就是接收什么,响应什么。
    private String process(String request) {
        return request;
    }

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

2.1 UDP客户端

  1. 创建 DatagramSocket 对象,网络编程的基础,客户端不需要指定端口号。
  2. 发送(send 函数)请求,打包成一个数据报。
  3. 接收(receive 函数)服务器传来的响应,要创建一个空的数据报来接收数据。
import java.io.IOException;
import java.net.*;
import java.util.Scanner;

//源 IP:本机 IP
//源端口:系统分配的端口
//目的 IP:服务器的 IP
//目的端口:服务器端口号
//协议类型:UDP

public class UdpEchoClient {
    //网络编程的基础要有一个socket对象。
    private DatagramSocket socket = null;
    private String serverIP;
    private int serverPort;

    //使用构造函数来初始化socket对象,服务器的IP和端口号
    public UdpEchoClient() throws SocketException {
        socket = new DatagramSocket();
        serverIP = "127.0.0.1";
        serverPort = 9090;
    }

    public void start() throws IOException {
        Scanner sc = new Scanner(System.in);
        while(true){
            System.out.print("-> ");
            //输入要传给服务器的内容。
            String request = sc.nextLine();
            //要传给服务器的内容要封装成一个数据报(DatagramPacket),数据报中要有目标地址的IP和端口号
            DatagramPacket requestPacket = new DatagramPacket(request.getBytes(), request.getBytes().length,
                    InetAddress.getByName(serverIP), serverPort);
            //传送数据报
            socket.send(requestPacket);
            //接收要用数据报来接收,先构造出数据报
            DatagramPacket responsePacket = new DatagramPacket(new byte[1024], 1024);
            //接收服务器传来的数据报
            socket.receive(responsePacket);
            //把数据报中的内容变成字符串
            String response = new String(responsePacket.getData(), 0, responsePacket.getLength(), "UTF-8");
            System.out.printf("request: %s, response: %s\n", request, response);
        }
    }

    public static void main(String[] args) throws IOException {
        UdpEchoClient client = new UdpEchoClient();
        client.start();
    }
}

2.3 结果测试

在这里插入图片描述
在这里插入图片描述

3 TCP流套接字编程

TCP API 中,也是涉及到两个核心的类 ServerSocket 类和 Socket 类

1) ServerSocket(专门给TCP服务器用的)使用 accept 方法和客户端建立连接。

2)Socket 既需要给服务器用,也需要给客户端用,TCP协议是面向字节流的,所以不需要有一个数据报类,这里直接用 Socket 中自带的文件来处理。

3.1 TCP服务端

  1. 创建 ServerSocket 对象,指定服务器的端口号。
  2. 建立连接,建立连接需要使用到 accept函数,这个函数返回 Socket 类型的对象。
  3. 从文件中读取客户端的请求。
  4. 对客户端的请求做出响应。
  5. 对文件进行写入服务器的响应。
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 {
    //创建ServerSocket对象,是为了建立连接和接收客户端所传的信息
    private ServerSocket serverSocket = null;

    //服务器要指定端口号,方便客户端找到服务器
    public TcpEchoServer(int port) throws IOException {
        serverSocket = new ServerSocket(port);
    }

    public void start() throws IOException {
        System.out.println("程序启动!!");
        while(true) {
            //serverSocket 使用 accept 方法建立连接,这个方法会返回客户端的请求信息。
            //用 Socket 这个类来接收客户端的信息
            Socket socket = serverSocket.accept();
            //因为要建立连接,processConnection 方法要执行完,也就是客户端断开链接,才能执行下一个客户端请求。
            //这样就不能同时处理多个客户端的请求了,为了解决这个问题,使用了多线程,每一个线程负责一个客户端。
            Thread thread = new Thread(()->{
                processConnection(socket);
            });
            thread.start();
        }
    }

    private void processConnection(Socket socket) {
        System.out.printf("[%s, %d] 建立建立连接!!", socket.getInetAddress().toString(), socket.getPort());
        //Socket 对象要使用文件来进行操作,每一个对象中都自带有一个文件。
        try(InputStream inputStream = socket.getInputStream()) {
            try(OutputStream outputStream = socket.getOutputStream()){
                //使用Scanner类,为了简化代码,写起来更加的方便
                Scanner scanner = new Scanner(inputStream);
                while(true){
                    //如果客户端的请求接收完成,就断开链接,退出循环
                    if(!scanner.hasNext()){
                        System.out.printf("[%s, %d] 断开链接!!!", socket.getInetAddress().toString(),socket.getPort());
                        break;
                    }
                    //接收客户端的请求
                    String request = scanner.next();
                    //服务器对客户端的请求,做出响应
                    String response = process(request);
                    //把客服端的响应写入文件中,让客户端接收
                    PrintWriter printWriter = new PrintWriter(outputStream);
                    printWriter.println(response);
                    //其中的 flush 是刷新缓冲区,为了让客户端及时接收到数据
                    printWriter.flush();
                    System.out.printf("[%s, %d], request:%s, response:%s\n", socket.getInetAddress().toString(),
                            socket.getPort(), request, response);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                //关闭文件
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    //服务器根据请求做出响应
    private String process(String request) {
        return request;
    }

    //启动服务器
    public static void main(String[] args) throws IOException {
        TcpEchoServer server = new TcpEchoServer(9090);
        server.start();
    }
}

3.2 TCP客户端

  1. 创建 Socket 对象,并传入服务器的 IP地址 和 端口号。
  2. 发送客户端请求。
  3. 接收服务器响应。
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 {
    //创建 Socket 对象
    private Socket socket = null;

    //这里传入的是服务器的地址,和端口号,为了能找到服务器
    public TcpEchoClient(String serverIP, int serverPort) throws IOException {
        socket = new Socket(serverIP, serverPort);
    }
    
    public void start(){
        System.out.println("链接建立成功");
        Scanner scanner = new Scanner(System.in);
        try(InputStream inputStream = socket.getInputStream()) {
            try(OutputStream outputStream = socket.getOutputStream()){
                while(true){
                    //客户端发出请求
                    String request = scanner.next();
                    //向文件中写入请求
                    PrintWriter printWriter = new PrintWriter(outputStream);
                    printWriter.println(request);
                    printWriter.flush();

                    //接收文件中返回来的响应
                    Scanner scanner1 = new Scanner(inputStream);
                    String response = scanner1.next();
                    System.out.printf("request: %s, response: %s\n", request, response);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    //启动客户端
    public static void main(String[] args) throws IOException {
        TcpEchoClient tcpEchoClient = new TcpEchoClient("127.0.0.1", 9090);
        tcpEchoClient.start();
    }
}

3.2 运行结果

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

评论 22
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

IT技术博主-方兴未艾

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值