Java-socket网络编程

Java 网络编程

网络编程是指编写运行在多个设备(计算机)的程序,这些设备都通过网络连接起来。

java.net 包中 J2SE 的 API 包含有类和接口,它们提供低层次的通信细节。你可以直接使用这些类和接口,来专注于解决问题,而不用关注通信细节。

java.net 包中提供了两种常见的网络协议的支持:

TCP:TCP 是传输控制协议的缩写,它保障了两个应用程序之间的可靠通信。通常用于互联网协议,被称 TCP / IP。

UDP:UDP 是用户数据报协议的缩写,一个无连接的协议。提供了应用程序之间要发送的数据的数据包。

Socket 编程:这是使用最广泛的网络概念,它已被解释地非常详细。

URL 处理:这部分会在另外的篇幅里讲,点击这里更详细地了解在 Java 语言中的 URL 处理。

Socket 编程

套接字使用TCP提供了两台计算机之间的通信机制。 客户端程序创建一个套接字,并尝试连接服务器的套接字。

当连接建立时,服务器会创建一个 Socket 对象。客户端和服务器现在可以通过对 Socket 对象的写入和读取来进行通信。

java.net.Socket 类代表一个套接字,并且 java.net.ServerSocket 类为服务器程序提供了一种来监听客户端,并与他们建立连接的机制。

以下步骤在两台计算机之间使用套接字建立TCP连接时会出现:

服务器实例化一个 ServerSocket 对象,表示通过服务器上的端口通信。

服务器调用 ServerSocket 类的 accept() 方法,该方法将一直等待,直到客户端连接到服务器上给定的端口。

服务器正在等待时,一个客户端实例化一个 Socket 对象,指定服务器名称和端口号来请求连接。

Socket 类的构造函数试图将客户端连接到指定的服务器和端口号。如果通信被建立,则在客户端创建一个 Socket
对象能够与服务器进行通信。

在服务器端,accept() 方法返回服务器上一个新的 socket 引用,该 socket 连接到客户端的 socket。

连接建立后,通过使用 I/O 流在进行通信,每一个socket都有一个输出流和一个输入流,客户端的输出流连接到服务器端的输入流,而客户端的输入流连接到服务器端的输出流。

TCP 是一个双向的通信协议,因此数据可以通过两个数据流在同一时间发送.以下是一些类提供的一套完整的有用的方法来实现 socket。

ServerSocket 类的方法

服务器应用程序通过使用 java.net.ServerSocket 类以获取一个端口,并且侦听客户端请求。

ServerSocket 类有四个构造方法:

序号 方法 方法描述
1 public ServerSocket (int port) throws IOException 创建绑定到特定端口的服务器套接字。
2 public ServerSocket (int port, int backlog) throws IOException 利用指定的 backlog 创建服务器套接字并将其绑定到指定的本地端口号。
3 public ServerSocket (int port, int backlog, InetAddress address) throws IOException 使用指定的端口、侦听 backlog 和要绑定到的本地 IP 地址创建服务器。
4 public ServerSocket () throws IOException 创建非绑定服务器套接字。

创建非绑定服务器套接字。 如果 ServerSocket 构造方法没有抛出异常,就意味着你的应用程序已经成功绑定到指定的端口,并且侦听客户端请求。

这里有一些 ServerSocket 类的常用方法:

序号 方法 方法描述
1 public int getLocalPort () 返回此套接字在其上侦听的端口。
2 public Socket accept () throws IOException 侦听并接受到此套接字的连接。
3 public void setSoTimeout (int timeout) 通过指定超时值启用/禁用 SO_TIMEOUT,以毫秒为单位。
4 public void bind (SocketAddress host, int backlog) 将 ServerSocket 绑定到特定地址(IP 地址和端口号)。

Socket 类的方法

java.net.Socket 类代表客户端和服务器都用来互相沟通的套接字。

客户端要获取一个 Socket 对象通过实例化 ,而 服务器获得一个 Socket 对象则通过 accept() 方法的返回值。

Socket 类有五个构造方法.

序号 方法 方法描述
1 public Socket (String host, int port) throws UnknownHostException, IOException. 创建一个流套接字并将其连接到指定主机上的指定端口号。
2 public Socket (InetAddress host, int port) throws IOException 创建一个流套接字并将其连接到指定 IP 地址的指定端口号。
3 public Socket (String host, int port, InetAddress localAddress, int localPort) throws IOException. 创建一个套接字并将其连接到指定远程主机上的指定远程端口。
4 public Socket (InetAddress host, int port, InetAddress localAddress, int localPort) throws IOException. 创建一个套接字并将其连接到指定远程地址上的指定远程端口。
5 public Socket () 通过系统默认类型的 SocketImpl 创建未连接套接字

当 Socket 构造方法返回,并没有简单的实例化了一个 Socket 对象,它实际上会尝试连接到指定的服务器和端口。

下面列出了一些感兴趣的方法,注意客户端和服务器端都有一个 Socket 对象,所以无论客户端还是服务端都能够调用这些方法。

序号 方法 方法描述
1 public void connect (SocketAddress host, int timeout) throws IOException 将此套接字连接到服务器,并指定一个超时值。
2 public InetAddress getInetAddress () 返回套接字连接的地址。
3 public int getPort () 返回此套接字连接到的远程端口。
4 public int getLocalPort () 返回此套接字绑定到的本地端口。
5 public SocketAddress getRemoteSocketAddress () 返回此套接字连接的端点的地址,如果未连接则返回 null。
6 public InputStream getInputStream () throws IOException 返回此套接字的输入流。
7 public OutputStream getOutputStream () throws IOException 返回此套接字的输出流。
8 public void close () throws IOException 关闭此套接字。

InetAddress 类的方法

这个类表示互联网协议(IP)地址。下面列出了 Socket 编程时比较有用的方法:

序号 方法 方法描述
1 static InetAddress getByAddress (byte[] addr) 在给定原始 IP 地址的情况下,返回 InetAddress 对象。
2 static InetAddress getByAddress (String host, byte[] addr) 根据提供的主机名和 IP 地址创建 InetAddress。
3 static InetAddress getByName (String host) 在给定主机名的情况下确定主机的 IP 地址。
4 String getHostAddress () 返回 IP 地址字符串(以文本表现形式)。
5 String getHostName () 获取此 IP 地址的主机名。
6 static InetAddress getLocalHost () 返回本地主机。
7 String toString () 将此 IP 地址转换为 String。

TCP Socket

Java 为 TCP 协议提供了两个类:Socket 类和 ServerSocket 类。
一个 Socket 实例代表了TCP连接的一端。
一个 TCP 连接(TCP connection)是一条抽象的双向信道,两端分别由IP地址和端口号确定。在开始通信之前,要建立一个 TCP 连接,这需要先由客户端 TCP 向服务器端 TCP发送连接请求。
ServerSocket 实例则监听 TCP 连接请求,并为每个请求创建新的 Socket 实例。
也就是说,服务器端要同时处理ServerSocket 实例和 Socket 实例,而客户端只需要使用 Socket实例。
每个Socket实例会关联一个InputStream和OutputStream对象,我们通过将字节写入套接字的OutputStream来发送数据,并通过从InputStream来接收数据。

TCP连接的建立步骤

客户端向服务器端发送连接请求后,就被动地等待服务器的响应。典型的TCP客户端要经过下面三步操作:

1、创建一个Socket实例:构造函数向指定的远程主机和端口建立一个TCP连接;
2、通过套接字的I/O流与服务端通信;
3、使用Socket类的close方法关闭连接。

服务端的工作是建立一个通信终端,并被动地等待客户端的连接。典型的TCP服务端执行如下两步操作:

1、创建一个ServerSocket实例并指定本地端口,用来监听客户端在该端口发送的TCP连接请求;
2、重复执行:

1)调用ServerSocket的accept()方法以获取客户端连接,并通过其返回值创建一个Socket实例;
2)为返回的Socket实例开启新的线程,并使用返回的Socket实例的I/O流与客户端通信;
3)通信完成后,使用Socket类的close()方法关闭该客户端的套接字连接

Java-socket网络编程_第1张图片

TCP Demo

Demo1.基本的流发送示例:

服务端开启一个TCP服务器,参数为端口号
客户端启动一个TCP服务,参数为服务端的地址,发送的字符,端口号

客户端

import java.net.Socket;
import java.net.SocketException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

public class TCPClient {

    public static void main(String[] args) throws IOException {

        if ((args.length < 2) || (args.length > 3))

            throw new IllegalArgumentException(
                    "Parameter(s):  []");

        String server = args[0]; // Server name or IP address
        /* Convert argument String to bytes using the default character encoding */
        byte[] senddata = args[1].getBytes();
        int servPort = (args.length == 3) ? Integer.parseInt(args[2]) : 7;
        /* Create socket that is connected to server onspecified port*/
        Socket socket = new Socket(server, servPort);
        System.out.println("Connected to server...sending echo string");

        InputStream in = socket.getInputStream();
        OutputStream out = socket.getOutputStream();
        out.write(senddata); // Send the encoded string to the server

        int totalBytesRcvd = 0; // Total bytes received so far
        byte[] recvdata = new byte[in.available()];
        int bytesRcvd; // Bytes received in last read
        while (totalBytesRcvd < recvdata.length) {
            if ((bytesRcvd = in.read(recvdata, totalBytesRcvd, recvdata.length
                    - totalBytesRcvd)) == -1)
                throw new SocketException("Connection closed prematurely");
            totalBytesRcvd += bytesRcvd;
        } // data array is full

        System.out.println("Received: " + new String(recvdata));

        socket.close(); // Close the socket and its streams
    }
}

服务端

import java.net.*; // for Socket, ServerSocket, andInetAddress
import java.io.*; // for IOException and Input/OutputStream

public class TCPServer {

    private static final int BUFSIZE = 32; // Size of receivebuffer

    public static void main(String[] args) throws IOException {

        if (args.length != 1) // Test for correct # of args
            throw new IllegalArgumentException("Parameter(s): ");

        int servPort = Integer.parseInt(args[0]);

        /* Create a server socket to accept client connection requests */
        ServerSocket servSock = new ServerSocket(servPort);

        int recvMsgSize; // Size of received message
        byte[] receiveBuf = new byte[BUFSIZE]; // Receive buffer

        while (true) { // Run forever, accepting and servicing connections

            Socket clntSock = servSock.accept(); // Get client connection

            SocketAddress clientAddress = clntSock.getRemoteSocketAddress();

            System.out.println("Handling client at " + clientAddress);

            InputStream in = clntSock.getInputStream();
            OutputStream out = clntSock.getOutputStream();

            /* Receive until client closes connection, indicated by -1 return*/

            while ((recvMsgSize = in.read(receiveBuf)) != -1) {

                out.write(receiveBuf, 0, recvMsgSize);
            }
            clntSock.close(); // Close the socket. We are done with this client!
        }
        /* NOT REACHED */

    }
}

Demo2.

客户端从控制台输入字符串,发送到服务端
服务端受到客户端的字符串后,加上部分信息(echo字符串)返回到客户端

客户端

package zyb.org.client;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.Socket;
import java.net.SocketTimeoutException;

public class Client {
    public static void main(String[] args) throws IOException {
        //客户端请求与本机在20006端口建立TCP连接 
        Socket client = new Socket("127.0.0.1", 20006);
        client.setSoTimeout(10000);
        //获取键盘输入 
        BufferedReader input = new BufferedReader(new InputStreamReader(System.in));
        //获取Socket的输出流,用来发送数据到服务端  
        PrintStream out = new PrintStream(client.getOutputStream());
        //获取Socket的输入流,用来接收从服务端发送过来的数据  
        BufferedReader buf =  new BufferedReader(new InputStreamReader(client.getInputStream()));
        boolean flag = true;
        while(flag){
            System.out.print("输入信息:");
            String str = input.readLine();
            //发送数据到服务端  
            out.println(str);
            if("bye".equals(str)){
                flag = false;
            }else{
                try{
                    //从服务器端接收数据有个时间限制(系统自设,也可以自己设置),超过了这个时间,便会抛出该异常
                    String echo = buf.readLine();
                    System.out.println(echo);
                }catch(SocketTimeoutException e){
                    System.out.println("Time out, No response");
                }
            }
        }
        input.close();
        if(client != null){
            //如果构造函数建立起了连接,则关闭套接字,如果没有建立起连接,自然不用关闭
            client.close();    //只关闭socket,其关联的输入输出流也会被关闭
        }
    }
}

服务端

package zyb.org.server;

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

public class Server {
    public static void main(String[] args) throws Exception{
        //服务端在20006端口监听客户端请求的TCP连接
        ServerSocket server = new ServerSocket(20006);
        Socket client = null;
        boolean f = true;
        while(f){
            //等待客户端的连接,如果没有获取连接
            client = server.accept();
            System.out.println("与客户端连接成功!");
            //为每个客户端连接开启一个线程
            new Thread(new ServerThread(client)).start();
        }
        server.close();
    }
}

服务端 线程类

package zyb.org.server;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.Socket;

/**
 * 该类为多线程类,用于服务端
 */
public class ServerThread implements Runnable {

    private Socket client = null;
    public ServerThread(Socket client){
        this.client = client;
    }
    
    @Override
    public void run() {
        try{
            //获取Socket的输出流,用来向客户端发送数据
            PrintStream out = new PrintStream(client.getOutputStream());
            //获取Socket的输入流,用来接收从客户端发送过来的数据
            BufferedReader buf = new BufferedReader(new InputStreamReader(client.getInputStream()));
            boolean flag =true;
            while(flag){
                //接收从客户端发送过来的数据
                String str =  buf.readLine();
                if(str == null || "".equals(str)){
                    flag = false;
                }else{
                    if("bye".equals(str)){
                        flag = false;
                    }else{
                        //将接收到的字符串前面加上echo,发送到对应的客户端
                        out.println("echo:" + str);
                    }
                }
            }
            out.close();
            client.close();
        }catch(Exception e){
            e.printStackTrace();
        }
    }

}

UDP Socket

UDP协议提供的服务不同于TCP协议的端到端服务,它是面向非连接的,属不可靠协议,UDP套接字在使用前不需要进行连接。实际上,UDP协议实现了两个功能:

1)在IP协议的基础上添加了端口;
2)对传输过程中可能产生的数据错误进行了检测,并抛弃已经损坏的数据。 UDP的Java支持

Java通过DatagramPacket类和DatagramSocket类来使用UDP套接字,客户端和服务器端都通过DatagramSocket的send()方法和receive()方法来发送和接收数据,用DatagramPacket来包装需要发送或者接收到的数据。发送信息时,Java创建一个包含待发送信息的DatagramPacket实例,并将其作为参数传递给DatagramSocket实例的send()方法;
接收信息时,Java程序首先创建一个DatagramPacket实例,该实例预先分配了一些空间,并将接收到的信息存放在该空间中,然后把该实例作为参数传递给DatagramSocket实例的receive()方法。
在创建DatagramPacket实例时,要注意:如果该实例用来包装待接收的数据,则不指定数据来源的远程主机和端口,只需指定一个缓存数据的byte数组即可(在调用receive()方法接收到数据后,源地址和端口等信息会自动包含在DatagramPacket实例中),而如果该实例用来包装待发送的数据,则要指定要发送到的目的主机和端口。

UDP的通信建立的步骤

UDP客户端首先向被动等待联系的服务器发送一个数据报文。一个典型的UDP客户端要经过下面三步操作:

1、创建一个DatagramSocket实例,可以有选择地对本地地址和端口号进行设置,如果设置了端口号,则客户端会在该端口号上监听从服务器端发送来的数据;
2、使用DatagramSocket实例的send()和receive()方法来发送和接收DatagramPacket实例,进行通信;
3、通信完成后,调用DatagramSocket实例的close()方法来关闭该套接字。

由于UDP是无连接的,因此UDP服务端不需要等待客户端的请求以建立连接。另外,UDP服务器为所有通信使用同一套接字,这点与TCP服务器不同,TCP服务器则为每个成功返回的accept()方法创建一个新的套接字。一个典型的UDP服务端要经过下面三步操作:

1、创建一个DatagramSocket实例,指定本地端口号,并可以有选择地指定本地地址,此时,服务器已经准备好从任何客户端接收数据报文;
2、使用DatagramSocket实例的receive()方法接收一个DatagramPacket实例,当receive()方法返回时,数据报文就包含了客户端的地址,这样就知道了回复信息应该发送到什么地方;
3、使用DatagramSocket实例的send()方法向服务器端返回DatagramPacket实例。

Java-socket网络编程_第2张图片
这里有一点需要注意:

UDP程序在receive()方法处阻塞,直到收到一个数据报文或等待超时。由于UDP协议是不可靠协议,如果没有收到DatagramPacket,那么程序将会一直阻塞在receive()方法处,这样客户端将永远都接收不到服务器端发送回来的数据,但是又没有任何提示。为了避免这个问题,我们在客户端使用DatagramSocket类的setSoTimeout()方法来制定receive()方法的最长阻塞时间,并指定重发数据报的次数,如果每次阻塞都超时,并且重发次数达到了设置的上限,则关闭客户端。

TCP Demo

一个客户端服务端UDP通信的Demo(没有用多线程),该客户端在本地2222端口监听接收到的数据,并将字符串"Hello UDPserver"发送到本地服务器的3222端口,服务端在本地3222端口监听接收到的数据,如果接收到数据,则返回字符串"Hello UDPclient"到该客户端的2222端口。在客户端,由于程序可能会一直阻塞在receive()方法处,因此这里我们在客户端用DatagramSocket实例的setSoTimeout()方法来指定receive()的最长阻塞时间TIMEOUT ,并设置重发数据的次数MAXNUM ,如果最终依然没有接收到从服务端发送回来的数据,我们就关闭客户端。

客户端

import java.io.IOException;
import java.io.InterruptedIOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;

public class UdpClient {
    private static final int MAXNUM = 5; // 设置重发数据的最多次数
    private static final int TIMEOUT = 5000;  //设置接收数据的超时时间
    private static final int CLIENT_PORT = 2222;
    private static final int SERVER_PORT = 3222;
    private static final int REV_SIZE = 1024; //接收数据的存储空间大小
    
    public static void main(String[] args) throws IOException {
        String str_send = "Hello UDPserver"; //要发送的字串
        byte[] buf_rev = new byte[REV_SIZE];     //要接收的存储空间
        
        /*第一步 实例化DatagramSocket*/
        DatagramSocket mSoc = new DatagramSocket(CLIENT_PORT);
        mSoc.setSoTimeout(TIMEOUT);              //设置接收数据时阻塞的最长时间  
        
        /*第二步 实例化用于发送的DatagramPacket和用于接收的DatagramPacket*/
        InetAddress inetAddress = InetAddress.getLocalHost();
        DatagramPacket data_send = new DatagramPacket(str_send.getBytes(),
                str_send.length(), inetAddress, SERVER_PORT);
        
        DatagramPacket data_rev = new DatagramPacket(buf_rev, REV_SIZE);

        
        /*第三步 DatagramPacket send发送数据,receive接收数据*/
        int send_count = 0; // 重发数据的次数
        boolean revResponse = false; // 是否接收到数据的标志位
        while (!revResponse && send_count < MAXNUM) {
            try {
                mSoc.send(data_send); //发送数据
                mSoc.receive(data_rev);//接收数据
                if (!data_rev.getAddress().getHostAddress()
                        .equals(InetAddress.getLocalHost().getHostAddress())) {
                    throw new IOException(
                            "Received packet from an umknown source");
                }
                revResponse = true;
            } catch (InterruptedIOException e) {
                // 如果接收数据时阻塞超时,重发并减少一次重发的次数
                send_count += 1;
                System.out.println("Time out," + (MAXNUM - send_count)
                        + " more tries...");
            }
        }
        if (revResponse) {
            // 如果收到数据,则打印出来
            System.out.println("client received data from server:");
            String str_receive = new String(data_rev.getData(), 0,
                    data_rev.getLength())
                    + " from "
                    + data_rev.getAddress().getHostAddress()
                    + ":"
                    + data_rev.getPort();
            System.out.println(str_receive);
            // 由于dp_receive在接收了数据之后,其内部消息长度值会变为实际接收的消息的字节数,
            // 所以这里要将dp_receive的内部消息长度重新置为1024
            data_rev.setLength(REV_SIZE);
        } else {
            // 如果重发MAXNUM次数据后,仍未获得服务器发送回来的数据,则打印如下信息
            System.out.println("No response -- give up.");
        }
        
        /*第四步 关闭DatagramPacket*/
        mSoc.close();
    }

}

服务端

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;

public class UdpServer {

    private static final int SERVER_PORT = 3222;
    private static final int REV_SIZE = 1024; // 接收数据的存储空间大小

    public static void main(String[] args) throws IOException {
        byte[] buf_rev = new byte[REV_SIZE];
        String str_send = "Hello UDPclient";
        /* 第一步 实例化DatagramSocket */
        DatagramSocket mSoc = new DatagramSocket(SERVER_PORT);

        /* 第二步 实例化用于接收的DatagramPacket 并从DatagramSocket接收数据 */
        DatagramPacket data_rev = new DatagramPacket(buf_rev, REV_SIZE);
        boolean f = true;
        while (f) {
            mSoc.receive(data_rev);
            InetAddress inetAddress = data_rev.getAddress();
            int port = data_rev.getPort();
            System.out.println("server received data from client:");
            String str_rev = new String(data_rev.getData(), 0,
                    data_rev.getLength())
                    + " from " + inetAddress.getHostAddress() + ":" + port;
            System.out.println(str_rev);

            /* 第三步 实例化用于发送的DatagramPacket,并从DatagramSocket中发送出去 */
            DatagramPacket data_send = new DatagramPacket(str_send.getBytes(),
                    str_send.length(), inetAddress, port);
            mSoc.send(data_send);

            /*
             * 由于dp_receive在接收了数据之后,其内部消息长度值会变为实际接收的消息的字节数,
             * 所以这里要将dp_receive的内部消息长度重新置为1024
             */
            data_rev.setLength(REV_SIZE);
        }
        mSoc.close();

    }
}

注意

1、UDP套接字和TCP套接字的一个微小但重要的差别:UDP协议保留了消息的边界信息。

DatagramSocket的每一次receive()调用最多只能接收调用一次send()方法所发送的数据,而且,不同的receive()方法调用绝对不会返回同一个send()方法所发送的额数据。

当在TCP套接字的输出流上调用write()方法返回后,所有调用者都知道数据已经被复制到一个传输缓存区中,实际上此时数据可能已经被发送,也有可能还没有被传送,而UDP协议没有提供从网络错误中恢复的机制,因此,并不对可能需要重传的数据进行缓存。这就意味着,当send()方法调用返回时,消息已经被发送到了底层的传输信道中。

2、UDP数据报文所能负载的最多数据,亦及一次传送的最大数据为65507个字节

当消息从网络中到达后,其所包含的数据被TCP的read()方法或UDP的receive()方法返回前,数据存储在一个先进先出的接收数据队列中。对于已经建立连接的TCP套接字来说,所有已接受但还未传送的字节都看作是一个连续的字节序列。然而,对于UDP套接字来说,接收到的数据可能来自不同的发送者,一个UDP套接字所接受的数据存放在一个消息队列中,每个消息都关联了其源地址信息,每次receive()调用只返回一条消息。如果receive()方法在一个缓存区大小为n的DatagramPacket实例中调用,而接受队里中的第一条消息的长度大于n,则receive()方法只返回这条消息的前n个字节,超出部分会被自动放弃,而且对接收程序没有任何消息丢失的提示!

出于这个原因,接受者应该提供一个有足够大的缓存空间的DatagramPacket实例,以完整地存放调用receive()方法时应用程序协议所允许的最大长度的消息。一个DatagramPacket实例中所允许传输的最大数据量为65507个字节,也即是UDP数据报文所能负载的最多数据。因此,可以用一个65600字节左右的缓存数组来接受数据。

3、DatagramPacket的内部消息长度值在接收数据后会发生改变,变为实际接收到的数据的长度值。

每一个DatagramPacket实例都包含一个内部消息长度值,其初始值为byte缓存数组的长度值,而该实例一旦接受到消息,这个长度值便会变为接收到的消息的实际长度值,这一点可以用DatagramPacket类的getLength()方法来测试。如果一个应用程序使用同一个DatagramPacket实例多次调用receive()方法,每次调用前就必须显式地将其内部消息长度重置为缓存区的实际长度,以免接受的数据发生丢失。

4、DatagramPacket的getData()方法总是返回缓冲区的原始大小,忽略了实际数据的内部偏移量和长度信息。

由于DatagramPacket的getData()方法总是返回缓冲数组的原始大小,即刚开始创建缓冲数组时指定的大小,在上面程序中,该长度为1024,因此如果我们要获取接收到的数据,就必须截取getData()方法返回的数组中只含接收到的数据的那一部分。

我们可以使用Arrays.copyOfRange()方法来实现,只需一步便可实现以上功能:

byte[] destbuf = Arrays.copyOfRange(data_rev.getData(),data_rev.getOffset(), data_rev.getOffset() + data_rev.getLength());

当然,如果要将接收到的字节数组转换为字符串的话,也可以采用本程序中直接new一个String对象的方法:

new String(data_rev.getData(),data_rev.getOffset(),data_rev.getOffset() + data_rev.getLength());

你可能感兴趣的:(Java)