java的UDP(二)

news/2024/12/2 20:00:48/

文章目录

  • 1. DatagramSocket类
  • 2. 简单的UDP客户端
  • 3. DatagramChannel

1. DatagramSocket类

要收发DatagramPacket,必须打开一个数据报Socket。在java中,数据报Socekt通过DatagramSocekt类创建和访问。服务器Socket需要指定绑定端口,而用户端Socket不用关心(其他方面两个没有什么区别)。

  • 构造函数

DatagramSocekt构造函数用于不同的情况,这与DatagramSocket构造函数类似。

public DatagramSocket() throws SocketException

在匿名本地端口打开一个数据报Socket,一般用于客户端。用户不用关心端口,服务器会将响应发送到发出数据报的端口。让系统分配一个端口。如果出于某些原因需要知道本地端口,可以使用getLocalPort()方法得到。

public DatagramSocket(int port) throws SocektException

这个构造函数创建一个在指定端口监听入站数据报的Socket。TCP端口和UDP端口没有任何关联。对于两个不同的程序,如果一个使用UDP而另一个使用TCP,那么它们可以使用相同的端口号。

public DatagramSocket(int port,InetAddress interface) throws SocketException

创建一个绑定到指定端口和指定网络接口的DatagramSocket对象

public DatagramSocket(SocketAddress interface)throws SocketException

这个构造函数和前一个相似就,只是网络接口地址和端口,由SocektAddress读取。

protected DatagramSocket(DatagramSocketImpl impl) throws SocketException

这个构造函数允许子类提供自己的UDP实现,而不是默认实现。与其他4个构造函数创建的Socket不同,这个Socket一开始没有与端口绑定。使用前必须啊通过bind()方法绑定到一个SocketAddress。可以向这个方法传递null,将Socket绑定到任何可用的地址和端口

  • 发送和接受数据报

DatagramSocket类的首要任务是发送和接受UDP数据,一个Socket既可以发送又接收数据报,事实上,它可以和多台主机收发数据。

public void send(DatagramPacket dp) throws IOException

下面是一个基于UDP的discard客户端。它从System.in读取用户输入的行就,将其发送给discard服务器,这个服务器只是丢弃所有数据。每一行都填充在一个DatagramPacket中。

 public static void main(String[] args)  {try(DatagramSocket thesocket=new DatagramSocket()){InetAddress server=InetAddress.getByName("localhost");BufferedReader userInput=new BufferedReader(new InputStreamReader(System.in));while(true){String theline=userInput.readLine();if(theline.equals("."))break;byte[] data=theline.getBytes();DatagramPacket thoutput=new DatagramPacket(data,data.length,server,8080);thesocket.send(thoutput);}} catch (SocketException | UnknownHostException e) {throw new RuntimeException(e);} catch (IOException e) {throw new RuntimeException(e);}}
public void receive(DatagramPacket dp)throws IOException

这个方法从网络中接收一个UDP数据报,存储在现有的DatagramPacket对象dp中。与ServerSocket类的accept方法类似,这个方法会阻塞调用线程,直到数据报到达。如果程序除了等待数据报外还有其他操作,就应当在单独线程中调用receive方法

public void close()

调用DatagramSocket对象的close方法将释放该Socket占用的端口。与流和TCP Socket一样。

public int getLocalPort()

DatagramSocket的getLocalPort()方法返回一个int,表示Socket正在监听的本地端口。如果我们创建DatagramSocket时是系统帮我们选定的端口,此时可以使用这个方法查看系统给我们分配的是什么端口。

public InetAddress getLocalAddress()

DatagramSocket的getLocalAddress() 方法返回一个InetAddress对象,表示Socekt绑定到的本地地址。实际中很少需要这样做。

public SocketAddress getLocalAddress()

该方法返回一个SocketAddress对象,这个对象包装了Socket绑定的本地接口和端口。实际用处也不大

  • 管理连接

与TCP socket不同,数据报Socket不太在意与谁对话。事实上,默认情况下它们可以与任何人对话,但这通常不是你希望的。

public void connect(InetAddress host, int port)

connect()方法并不真正建立TCP意义上的连接。不过,它确实指定了DatagramSocket只对指定远程主机和指定远程端口收发数据包。试图向其他主机发送数据包将抛出一个IllegalArgumentException异常。从其它的主机或其他的主机或其他的端口接收的数据报将被丢弃,没有异常,也没有通知。

public void disconnect()

disconnect()方法中断已连接DatagramSocket的“连接”,从而可以再次收发任何主机和端口的包。

public int getPort()

当且仅当DatagramSocket已连接时,getPort()方法返回它所连接的远程端口。否则返回-1.

public InetAddress getInetAddress()

当且仅当DatagramSocket已连接时,getInetAddress()方法返回它所连接的远程主机的地址。否则返回null

public InetAddress getRemoteSocketAddress()

如果DatagramSocket已连接,该方法返回它所连接的远程主机的地址

  • Socket选项

Java支持6个UDP Socket选项:

  1. SO_TIMEOUT
  2. SO_RCVBUF
  3. SO_SNDBUF
  4. SO_REUSEADDR
  5. SO_BROADCAST
  6. IP_TOS

SO_TIMEOUT
是receive()在抛出InterruptedIOException(IOException 的一个子类)异常前等待入站数据报的时间,以毫秒计算。可以用下面两个方法设置和查看

public void setSoTimeout(int timeout) throws SocketException
public int getSoTimeout() throws IOException

SO_RCVBUF
DatagramSocket的SO_RCVBUF选项与Socket的SO_RCVBUF选项紧密相关。它确定了用于网络I/O的缓冲区大小。对于相当快的连接(如以太网速的连接),较大的缓冲区有助于提升性能,因为在溢出前可以存储更多的入站数据报。与TCP相比,对于UDP,足够大的接收缓冲区甚至更加重要,因为在缓冲区满时到达的的UDP数据报就会丢失,而缓冲区满时到达的TCP数据报最后还会重传。此外,SO_RCVBUF设置了应用程序可以接受的数据报包的大小,接收的缓冲区中放不下的包会不声不响地被丢弃掉。

//它只是建议,具体的底层实现可以忽略这个建议
public void setReceiveBufferSize(int size) throws SocketException
public void getReceiveBufferSize() throws SocketException

如果底层Socket实现不能识别SO_RCVBUF选项,这两个方法都会抛出SocketException异常

SO_REUSEADDR

SO_REUSEADDR选项对于UDP Socket的意义与对于TCP Socket的意义有所不同,对于UDP,该选项可以控制是否允许多个数据报Socket同时绑定到相同端口和地址。如果多个Socket绑定到相同端口,接收的包将复制给绑定的所有Socket

public void setReuseAddress(boolean on) throws SocketException
public boolean getReuseAddress() SocketException

SO_BROADCAST
该选项控制是否允许一个Socket向广播地址收发包,如广播地址192.168.254.255。UDP广播常用于DHCP等协议。

public void setBroadcast(boolean on) throws SocketException
public boolean getBroadcast() throws SocketException

IP_TOS
由于业余流类型由多个IP数据报首部中的IP_TOS字段值来确定,所以它对于UDP与对于TCP同样重要,毕竟包要根据IP地址进行路由和区分优先级,而TCP和UPD都建立在IP基础之上。DatagramSocket的setTrafficClass()和getTrafficClass()方法与Socket中相应方法实际上没有分别。之所以必须在这里重复出现,只是因为DatagramSocket和Socket没有共同的超类

public int getTrafficClass() throws SocketException
public void setTrafficClass() throws SocketException

trafficClass参数是一个整数,表示要设置的流量类别。具体的数值取决于操作系统和网络设备的支持,常见的取值如下:

  • 最高两位(DSCP字段):
    • 0x00:Best Effort
    • 0x08:Express Forwarding
    • 0x10:Assured Forwarding
    • 0x18:Voice-Admit Forwarding
  • 后六位(ECN字段):
    • 0x00:Non-ECN
    • 0x01:ECT (0)
    • 0x02:ECT (1)
    • 0x03:CE (Congestion Encountered)

2. 简单的UDP客户端

一些Internet服务只需要知道客户端的地址和端口,它们会忽略客户端在数据报中发送的数据。所以下面实现一个UDPock简单额客户端

public class QuizCardBuilder {//希望从服务器接收到的数据报的大小private int bufferSize;//等待入站数据报超时时间private int timeout;private InetAddress host;private int port;public QuizCardBuilder(InetAddress address,int port , int bufferSize , int timeout){this.bufferSize=bufferSize;this.host=address;this.port=port;this.timeout=timeout;}public  QuizCardBuilder(InetAddress address, int port, int bufferSize){//没有指定超时时间的话就默认设置为30sthis(address, port , bufferSize ,30000);}public QuizCardBuilder(InetAddress address , int port){//如果没有指定缓冲区的大小,就将缓冲区大小指定为8192this(address, port, 8192, 30000);}public byte[] poke(){try(DatagramSocket socket=new DatagramSocket(0)){DatagramPacket outgoing= new DatagramPacket(new byte[1] , 1 ,host ,port);socket.connect(host,port);socket.send(outgoing);DatagramPacket incoming=new DatagramPacket(new byte[bufferSize],bufferSize);socket.receive(incoming);int numBytes=incoming.getLength();byte[] response=new byte[numBytes];System.arraycopy(incoming.getData(),0,response,0,numBytes);return  response;} catch (SocketException e) {throw new RuntimeException(e);} catch (IOException e) {throw new RuntimeException(e);}}public static void main(String[] args)  {InetAddress host;int port=0;try{host=InetAddress.getByName("time.nist.gov");port=Integer.parseInt("13");} catch (UnknownHostException e) {throw new RuntimeException(e);}try{QuizCardBuilder quizCardBuilder=new QuizCardBuilder(host,port);byte[] response=quizCardBuilder.poke();if(response==null){System.out.println("No response within allotted time");return;}String result=new String(response, "US-ASCII");System.out.println(result);} catch (UnsupportedEncodingException e) {throw new RuntimeException(e);}}
}

3. DatagramChannel

该类用于非阻塞的UDP应用程序,就是SocketChannel和ServerSocketChannel用于非阻塞的TCP应用程序一样。类似于SocketChannel和ServerSocketChannel,,DatagramChannel是SelectChannel的子类,可以注册到一个Selector。如果服务器中一个线程可以管理与多个不同客户端的通信,该类就很有用。不过,UDP天生就比TCP更具异步性,因而实际效果没有那么明显。在UDP汇总,一个数据报Socket可以处理多个客户端的输入和输出请求。该类所增加的就是以非阻塞方式来做到这一点,这样一来,如果网络没有立即准备好发送数据,这些方法就可以迅速返回

  • 打开一个Socket

该类没有公共构造函数,要使用静态方法open创建一个新的DatagramChannel对象

DatagramChannel channel= DatagramChannel.open()

这个通道开始时没有绑定到任何端口,在绑定端口,需要使用Socket()方法访问该通道的对等DatagramSocket对象。例如,下面的代码把通道绑定到3141:

SocketAddress address=new InetSocketAddress(3134);
DatagramSocket socket=channle.socket();
socket.bind(address);

Java 7提供了一个更加方便的方法

SocketAddress address=new InetSocketAddress(3134);
channel.bind(address);
  • 接收

receive方法从通道读取一个数据包,放在一个ByteBuffer中。它返回发送这个包的主机地址

publoic SocketAddress receive(ByteBuffer dst) throws IOException

如果通道是阻塞的,该方法在读取到包之前不会返回,如果通道是非阻塞的,没有包可以读取的情况下这个方法会立即返回null。如果数据报的数据超过了缓冲区的大小,多余的部分会被丢弃。

public static void main(String[] args){DatagramChannel channel =DatagramChannel.open();DatagramSocket socket=channel.socket();SocketAddress address= new InetSocketAddress(PORT);socket.bind(address);ByteBuffer buffer=ByteBuffer.allocateDirect(65507);while(true){SocketAddress client= channel.receive(buffer);buffer.flip();System.out.println(client+"says");while(buffer.hasRemainding()) System.out.write(buffer.get);System.out.println();buffer.close();}
}catch(IOException ex){System.err.println(ex);
}
  • 发送

send()方法将一个数据报从ByteBuffer写入通道,要写到由第二个参数指定的地址:

public int send(ByteBuffer src, SocketAddress target) throws IOException

send方法返回写入的字节数,这可能是要写的缓冲区中的可用的字节数,也可能是0,而不会是其他值,如果通道出于非阻塞模式,而且数据不能立即发送,就会返回0,否则,如果通道不在非阻塞模式,send会等待返回,知道他能发送缓冲区中的全部数据。

public static void main(String[] args){DatagramChannel channel=DatagramChannel.open();DatagramSocket address=channel.socket();SocketAddress address=new InetSocketAddress(7);socket.bind(address);ByteBuffer buffer=ByteBuffer.allocateDirect(65507);while(true){SocketAddress client=channel.receive(buffer);buffer.flip();channel.send(buffer,client);buffer.clear()}
}catch(IOException ex){System.err.println(ex);
}
  • 连接

一旦打开一个数据报通道,可以使用connect()方法,将它连接到一个特定的远程地址:

SocketAddress remote=new InetSocketAddress("time.nist.gov",37)

通道只向这个主机发送数据,或者只从这个主机接收数据,与SocketChannel的Connect()方法不同,这个方法本身不在网络上收发任何包,因为UDP是一种无连接协议。它只是建立一个主机,有数据准备好可以发送时,就会向这个主机发送数据包。因此,这个方法会相当快的返回,不会有任何方面阻塞。它有一个isConnected()方法,当且仅当DatagramSocket连接时,它会返回true。还有一个disconnect断开连接

  • 读取

除了用于特殊用途的receive方法,DatagramChannel还有3个一般的read()方法

public int read(ByteBuffer dst) throws IOException
public long read(ByteBuffer[] dsts) throws IOException
public read(ByteBuffer[] dsts, int offset ,int length) throws IOException

不过这些方法只能用于已连接的通道。也就是说,在调用这些方法之前。必须调用connect将通道连接到某个远程主机快,这使得这些方法适用客户端使用,因为它知道自己和谁在通信,而服务器可能需要和多个客户端通信。者3个方法都只从网络读取一个数据报包。数据报中的数据尽可能多地存储在参数ByteBuffer中。每个方法都会返回读取的字节数,或者如果通道关闭,则返回-1,如果出现羡慕的情况,这个方法会返回0

  1. 通道是非阻塞的,而且没有就绪的包
  2. 数据报中不包含任何数据
  3. 缓冲区已满
  • 写入
    很自然的DatagramChannel有3个write方法,可有可写、散布的通道有这3个方法写入,它们可以用来替代send方法
public int write(ByteBuffer src)throws IOException
public long write(ByteBuffer[] dsts) throws IOException
public long write(ByteBuffer[] dsts, int offset, int length)

同样这些方法也只能用于已经连接的通道。

public class UDPEchoClientWithChannels{public final static int PORT=7;private final static int LIMIT=100;public static void main(String[] args){SocketAddress remote;try{remote=new InetSocketAddress(args[0], PORT);}catch(RuntimeException ex){return;}try(DatagramChannel channel=DatagramChannel.open()){//开启非阻塞模式channel.configureBlocking(false);cahnnel.connect(remote);Selector selector=Selector.open();channel.register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE);ByteBuffer buffer= ByteBuffer.allocote(4);int n=0;int numbersRead=0;while(true){if(numbersRead==LIMIT)break;//为一个连接等待1分钟selector.select(60000);Set(SelectionKey> readyKeys= selector.selectKeys();if(readyKeys.isEmpty() && n==LIMIT){//所有的包已经写入//好像不会再有更多数据从网络到达break;}else{Iterator<SelectionKey> iterator=readyKeys.iterator();while(iterator.hasNext()){SelectionKey key=(SelectionKey) iterator.next();iterator.remove();if(key.isReachable){buffer.clear();channel.read(buffer);buffer.flip();int echo=buffer.getInt();System.out.println("Read:"+echo);numbersRead++;}if(key.isWritable()){buffer.clear();buffer.putInt(n);buffer.flip();channel.write(buffer);System.out.println("Wrote:"+n);n++;if(n==LIMIT){key.interestOps(SelectionKey.OP_READ);}}}}}}catch(IOException ex){}
}
}
  • 关闭

就像常规的Socket一样,应当在结束操作时关闭通道,释放它使用的端口和任何其他资源

public void close() throws IOException

关闭已关闭的通道没有任何效果。试图向已关闭的通道写入或读取数据会抛出异常。如果不确定一个通道是否关闭可以使用isOpen方法查看。与所有通道一样,在Java 7中实现了AutoCloseable。

try(DatagramChannel channel= DatagramChannel.open()){} catch(IOException e){}

在java 7 以后版本中,DatagramChannel支持8个Socket选项

在这里插入图片描述

前5个选项与之前介绍的Socket选项一致。后3个会在IP组播中使用。这些选项可以用3个方法来检查和设置

//改变选项值
public<T> DatagramChannel setOption(SocketOption<T> name ,T value)throws IOException
//指出任意一个选项的当前值
public<T> T getOption(SocketOption<T> name) throws IOException
//会列出所有的可用的Socket选项
public Set<SocketOption<?>> supportedOptions()
public class QuizCardBuilder {public static void main(String[] args)  {try(DatagramChannel channel= DatagramChannel.open()){for(SocketOption<?> option: channel.supportedOptions()){System.out.println(option.name()+":"+channel.getOption(option));}} catch (IOException e) {throw new RuntimeException(e);}}
}

在这里插入图片描述


http://www.ppmy.cn/news/203978.html

相关文章

在Windows中安装极狐GitLab Runner

官方文档&#xff1a;https://docs.gitlab.cn/runner/install/windows.html 在Windows中安装极狐GitLab Runner 1.下载GitLab Runner二进制文件&#xff0c;新建一个文件夹&#xff08;不要有中文&#xff09;&#xff0c;并将二进制文件放入该文件夹&#xff0c;重命名为“git…

a5解锁 oppo_oppoa5忘记密码了怎么强制解锁

大家好&#xff0c;我是时间财富网智能客服时间君&#xff0c;上述问题将由我为大家进行解答。 oppoa5忘记密码了强制解锁的方法&#xff1a; 1、长按手机的电源键&#xff0c;将手机关机。 2、同时按住电源键和音量上键&#xff0c;进入OPPO恢复模式。 3、用音量键上下翻页&am…

oppo计算机找不到,oppo手机文件在电脑上无法读取怎么办

大家在使用oppo手机的时候有没有遇到过手机文件在电脑上无法读取的情况。下面由学习啦小编为你整理了oppo手机文件在电脑上无法读取怎么办的相关方法&#xff0c;希望对你有帮助! oppo手机文件电脑无法读取解决方法如下 一般来说&#xff0c;当用USB数据线连接手机和电脑后&…

oppo开启系统更新服务器,oppo手机系统升级开不了机怎么办

大家好&#xff0c;我是时间财富网智能客服时间君&#xff0c;上述问题将由我为大家进行解答。 oppo手机系统升级开不了机的原因及解决方法如下&#xff1a; 1、可能是因为手机电池没有电了导致的&#xff0c;取出手机数据线&#xff0c;请充电半小时后再按电源键看能否开机&am…

oppo怎么修改dns服务器地址,OPPO R7/R7 Plus修改DNS图文教程

OPPO R7/R7 Plus怎么修改DNS&#xff1f;以下是操作方法 1、进入WLAN设置界面 ▲打开设置 - - WLAN&#xff0c;进入wlan设置界面 - 长按已经连接上的网络名称 2、找到“修改网络” ▲接着弹出来一个选项框 - 选择“修改网络” - 勾选“显示高级选项” 3、将“IP设置”改成“静…

oppo手机怎么分屏android,OPPO手机怎么分屏 OPPO手机设置分屏模式的方法

OPPO手机怎么分屏? 现在的新款oppo手机都是支持分屏功能的&#xff0c;开启了分屏功能后&#xff0c;OPPO手机就可以一边看视频一边聊天啦&#xff0c;如何在一个屏幕上观看两个应用的画面呢。下面新机汇小编告诉大家具体的OPPO分屏设置方法&#xff0c;一起来了解一下&#x…

oppo手机鸿蒙系统安装教程,oppo手机怎么刷机的步骤如下

如果能进入recovery&#xff0c;刷机。 进入Recovery模式方法&#xff1a; 方法一&#xff1a;如果手机是开机状态&#xff0c;请先关机&#xff0c;抠下电池再装上&#xff0c;在关机情况下&#xff0c;同时按住电源键 小房子(Home)键&#xff0c;直到出现Recovery界面为止。…

app上架oppo应用商店流程

目录 一、登录oppo开放平台二、点击【发布应用】三、选择主体类型四、按步骤分别进行实名认证、录入企业信息与银行账号信息、输入打款金额、完成认证五、进入“管理中心”->“应用服务平台”六、点击【创建应用】七、选择应用类型为“普通应用”八、输入应用名称、应用包名…