基于UDP和TCP套接字实现简单的回显客户端服务器程序

6 篇文章 0 订阅
5 篇文章 0 订阅

目录

1. 套接字

2. 基于UDP 套接字实现的简单客户端 服务器程序

3. 基于TCP套接字实现的简单客户端 服务器程序


1. 套接字

       之前我们有分享到协议分层这个概念,其中就讲到上层协议调用下层协议,下层协议给上层协议提供支持,这里支持指的是就是socket套接字,它是操作系统给应用程序提供的一组用于网络编程的API(传输层给应用层的支持).


2. 基于UDP 套接字实现的简单客户端 服务器程序

       UDP是传输层协议之一,它的特点是无连接,不可靠,面向数据报传输,半双工(数据传输允许数据在两个方向上传输,但在某一时刻,只允许数据在一个方向传输).

       ● UDP的套接字

          DatagramSocket(指定端口号为服务器,不指定端口号则为客户端)类是用于发送和接收数据报的套接字,而DatagramPacket类是表示UDP数据报,客户端和服务器双方传输的都是数据报.

          1) DatagramSocket常用构造方法有:

//不指定端口号,表示客户端
DatagramSocket socket = new DatagramSocket();
//指定端口号,表服务端
DatagramSocket socket = new DatagramSocket(port);

              其它的构造方法参考jdk文档:

 主要的普通方法有receive():从此套接字接收数据报包 :

它的参数是一个输出型参数,传入一个空的数据报,那么就会将从网卡读取到的数据填充进去.打个比方,就是你拿一个空的饭盒去食堂窗口打菜,有菜食堂就会往你饭盒打菜.等你从窗口处拿到你的饭盒,你的饭盒就已经有菜了.这时你的饭盒就相当于输出型参数.当然如果窗口菜还没炒好,你就需要等待.所以receive()方法可能会产生阻塞.

                              send():从此套接字发送数据报包:

                              ,close():关闭该数据报套接字.

注:要确定该数据报套接字不会再使用才能关闭该资源.还有其它很多该套接字的方法,大家想了解的话可以去查看jdk文档学习.

          2) DatagramPacket常用构造方法有:

               其它的构造方法参考jdk文档:

               其它的方法主要分为获取和设置:包括(数据报发送或接收的计算机IP地址,端口号),数据缓冲区,发送或接收数据的长度等等,根据我们自己的实际需求去调用.这里我们用到的就是以下方法:

       ● 示例代码

          这里先将示例代码展示,然后再根据代码跟大家分享一下我的思路.

//服务器
public class UdpEchoServer {
    DatagramSocket socket;
    // 1.初始化一个带端口号的DatagramSocket对象(作为服务器)
    public UdpEchoServer(int port) throws SocketException {
        socket = new DatagramSocket(port);
    }

    public void start() throws IOException {
        System.out.println("服务器启动!");
        while (true) {
            // 2.准备一个数据报(空的饭盒),调用receive方法阻塞等待接收客户端的请求数据
            DatagramPacket request = new DatagramPacket(new byte[2023],2023);
            // 5.收到客户端发送的数据报数据并填入步骤2准备的数据报中
            socket.receive(request);

            // 将请求数据转换为字符串
            String req = new String(request.getData(),0,request.getLength());

            // 6.根据请求计算响应(这里由于是回显服务器,所以请求和响应是一样的)
            String response = process(req);

            // 7.将响应数据放入一个数据报中,使用send()方法返回给客户端
            DatagramPacket resp = new DatagramPacket(response.getBytes(),0,response.getBytes().length,request.getSocketAddress());
            socket.send(resp);

            // (最后打印请求和响应数据)
            System.out.printf("[ip:%s port:%s] req:%s res:%s\n",resp.getAddress(),socket.getPort(),req ,response);
        }
    }

    private String process(String request) {
        return request;
    }

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

//客户端
public class UdpEchoClient {
    DatagramSocket socket;
    // 发送请求数据报的服务端ip地址和端口
    public String ip;
    public int port;

    // 1.初始化一个不带端口号的DatagramSocket对象(作为客户端)
    public UdpEchoClient(String ip, int port) throws SocketException {
        socket = new DatagramSocket();
        this.ip = ip;
        this.port = port;
    }

    public void start() throws IOException {
        System.out.println("客户端启动");
        while (true) {
            Scanner s = new Scanner(System.in);
            String temp = s.nextLine();
            // 3.将请求的数据放入一个数据报中,使用send()发送
            DatagramPacket request = new DatagramPacket(temp.getBytes(),temp.getBytes().length,InetAddress.getByName(ip),port);
            socket.send(request);

            // 4.准备一个空的数据报接收响应数据,调用receive()方法阻塞等待服务器返回的响应数据
            DatagramPacket response = new DatagramPacket(new byte[2023],2023);
            // 8.收到响应数据报,将响应数据放入步骤4准备的空数据报中
            socket.receive(response);

            // 将数据报数据转换为字符串
            String res = new String(response.getData(),0,response.getLength());
            // (最后打印请求和响应数据)
            System.out.printf("req:%s res:%s\n", temp,res);
        }
    }

    private String process(String request) {
        return request;
    }

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

       ● 根据示例代码分析服务器和客户端的交互流程(图解)

注: ① 应用层可以通过编程实现UDP全双工通信,在某一时刻既可以发送数据,又可以接收数据.但在网络通信中,UDP本身仍然是半双工的协议.

      ② 这里的socket其实是一个特殊的文件,是网卡这个硬件设备的抽象表示(我们可以通过它间接读取或修改网卡数据).


3. 基于TCP套接字实现的简单客户端 服务器程序

      TCP也是传输层协议之一,它的特点是有连接,可靠传输,面向字节流,全双工(允许数据在两个方向同时传输).

       ● TCP的套接字

           可以分为两类,一个是ServerSocket类,它是服务器套接字,给服务器使用(一定要绑定端口号),另一个是Socket类,也是个套接字,但是客户端和服务器都可以使用它(和DatagramSocket相似,没绑定端口号给客户端使用,绑定端口号给服务器使用).

          1) ServerSocket常用构造方法有:

              其它构造方法:

 

 

           它的常用普通方法:accept() 与客户端建立连接(得到的是一个Socket对象):

                                         close() 关闭套接字(一定要确定该套接字不再使用之后再关闭)

          2) Socket常用构造方法有:

  

              其它构造方法:

         Socket中我们在这里用到的普通方法: 

 

         其它方法大家可以根据需求查看jdk文档使用. 

       ● 示例代码

// 服务器
public class TcpEchoServer {
    ServerSocket socket;

    public TcpEchoServer(int port) throws IOException {
        // 该服务器位于哪个端口
        socket = new ServerSocket(port);
    }

    public void start() throws IOException {
        System.out.println("服务器启动");

        while (true) {
            // 请求连接
            Socket clientSocket = socket.accept();
            // 可连接多个客户端,每个客户端在一个线程中处理自己的逻辑
            Thread thread = new Thread(() -> {
                try {
                    processClient(clientSocket);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            });
            thread.start();
        }
    }

    private void processClient(Socket clientSocket) throws IOException {
        System.out.println("客户端上线!");
        // try catch with resource操作会让这里创建的字节流在使用后自动关闭
        try (InputStream inputStream = clientSocket.getInputStream();
             OutputStream outputStream = clientSocket.getOutputStream()) {
            // 使用字符流处理
            Scanner input = new Scanner(inputStream);
            PrintWriter print = new PrintWriter(outputStream);
            while (true) {
                // 读取到字符流末尾
                if (!input.hasNext()) {
                    System.out.println("客户端下线!");
                    break;
                }
                // 获取请求
                String request = input.next();

                // 处理请求并返回响应
                String response = process(request);

                // 写回响应
                print.println(response);
                //刷新缓冲区
                print.flush();
                //打印结果
                System.out.printf("[ip:%s port:%s] request:%s response:%s\n", clientSocket.getInetAddress().toString(), clientSocket.getPort(), 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(4048);
        server.start();
    }
}

// 客户端
public class TcpEchoClient {
    Socket socket;

    public TcpEchoClient(String ip,int port) throws IOException {
        // 连接对应ip地址及端口的服务器
        socket = new Socket(ip,port);
    }

    public void start() throws IOException {
        System.out.println("客户端开启");
        Scanner s = new Scanner(System.in);
        try (InputStream inputStream = socket.getInputStream();
             OutputStream outputStream = socket.getOutputStream()){
            PrintWriter printWriter = new PrintWriter(outputStream);
            Scanner input = new Scanner(inputStream);
            while (true) {
                // 发送请求
                String request = s.next();
                printWriter.println(request);
                // 刷新缓冲区
                printWriter.flush();

                // 得到响应
                String response = input.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 client = new TcpEchoClient("127.0.0.1",4048);
        client.start();
    }
}

注:我们这里约定请求和响应都是字符串,需要使用字符流来处理.

       ● 根据示例代码分析服务器和客户端的交互流程(图解)

注:这里缓冲区策略和线程池策略类似,将数据先写到缓冲区,等缓冲区满了再统一自动写入网卡.我们这里手动刷新缓冲区是为了将数据直接写入网卡中.


 分享完毕~欢迎大家一起学习讨论~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值