Java网络编程socket基础学习

(注:有些图片被吞了看不了也没事,因为是运行结果截图,不影响学习)

InetAddress类

InetAddress没有公有构造方法,不能通过new创建对象

    /**
     * Constructor for the Socket.accept() method.
     * This creates an empty InetAddress, which is filled in by
     * the accept() method.  This InetAddress, however, is not
     * put in the address cache, since it is not created by name.
     */
    InetAddress() {
        holder = new InetAddressHolder();
    }

使用InetAddress获取本地主机名和ip地址

    public static void main(String[] args) throws UnknownHostException {
        InetAddress inetAddress = InetAddress.getLocalHost();
        System.out.println("主机名: " + inetAddress.getHostName());
        System.out.println(inetAddress.toString());

        InetAddress inetAddress2 = InetAddress.getByName("220.181.38.150");
        System.out.println("主机名2: " + inetAddress2.getCanonicalHostName());
        System.out.println(inetAddress2.toString());
    }

在这里插入图片描述

URL类

解析url字符串,这个url可能访问无效,只要长得像一个url就行

    public static void main(String[] args) {
        try {
            URL url = new URL("http://www.tsinghua.edu.cn/chn/index.htm");
            System.out.println("the Protocol: " + url.getProtocol());
            System.out.println("the hostname: " + url.getHost());
            System.out.println("the port: " + url.getPort());
            System.out.println("the file: " + url.getFile());
            System.out.println(url.toString());
        } catch (MalformedURLException e) {
            e.printStackTrace();
        }

在这里插入图片描述

        //使用URL类的openstream()方法打开连接,实质还是利用URLConnection
package com.index.network;

import jdk.internal.util.xml.impl.Input;

import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;

/**
 * URL对象的创建及使用
 * @author index
 * @date 2020/9/20
 **/
public class Myurl {
    public static void main(String[] args) {
        try {
            URL url = new URL("http://www.baidu.com");
            System.out.println("the Protocol: " + url.getProtocol());
            System.out.println("the hostname: " + url.getHost());
            System.out.println("the port: " + url.getPort());
            System.out.println("the file: " + url.getFile());
            System.out.println(url.toString());

            //使用URL类的openstream()方法打开连接
            InputStream in = url.openStream();
            int c;
            System.out.println("输出放回的response:");
            while((c = in.read()) != -1){
                System.out.print((char)c);
            }
            in.close();

        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

在这里插入图片描述

源码码:

    /**
     * Opens a connection to this {@code URL} and returns an
     * {@code InputStream} for reading from that connection. This
     * method is a shorthand for:
     * <blockquote><pre>
     *     openConnection().getInputStream()
     * </pre></blockquote>
     *
     * @return     an input stream for reading from the URL connection.
     * @exception  IOException  if an I/O exception occurs.
     * @see        java.net.URL#openConnection()
     * @see        java.net.URLConnection#getInputStream()
     */
    public final InputStream openStream() throws java.io.IOException {
        return openConnection().getInputStream();
    }

    /**
     * Returns a {@link java.net.URLConnection URLConnection} instance that
     * represents a connection to the remote object referred to by the
     * {@code URL}.
     *
     * <P>A new instance of {@linkplain java.net.URLConnection URLConnection} is
     * created every time when invoking the
     * {@linkplain java.net.URLStreamHandler#openConnection(URL)
     * URLStreamHandler.openConnection(URL)} method of the protocol handler for
     * this URL.</P>
     *
     * <P>It should be noted that a URLConnection instance does not establish
     * the actual network connection on creation. This will happen only when
     * calling {@linkplain java.net.URLConnection#connect() URLConnection.connect()}.</P>
     *
     * <P>If for the URL's protocol (such as HTTP or JAR), there
     * exists a public, specialized URLConnection subclass belonging
     * to one of the following packages or one of their subpackages:
     * java.lang, java.io, java.util, java.net, the connection
     * returned will be of that subclass. For example, for HTTP an
     * HttpURLConnection will be returned, and for JAR a
     * JarURLConnection will be returned.</P>
     *
     * @return     a {@link java.net.URLConnection URLConnection} linking
     *             to the URL.
     * @exception  IOException  if an I/O exception occurs.
     * @see        java.net.URL#URL(java.lang.String, java.lang.String,
     *             int, java.lang.String)
     */
    public URLConnection openConnection() throws java.io.IOException {
        return handler.openConnection(this);
    }

URLConnection类

URLConnection是一个抽象类,除了connect()方法,其他方法都已经实现。表示指向URL指定资源的活动连接。
子类:
在这里插入图片描述

copy一段常用方法:(参考https://www.cnblogs.com/lukelook/p/11236481.html)

String getContentType()

  返回 Content-type 头字段的值。即数据的MIME内容类型。若类型不可用,则返回null。

  除了HTTP协议,极少协议会使用MIME首部。若内容类型是文本。则Content-type首部可能会包含一个标识内容编码方式的字符集。

例:Content-type:text/html; charset=UTF-8

 

int getContentLength()

  返回 Content-length 头字段的值。该方法获取内容的字节数。许多服务器只有在传输二进制文件才发送Content-length首部,在传输文本文件时并不发送。若没有该首部,则返回-1。

  若需要知道读取的具体字节数,或需要预先知道创建足够大的缓冲区来保存数据时,可以使用该方法。

 

String getContentEncoding()

  返回 Content-encoding 头字段的值。获取内容的编码方式。若内容无编码,则该方法返回null。

  注意:Content-encoding(内容编码)与字符编码不同。字符编码方式由Content-type首部或稳定内容的信息确定,它指出如何使用字节指定字符。内容编码方式则指出字节如何编码其他字节。

例:若编码格式为x-gzip,则可以使用java.util.zip.GZipInputStream来解码。

 

long getDate()

  返回 date 头字段的值。获取请求的发送时间。即自190011日子夜后过去的毫秒数。

  注意:这是从服务器的角度看到的发送时间。可能与本地时间不一致。若首部不包括Date字段,则返回0long getExpiration()

  返回 expires 头字段的值。获取Expires的值。若无该字段,则返回00即表示不过期,永远缓存。

  注意:该值是自197011日上午12:00后的毫秒数。

 

long getLastModified()

  返回 last-modified 头字段的值。该值是自190011日子夜后过去的毫秒数。若无该字段,则返回0

测试与输出:

package com.index.network;

import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.util.Date;

/**
 * 使用URLConnection从Web服务器读取文件
 * @author index
 * @date 2020/9/20
 **/
public class URLDemo {
    public static void main(String[] args) throws Exception{
        System.out.println("starting...");
        int c;
        URL url = new URL("http://www.baidu.com");
        URLConnection urlConnection = url.openConnection();
        System.out.println("the date is :" + new Date(urlConnection.getDate()));
        System.out.println("content_type:" + urlConnection.getContentType());
        InputStream in = urlConnection.getInputStream();
        while(((c = in.read())!= -1))
            System.out.print((char)c);
        in.close();
    }
}

在这里插入图片描述

使用URLConnection发送post请求

下面代码copy自:https://blog.csdn.net/bingguang1993/article/details/79943340

1.通过在 URL 上调用 openConnection 方法创建连接对象。
2.处理设置参数和一般请求属性,获取URLconnection实例对应的输出流来发送数据。
3.使用 connect 方法建立到远程对象的实际连接。
4.远程对象变为可用。远程对象的头字段和内容变为可访问。

package com.index.network;

import java.io.*;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.List;
import java.util.Map;

/**
 * 使用URLConnection从Web服务器读取文件
 * @author index
 * @date 2020/9/20
 **/
public class URLDemo {
    public static String doPost(String posturl,String params) {


        try {
            //1.通过在 URL 上调用 openConnection 方法创建连接对象
            URL url=new URL(posturl);
            URLConnection conn=url.openConnection();

            //2.处理设置参数和一般请求属性
            //2.1设置参数
            //可以根据请求的需要设置参数
            conn.setDoInput (true);  //默认为true 所以不设置也可以
            conn.setDoOutput(true);  //默认为false 发送post请求必须设置setDoOutput(true)
            conn.setUseCaches(false); //是否可以使用缓存 不使用缓存
            conn.setConnectTimeout(5000);//请求超时时间

            //2.2请求属性
            //设置通用的请求属性 消息报头 即设置头字段 更多的头字段信息可以查阅HTTP协议
            conn.setRequestProperty("accept", "*/*");
            conn.setRequestProperty("connection", "Keep-Alive");

            //2.3设置请求正文 即要提交的数据
            PrintWriter pw=new PrintWriter(new OutputStreamWriter(conn.getOutputStream()));
            pw.print(params);
            pw.flush();
            pw.close();

            //3.使用 connect 方法建立到远程对象的实际连接。
            conn.connect();

            //4.远程对象变为可用。远程对象的头字段和内容变为可访问。
            //4.1获取响应的头字段
            Map<String, List<String>> headers=conn.getHeaderFields();
            System.out.println(headers); //输出头字段

            //4.2获取响应正文
            BufferedReader reader = null;
            StringBuffer resultBuffer = new StringBuffer();
            String tempLine = null;

            reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
            while ((tempLine = reader.readLine()) != null) {
                resultBuffer.append(tempLine);
            }
            //System.out.println(resultBuffer);
            reader.close();
            return resultBuffer.toString();
        } catch (MalformedURLException e) {
            // TODO 自动生成的 catch 块
            e.printStackTrace();
        } catch (IOException e) {
            // TODO 自动生成的 catch 块
            e.printStackTrace();
        }
        finally {

        }
        return null;

    }

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

        System.out.println("starting...");
        System.out.println("post请求返回的response信息: ");
        System.out.println(
                doPost("http://127.0.0.1:8080/login", "username=index&password=123456"));
    }
    //"{\"username\":\"index\",\"password\":\"123456\"}"
}

啊输出结果就是登录成功后的页面信息,就不截图了

除了,利用URLConnection,我们其实可以用HttpURLConnection更方便的发送与http有关的get及post请求

例子源于:https://www.cnblogs.com/hsuhung/p/10796739.html
挺详细的。

package com.index.network;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;

/**
 * @author index
 * @date 2020/9/23
 **/
public class HttpURLConnectionDemo {
    /**
     * POST请求
     *
     * @param requestUrl 请求地址
     * @param param 请求数据
     * @return
     */
    public static String post(String requestUrl, String param) {

   HttpURLConnection connection = null;
   InputStream is = null;
    OutputStream os = null;
    BufferedReader br = null;
    String result = null;

    try {
            /** 创建远程url连接对象 */
            URL url = new URL(requestUrl);

            /** 通过远程url对象打开一个连接,强制转换为HttpUrlConnection类型 */
            connection = (HttpURLConnection) url.openConnection();

            /** 设置连接方式:POST */
            connection.setRequestMethod("POST");
            /** 设置连接主机服务器超时时间:15000毫秒 */
            connection.setConnectTimeout(15000);
            /** 设置读取远程返回的数据时间:60000毫秒 */
            connection.setReadTimeout(60000);

            /** 设置是否向httpUrlConnection输出,设置是否从httpUrlConnection读入,此外发送post请求必须设置这两个 */
            // 默认值为:false,当向远程服务器传送数据/写数据时,需要设置为true
            connection.setDoOutput(true);
            // 默认值为:true,当前向远程服务读取数据时,设置为true,该参数可有可无
            connection.setDoInput(true);

            /** 设置通用的请求属性 */
            connection.setRequestProperty("accept", "*/*");
            connection.setRequestProperty("connection", "Keep-Alive");
            connection.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
            // 设置传入参数的格式:请求参数应该是 name1=value1&name2=value2 的形式
            connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");

            /** 通过连接对象获取一个输出流 */
            os = connection.getOutputStream();
            /** 通过输出流对象将参数写出去/传输出去,它是通过字节数组写出的 */
            // 若使用os.print(param);则需要释放缓存:os.flush();即使用字符流输出需要释放缓存,字节流则不需要
            if(param != null && param.length() > 0) {
                os.write(param.getBytes());
            }

            /** 请求成功:返回码为200 */
            if (connection.getResponseCode() == 200) {
                /** 通过连接对象获取一个输入流,向远程读取 */
                is = connection.getInputStream();
                /** 封装输入流is,并指定字符集 */
                br = new BufferedReader(new InputStreamReader(is, "UTF-8"));

                /** 存放数据 */
                StringBuffer sbf = new StringBuffer();
                String line = null;
                while ((line = br.readLine()) != null) {
                    sbf.append(line);
                    sbf.append("\r\n");
                }
                result = sbf.toString();
            }

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            /** 关闭资源 */
            try {

                if (null != br) {
                    br.close();
                }

                if (null != is) {
                        is.close();
                }

                if (null != os) {
                    os.close();
                }

            } catch (Exception e) {
                e.printStackTrace();
            }

            /** 关闭远程连接 */
            // 断开连接,最好写上,disconnect是在底层tcp socket链接空闲时才切断。如果正在被其他线程使用就不切断。
            // 固定多线程的话,如果不disconnect,链接会增多,直到收发不出信息。写上disconnect后正常一些
            connection.disconnect();

            System.out.println("--------->>> POST request end <<<----------");
        }

        return result;
    }

    public static void main(String[] args) {
        System.out.println("starting...");
        System.out.println("post请求返回的response信息: ");
        System.out.println(
                post("http://127.0.0.1:8080/login", "username=index&password=123456"));

    }
}

Socket类

先举例子,注意java服务端socket默认接收的编码是GBK,客户端发送消息前需要指定编码为GBK。当然咯,也可以指定服务端和接收端的编码格式都为UTF-8

例子参考:https://www.cnblogs.com/linkenpark/p/11289018.html

Server:

package com.index.network.socket;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.StandardCharsets;

/**
 * @author index
 * @date 2020/9/23
 **/


public class ServerSocketDemo {

    public static void main(String[] args) {
        try {
            //初始化服务端socket并且绑定9999端口
            ServerSocket serverSocket = new ServerSocket(9999);
            //等待客户端的连接
            Socket socket = serverSocket.accept();
            //获取输入流
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            //读取一行数据
            String str = bufferedReader.readLine();
            //输出打印
            System.out.println(str);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Client:

package com.index.network.socket;

import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.net.Socket;

public class ClientSocketDemo {
    public static void main(String[] args) {
        try {
            Socket socket = new Socket("127.0.0.1", 9999);
            BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(), "GBK"));
            String str = "socket服务端向你发送消息了";
            bufferedWriter.write(str);
            //刷新输入流
            bufferedWriter.flush();
            //关闭socket的输出流
            socket.shutdownOutput();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

先运行Server程序,后运行Client程序,即可在Server端看到响应的输出结果

发送多条消息:
客户端每发送一行,添加一个“\n”标识,服务端就可以读取客户端发来的数据,判断是否到了流的结尾,如果没有到结尾,服务端就会一直阻塞,等待用户新的输入。

Server:

package com.index.network.socket;


import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;

public class ServerSocketDemo2 {
    public static void main(String[] args) {
        try {
            //初始化服务端socket并且绑定9999端口
            ServerSocket serverSocket = new ServerSocket(9999);
            //等待客户端的连接
            Socket socket = serverSocket.accept();
            //获取输入流,并且指定统一的编码格式
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream(), "UTF-8"));
            //读取一行数据
            String str;
            //通过while循环不断读取信息,
            while ((str = bufferedReader.readLine()) != null) {
                //输出打印
                System.out.println(str);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Client:

package com.index.network.socket;

import java.io.*;
import java.net.Socket;

public class ClientSocketDemo2 {
    public static void main(String[] args) {
        try {

            //初始化一个socket
            Socket socket = new Socket("127.0.0.1", 9999);
            //通过socket获取字符流
            BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
            //通过标准输入流获取字符流
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in, "UTF-8"));
            while (true) {
                String str = bufferedReader.readLine();
                bufferedWriter.write(str);
                bufferedWriter.write("\n");
                bufferedWriter.flush();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Client输入:
在这里插入图片描述

Server输出:
在这里插入图片描述

不过上面这种通信是阻塞式的,即客户端A与服务器建立连接的话,客户端B就不能建立连接

解决办法: 在服务端使用多线程
Server:

package com.index.network.socket;


import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;

public class ServerSocketDemo2 {
    public static void main(String[] args) throws IOException {
        //初始化服务端socket并且绑定9999端口
        ServerSocket serverSocket = new ServerSocket(9999);
        int count = 0;
        while(true) {
            //等待客户端的连接
            Socket socket = serverSocket.accept();
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        //获取输入流,并且指定统一的编码格式
                        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream(), "UTF-8"));
                        //读取一行数据
                        String str;

                        System.out.println(Thread.currentThread().getName() + ": ");

                        //通过while循环不断读取信息,
                        while ((str = bufferedReader.readLine()) != null) {
                            //输出打印
                            System.out.println(str);
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }, "线程" + String.valueOf(count) + ": ").start();
        }
    }
}

使用线程池分配线程,而不是每来一个客户端就创建一个线程

Server:

package com.index.network.socket;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ServerSocketDemo3 {
    public static void main(String[] args) throws IOException {
        //初始化服务端socket并且绑定9999端口
        ServerSocket serverSocket = new ServerSocket(9999);
        //创建一个线程池
        ExecutorService executorService = Executors.newFixedThreadPool(100);
        while (true) {
            //等待客户端的连接
            Socket socket = serverSocket.accept();
            Runnable runnable = () -> {
                BufferedReader bufferedReader = null;
                try {
                    bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream(), "UTF-8"));
                    //读取一行数据
                    String str;
                    //通过while循环不断读取信息,
                    while ((str = bufferedReader.readLine()) != null) {
                        //输出打印
                        System.out.println("客户端说:" + str);
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            };
            executorService.submit(runnable);
        }
    }
}

发送指定类型,指定长度的数据包,而不是向前面那样每次发送都是以/n结尾:

Server:

package com.index.network.socket;

import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class ServerSocketDemo4 {
    public static void main(String[] args) {
        try {
            ServerSocket serverSocket = new ServerSocket(9999);
            Socket client = serverSocket.accept();
            InputStream inputStream = client.getInputStream();
            DataInputStream dataInputStream = new DataInputStream(inputStream);
            while (true) {
                byte b = dataInputStream.readByte();
                int len = dataInputStream.readInt();
                byte[] data = new byte[len - 5];
                dataInputStream.readFully(data);
                String str = new String(data);
                System.out.println("获取的数据类型为:" + b);
                System.out.println("获取的数据长度为:" + len);
                System.out.println("获取的数据内容为:" + str);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Client:

package com.index.network.socket;

import java.io.*;
import java.net.Socket;
import java.util.Scanner;

public class ClientSocketDemo4 {
    public static void main(String[] args) {
        try {
            Socket socket =new Socket("127.0.0.1",9999);
            OutputStream outputStream = socket.getOutputStream();
            DataOutputStream dataOutputStream =new DataOutputStream(outputStream);
            Scanner scanner =new Scanner(System.in);
            if(scanner.hasNext()){
                String str = scanner.next();
                int type =1;
                byte[] data = str.getBytes();
                int len = data.length +5;
                dataOutputStream.writeByte(type);
                dataOutputStream.writeInt(len);
                dataOutputStream.write(data);
                dataOutputStream.flush();
            }
        }catch (IOException e) {
            e.printStackTrace();
        }
    }
}

在这里插入图片描述

在这里插入图片描述

DatagramSocket

参考:https://blog.csdn.net/jiangxinyu/article/details/8161044

UDP套接字的使用是通过DatagramPacket类和DatagramSocket类。
UDP客户端和服务端都主要执行如下3个步骤:

1.创建DatagramSocket实例;

2.使用DatagramSocket类的send()和receive()方法发送和接收DatagramPacket实例;

3.最后使用DatagramSocket类的close()方法销毁该套接字。

例子:设置在receive()方法上最多阻塞等待3秒,最多重发次数为5

Server:

package com.index.network.socket;


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

public class UDPEchoServer {

    private static final int ECHOMAX = 255; // 发送或接收的信息最大字节数

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


        final int servPort = 9999;

        DatagramSocket socket = new DatagramSocket(servPort);
        DatagramPacket packet = new DatagramPacket(new byte[ECHOMAX], ECHOMAX);

        while (true) { // 不断接收来自客户端的信息及作出相应的响应
            socket.receive(packet); // Receive packet from client
            System.out.println("Handling client at " + packet.getAddress().getHostAddress() + " on port " + packet.getPort());
            socket.send(packet); // 将客户端发送来的信息返回给客户端
            packet.setLength(ECHOMAX);
            // 重置packet的内部长度,因为处理了接收到的信息后,数据包的内部长度将被
            //设置为刚处理过的信息的长度,而这个长度可能比缓冲区的原始长度还要短,
            //如果不重置,而且接收到的新信息长于这个内部长度,则超出长度的部分将会被截断,所以这点必须注意到。
        }
    }
}

Client:

package com.index.network.socket;

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

public class UDPEchoClient {

    private static final int TIMEOUT = 3000;   // 设置超时为3秒
    private static final int MAXTRIES = 5;     // 最大重发次数5次

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


        InetAddress serverAddress = InetAddress.getLocalHost(); // 服务器地址
        // Convert the argument String to bytes using the default encoding
        //发送的信息
        byte[] bytesToSend = "UDP客户端,前来报到".getBytes();

        int servPort = 9999;

        DatagramSocket socket = new DatagramSocket();

        socket.setSoTimeout(TIMEOUT); // 设置阻塞时间

        DatagramPacket sendPacket = new DatagramPacket(bytesToSend, // 相当于将发送的信息打包
                bytesToSend.length, serverAddress, servPort);

        DatagramPacket receivePacket =                              // 相当于空的接收包
                new DatagramPacket(new byte[bytesToSend.length], bytesToSend.length);

        int tries = 0;      // Packets may be lost, so we have to keep trying
        boolean receivedResponse = false;
        do {
            socket.send(sendPacket);          // 发送信息
            try {
                socket.receive(receivePacket); // 接收信息

                if (!receivePacket.getAddress().equals(serverAddress)) {// Check source
                    throw new IOException("Received packet from an unknown source");
                }
                receivedResponse = true;
            } catch (InterruptedIOException e) { // 当receive不到信息或者receive时间超过3秒时,就向服务器重发请求
                tries += 1;
                System.out.println("Timed out, " + (MAXTRIES - tries) + " more tries...");
            }
        } while ((!receivedResponse) && (tries < MAXTRIES));

        if (receivedResponse) {
            System.out.println("Received: " + new String(receivePacket.getData()));
        } else {
            System.out.println("No response -- giving up.");
        }
        socket.close();
    }
}

在这里插入图片描述

服务端将接收到的消息原样返回:
在这里插入图片描述

MulticastSocket实现广播

DatagramSocket只允许数据报发送给指定的目标地址,而MulticastSocket可以将数据报以广播的方式发送到多个客户端

MulitcastSocket是DatagramSocket的一个子类,当要发送一个数据报时,可以使用随机端口创建一个MulticastSocket,也可以在指定端口创建MulticastSocket。MulticastSocket提供了如下3个构造器。

1、MulticastSocket():使用本机默认地址、随机端口来创建MulticastSocket对象

2、MulticastSocket(int portNumber)使用本机默认地址、指定端口来创建对象

3、MulticastSocket(SocketAddress bindaddr):使用本机指定IP地址、指定端口来创建对象

创建MulticastSocket对象后,还需要将该MulticastSocket加入到指定的多点广播地址,MulticastSocket使用joinGroup()方法加入指定组;使用leaveGroup()方法脱离一个组。

1、joinGroup(InetAddress multicastAddr):将该MulticastSocket加入指定的多点广播地址。

2、leaveGroup(InetAddress multicastAddr):让该MulticastSocket离开指定的多点广播地址。

例子:将224.0.0.1作为广播地址,监听这个广播地址的listener都算做一个组(通过广播地址向组内的所有Ip地址发送数据包)

ip地址的范围: 参考:https://zhidao.baidu.com/question/53554208.html
在这里插入图片描述
其中D类地址是广播地址,D类IP地址范围224.0.0.1-239.255.255.254

listener1:

package com.index.network.socket;

import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.MulticastSocket;
import java.net.UnknownHostException;

public class MulticastSender {
    private final int port;
    private final String host;
    private final String data;

    public MulticastSender(String data, String host, int port) {
        this.data = data;
        this.host = host;
        this.port = port;
    }

    public void send() {
        try {
            InetAddress ip = InetAddress.getByName(this.host);
            DatagramPacket packet = new DatagramPacket(this.data.getBytes(), this.data.length(), ip, this.port);
            MulticastSocket ms = new MulticastSocket();
            ms.send(packet);
            ms.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws UnknownHostException {
        int port = 1234;
        String host = "224.0.0.1";
        String data = "hello world.";
        System.out.println(data);
        MulticastSender ms = new MulticastSender(data, host, port);
        ms.send();
    }
}

listener2:

package com.index.network.socket;

import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.MulticastSocket;
import java.net.UnknownHostException;

public class MulticastListener2 {
    private final int port;
    private final String host;

    public MulticastListener2(String host, int port) {
        this.host = host;
        this.port = port;
    }

    public void listen() {
        byte[] data = new byte[256];
        try {
            InetAddress ip = InetAddress.getByName(this.host);
            MulticastSocket ms = new MulticastSocket(this.port);
            ms.joinGroup(ip);
            DatagramPacket packet = new DatagramPacket(data, data.length);
            //receive()是阻塞方法,会等待客户端发送过来的信息
            ms.receive(packet);
            String message = new String(packet.getData(), 0, packet.getLength());
            System.out.println(message);
            ms.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws UnknownHostException {
        int port = 1234;
        String host = "224.0.0.1";
        MulticastListener ml = new MulticastListener(host, port);
        while (true) {
            ml.listen();
        }
    }
}

sender:

package com.index.network.socket;

import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.MulticastSocket;
import java.net.UnknownHostException;

public class MulticastSender {
    private final int port;
    private final String host;
    private final String data;

    public MulticastSender(String data, String host, int port) {
        this.data = data;
        this.host = host;
        this.port = port;
    }

    public void send() {
        try {
            InetAddress ip = InetAddress.getByName(this.host);
            DatagramPacket packet = new DatagramPacket(this.data.getBytes(), this.data.length(), ip, this.port);
            MulticastSocket ms = new MulticastSocket();
            ms.send(packet);
            ms.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws UnknownHostException {
        int port = 1234;
        String host = "224.0.0.1";
        String data = "hello world.";
        System.out.println(data);
        MulticastSender ms = new MulticastSender(data, host, port);
        ms.send();
    }
}

运行结果就是一个sender发送,2个listener接收相同的消息
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值