网络编程套接字之UDP实现回显服务器及客户端

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

目录

前言

基础理解

传输层协议

UDP

TCP

Socket API

DatagramSocket API

DatagramPacket API

UDP实现回显服务器

完整代码展现有详细注释

UDP实现回显客户端

完整代码展现有详细注释

小结


前言

    通过套接字Socket就可以实现客户端发送请求服务起接收请求处理完成后就可以响应给客户端。这样的一套流程就实现了数据在网络上的传输。

基础理解

    网络编程中在硬件上使用网卡发送和接收数据。在java中使用Socket直接操作网卡而对于操作系统来说一切皆文件那么这个Socket对象在操作系统中是被当作文件处理的。Socket就是操作系统给应用程序提供的接口。

    Socket所提供的api和传输层密切相关应用层首先接触的就是传输层。使用Socket所提供的api就可以实现应用层的代码并且和传输层进行交互。

    客户端发起请求 --> 服务器接收请求 --> 服务器处理请求并响应给客户端 --> 客户端接收响应

传输层协议

UDP

    特点无连接不可靠传输面向数据报全双工大小首先一次最多64k有接收缓冲区无发送缓冲区。

TCP

    特点有连接。可靠传输面向字节流全双工大小不限有接收缓冲区和发送缓冲区。

理解

    1无连接不需要建立客户端和服务器之间的连接就可以发送数据。例如微信发消息

    2有连接需要建立客户端和服务器之间的连接才可发送数据。例如打电话需要接听

    3不可靠传输发送方不知道数据是发过去了还是丢包了。

    4可靠传输发送方知道自己的消息是否发送过去。

    注意可靠性就是针对发送方是否清楚数据是否发送过去。

    5面向数据报数据传输以“数据报”为基本单位一块一块的发数据。

    6面向字节流数据传输和读文件类似“流式”的。一次发送部分数据也可以发送全部数据。

    7全双工可以同时发送和接收数据那么半双工就不支持。

Socket API

    java中使用UDP协议通信主要基于 DatagramSocket 类来创建数据报套接字并使用
DatagramPacket 作为发送或接收的UDP数据报。

DatagramSocket API

DatagramSocket构造方法

 注意

    创建一个UDP数据报套接字的Socket绑定本机任意一个随机端口一般用于客户端

 注意

    创建一个UDP数据报套接字的Socket绑定指定端口一般用于服务端

DatagramSocket方法

注意

    从网卡接收数据报。这个参数需要一个空的DatagramPacket对象当从网卡接收到数据报就会填充好这个空的对象以便供我们处理数据。

    如果没有接收数据报这个方法会阻塞等待。

 注意

    将已经构造好的数据报发送到网卡。不会阻塞等待直接发送。

 注意

     在操作系统中Socket对象是被当作文件处理的那么就需要释放pcb中文件描述符表中的资源。

DatagramPacket API

DatagramPacket构造方法

 注意

    构造一个DatagramPacket以用来接收数据报接收的数据保存在buf缓冲数组中接收的指定长度。

 注意

    构造一个DatagramPacket以用来接收数据报数据填充为字节数组从0起始位置到指定长度offsetlengthaddress指定目的主机IP和端口号。一般处理完请求后构造成数据报来发送

DatagramPacket方法

 注意

     从接收的数据报中获取发送端主机IP地址或从发送的数据报中获取接收端主机IP地址。

 注意

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

 注意

     获取数据报中的数据就是缓冲数组。

UDP实现回显服务器

    服务器是被动的一方需要接收客户端发起的请求。那么客户端就必须明确服务器的ip和具体进程的端口号。所以在实现服务器时就必须指定端口号这里实现的是本机到本机的数据发送ip就使用环回ip即可。

    由于不清楚客户端什么时候发起请求那么服务器不能休息随时待命。这里使用死循环的方式但它不会一直循环因为receive()方法当没有接收到请求时会阻塞等待。

    我们首先需要明确服务器的工作流程。接收客户端的请求 --> 处理请求 --> 将响应发送给客户端。

DatagramPacket requestPacket = new DatagramPacket(new byte[4096], 4096);
socket.receive(requestPacket);

注意

    首先构造一个空的DatagramPacket对象传入缓冲数组和指定长度。当下面receive()方法从网卡接收到客户端请求时就会填充这个空对象。数据是写入了缓冲数组

    当receive()方法当没有接收到请求时会阻塞等待。随时待命

String request = new String(requestPacket.getData(), 0, requestPacket.getLength());

注意

    由于接收的数据构造成了数据报这样不利于我们处理数据。我们将数据报中的数据取出来构造成字符串。

String response = process(request);
public String process(String request) {
    return request;
}

注意

    服务器针对请求进行需求处理这里的process是一个方法。由于我们实现的是回显服务器即直接返回这个字符串即可。

 DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),response.getBytes().length,
                    requestPacket.getSocketAddress());
socket.send(responsePacket);

注意

    这里将处理完后的响应构造成数据报然后发送给客户端程序。这里需要传入字节数组填充的具体长度这里需要用字节数组的长度不能用字符串的长度。转换之后两者长度是不一致的和客户端的ip和端口号getSocketAddress()方法可以获得发送方的ip和端口号。

    当构造完成之后直接将数据报发给客户端即可。

完整代码展现有详细注释

public class UdpEchoSever {
    //Socket对象直接操作的是网卡在操作系统中任务Socket对象是文件一切皆文件
    //通过Socket对象接收和发送数据
    private DatagramSocket socket = null;
    public UdpEchoSever(int port) throws SocketException {
        //服务器是被动的一方客户端必须找到服务器的端口才能找到指定程序因此服务器必须指定端口号
        socket = new DatagramSocket(port);
    }
    public void start() throws IOException {
        System.out.println("启动服务器");
        while (true) {
            //构造空的Packet对象传入缓冲数组
            DatagramPacket requestPacket = new DatagramPacket(new byte[4096], 4096);
            //receive从网卡接收数据解析后填充这个空对象输出形参数可以认为写入了缓冲数组
            //客户端如果没有发请求receive就会阻塞直到客户端发送请求保证这里不会一直循环
            socket.receive(requestPacket);

            //根据接收的数据由于接收的数据不方便处理因此构造成字符串
            String request = new String(requestPacket.getData(), 0, requestPacket.getLength());
            //服务器响应处理
            String response = process(request);
            //构造发送的数据报字节数组字节数组长度IP和端口根据响应的字符串
            //这个DatagramPacket只认字节数组因此就需要获取字节数组的长度而不是字符的个数
            DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),response.getBytes().length,
                    requestPacket.getSocketAddress());
            //发送数据到Ip和端口指定的客户端程序
            socket.send(responsePacket);
            //打印下请求响应的中间结果
            System.out.printf("源IP%s 源端口%d 请求数据%s 响应数据%s\n", requestPacket.getAddress().toString(),
                    requestPacket.getPort(), request, response);
        }
    }
    //回显服务器处理直接返回数据响应
    public String process(String request) {
        return request;
    }
    public static void main(String[] args) throws IOException {
        //端口号的指定在 1024 -- 65535 里指定
        UdpEchoSever sever = new UdpEchoSever(8280);
        sever.start();
    }
}

UDP实现回显客户端

    客户端发送数据需要明确服务器的ip和具体进程的端口号。客户端的端口号我们不需要手动指定因为客户端程序是存在于客户主机上我们如果手动指定就很可能与其他进程端口号冲突这样就直接抛异常了Address already in use。直接让操作系统随机分配一个空闲的端口号。

    那么为什么服务端我们可以指定端口号这样就不怕与其他进程冲突了么因为服务器在我们自己手里我们明确里面的各种端口号简单来说就是可控的。 

    我们首先明确客户端的工作流程。用户输入数据 --> 发送到服务器 --> 接收服务器的响应。这里也使用死循环和上面一样receive()方法会阻塞不会一直循环。

 Scanner scanner = new Scanner(System.in);
 System.out.println("输入你要发送的数据");
 String request = scanner.next();
 if (request.equals("exit")) {
        System.out.println("bye bye");
        break;
 }

注意

    提示用户输入数据这里做了简单的判断。

 DatagramPacket requestPacket = new DatagramPacket(request.getBytes(), request.getBytes().length,
                    InetAddress.getByName(severIp), severPort);
 socket.send(requestPacket);

注意

    根据用户输入的数据构造成数据报。需要字节数组具体填充的长度同样的需要字节数组长度而不是字符串长度ip由于这里需要一个32位的ip而上面的是字符串因此需要转换和服务器端口号。然后直接发送即可。

 DatagramPacket responsePacket = new DatagramPacket(new byte[4096], 4096);
 socket.receive(responsePacket);

注意

    接收服务器的响应。首先构造一个空的DatagramPacket对象传入缓冲数组和指定长度。receive()方法从网卡接收到数据报然后构造好这个空的对象。

    receive()当没有接收到响应前同样的也会阻塞等待。

 String response = new String(responsePacket.getData(), 0, responsePacket.getLength());
 System.out.println(response);

注意

    由于是数据报不利于用户观察数据因此转转换为字符串。获取到数据报中的缓冲数组从0位置到指定长度来构造这个字符串最终显示给用户。

完整代码展现有详细注释

public class UdpEchoClient {
    private DatagramSocket socket = null;
    //客户端需要知道服务器的IP和端口这里先存一下
    private String severIp = null;
    private int severPort = 0;
    public UdpEchoClient(String severIp, int severPort) throws SocketException {
        //客户端不需要指定端口号客户端程序在用户手里指定端口号就可能和其他进程重复。因此让操作系统分配一个空闲的端口
        //服务器为什么指定端口不怕重复呢因为服务器在程序员手里我们清楚端口号的使用可控的而客户端是不可控的
        socket = new DatagramSocket();
        this.severIp = severIp;
        this.severPort = severPort;
    }
    //客户端启动
    public void start() throws IOException {
        //用户输入数据
        while (true) {
            Scanner scanner = new Scanner(System.in);
            System.out.println("输入你要发送的数据");
            String request = scanner.next();
            if (request.equals("exit")) {
                System.out.println("bye bye");
                break;
            }
            //发送数据报构造DatagramPacket对象
            //此处的IP需要一个32位的整数而上面的是字符串需要转换
            DatagramPacket requestPacket = new DatagramPacket(request.getBytes(), request.getBytes().length,
                    InetAddress.getByName(severIp), severPort);
            socket.send(requestPacket);

            //接收数据报填充这个空对象阻塞到服务器发送过来数据
            //receive的阻塞操作系统实现的JAVA只是封装了一下
            DatagramPacket responsePacket = new DatagramPacket(new byte[4096], 4096);
            socket.receive(responsePacket);
            //显示数据报将数据报转换为字符串
            String response = new String(responsePacket.getData(), 0, responsePacket.getLength());
            System.out.println(response);
        }
    }

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

小结

    这里大多是api的使用我们要理解其中的原理便能得心应手。

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: 服务器