基本套接字:UDP 客户端/服务器端

UDP客户端

 

UDP客户端首先向被动等待联系的服务器端发送一个数据报文。一个典型的UDP客户端主要执行以下三步:

 1. 创建一个DatagramSocket实例,可以选择对本地地址和端口号进行设置。

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

例,进行通信。

 3. 通信完成后,使用DatagramSocket类的close()方法来销毁该套接字。

 Socket类不同,DatagramSocket实例在创建时并不需要指定目的地址。这也是TCP协议和UDP协议的最大不同点之一。在进行数据交换前,TCP套接字必须跟特定主机和另一个端口号上的TCP套接字建立连接,之后,在连接关闭前,该套接字就只能与相连接的那个套接字通信。而UDP套接字在进行通信前则不需要建立连接,每个数据报文都可以发送到或接收于不同的目的地址。(DatagramSocket类的connect()方法确实允许指定远程地址和端口,但该功能是可选的。)

我们的UDP回馈客户端示例程序UDPEchoClientTimeout.java,发送一个带有回馈字符串的数据报文,并打印出从服务器收到的所有信息。一个UDP回馈服务器只是简单地将其收到的数据报文返回给客户端。当然,一个UDP客户端只与一个UDP服务器进行通信。许多系统都集成了UDP回馈服务程序,用于调试和测试。

使用UDP协议的一个后果是数据报文可能丢失。在我们的回馈协议中,客户端的回馈请求信息和服务器端的响应信息都有可能在网络中丢失。回顾前面所介绍的TCP回馈客户端,其发送了一个回馈字符串后,将在read()方法上阻塞等待响应。如果试图在我们的UDP回馈客户端上使用相同的策略,数据报文丢失后,我们的客户端就会永远阻塞在receive()方法上。为了避免这个问题,我们在客户端使用DatagramSocket类的setSoTimeout()方法来指定receive()方法的最长阻塞时间,因此,如果超过了指定时间仍未得到响应,客户端就会重发回馈请求。我们的回馈客户端执行以下步骤:

1. 向服务器端发送回馈字符串。 

2.  receive()方法上最多阻塞等待3秒钟,在超时前若没有收到响应,则重发请求(最多重发5次)。

3. 终止客户端。

 

UDPEchoClientTimeout.java

0 import java.net.DatagramSocket;

1 import java.net.DatagramPacket;

2 import java.net.InetAddress;

3 import java.io.IOException;

4 import java.io.InterruptedIOException;

5

6 public class UDPEchoClientTimeout {

7

8 private static final int TIMEOUT = 3000; // Resend timeout

(milliseconds)

9 private static final int MAXTRIES = 5; // Maximum

retransmissions 

10

11 public static void main(String[] args) throws

IOException {

12

13 if ((args.length < 2) || (args.length > 3)) { // Test

for correct # of args

14 throw new IllegalArgumentException("Parameter(s):

<Server> <Word> [<Port>]");

15 }

16 InetAddress serverAddress =

InetAddress.getByName(args[0]); // Server address

17 // Convert the argument String to bytes using the default

encoding

18 byte[] bytesToSend = args[1].getBytes();

19

20 int servPort = (args.length == 3) ?

Integer.parseInt(args[2]) : 7;

21

22 DatagramSocket socket = new DatagramSocket();

23

24 socket.setSoTimeout(TIMEOUT); // Maximum receive

blocking time (milliseconds)

25

26 DatagramPacket sendPacket = new

DatagramPacket(bytesToSend, // Sending packet

27 bytesToSend.length, serverAddress, servPort);

28

29 DatagramPacket receivePacket = // Receiving packet

30 new DatagramPacket(new byte[bytesToSend.length],

bytesToSend.length);

31

32 int tries = 0; // Packets may be lost, so we have to

keep trying

33 boolean receivedResponse = false;

34 do {

35 socket.send(sendPacket); // Send the echo string

36 try {

37 socket.receive(receivePacket); // Attempt echo reply

reception

38

39 if

(!receivePacket.getAddress().equals(serverAddress)) {//

Check source

40 throw new IOException("Received packet from an unknownsource");

41 }

42 receivedResponse = true;

43 } catch (InterruptedIOException e) { // We did not get

anything

44 tries += 1;

45 System.out.println("Timed out, " + (MAXTRIES - tries)

+ " more tries...");

46 }

47 } while ((!receivedResponse) && (tries < MAXTRIES));

48

49 if (receivedResponse) {

50 System.out.println("Received: " + new

String(receivePacket.getData()));

51 } else {

52 System.out.println("No response -- giving up.");

53 }

54 socket.close();

55 }

56 } 

 

UDPEchoClientTimeout.java

1. 应用程序设置和参数解析:第0-20

2. 创建UDP套接字:第22

DatagramSocket实例能够将数据报文发送给任何UDP套接字。我们没有指定本地地址和端口号,因此程序将自动选择本地地址和可用端口号。如果需要的话,我们也可以通过setLocalAddress()setLocalPort()方法或构造函数,来显式地设置本地地址和端口。

3. 设置套接字超时时间:第24

数据报文套接字的超时时间,用来控制调用receive()方法的最长阻塞时间(毫秒)。本例中我们设置超时时间为3秒。注意,超时时间是不精确的,receive()方法的调用可能会阻塞比这更长的时间(但不会少于超时时间)。

4. 创建发送的数据报文:第26-27

创建一个要发送的数据报文,我们需要指定三件事:数据,目的地址,以及目的端口。对于目的地址,我们可以使用主机名或IP地址来确定一个回馈服务器。若使用的是主机名,它将在构造函数中转换成实际的IP地址。

5. 创建接收的数据报文:第29-30 

创建一个要接收的数据报文,我们只需要定义一个用来存放报文数据的字节数组。而数据报文的源地址和端口号将从receive()方法获得。 

6. 发送数据报文:第32-47

由于数据报文可能丢失,我们必须准备好重新传输数据。本例中,我们最多循环5次,来发送数据报文并尝试接收响应信息。

发送数据报文:第35

send()方法将数据报文传输到其指定的地址和端口号去。 

处理数据报文的接收:第36-46

receive()方法将阻塞等待,直到收到一个数据报文或等待超时。超时信息由InterruptedIOException异常指示。一旦超时,发送尝试计数器(tries))加1,并重新发送。若尝试了最大次数后,仍没有接收到数据报文,循环将退出。如果receive()方法成功接收了数据,我们将循环标记receivedResponse设为true,以退出循环。由于数据报文可能发送自任何地址,我们需要验证所接收的数据报文,检查其源地址和端口号是否与所指定的回馈服务器地址和端口号相匹配。

7. 打印接收结果:第49-53

如果接收到了一个数据报文,即receivedResponsetrue,我们就可以打印出数据报文中的数据信息。

8. 关闭套接字:第54

在学习服务器端代码之前,我们先看看DatagramSocket类的主要方法。 

DatagramSocket:创建

DatagramSocket()

DatagramSocket(int localPort)

DatagramSocket(int localPort, InetAddress localAddr)

以上构造函数将创建一个UDP套接字。可以分别或同时设置本地端口和地址。如果没有指定本地端口,或将其设置为0,该套接字将与任何可用的本地端口绑定。如果没有指定本地地址,数据包(packet)可以接收发送向任何本地地址的数据报文。

 

DatagramSocket:连接与关闭

void connect(InetAddress remoteAddr, int remotePort)

void connect(SocketAddress remoteSockAddr)

void disconnect()

void close()

 connect()方法用来设置套接字的远程地址和端口。一旦连接成功,该套接字就只能与指定的地址和端口进行通信,任何向其他地址和端口发送数据报文的尝试都将抛出一个异常。套接字也将只接收从指定地址和端口发送来的数据报文,从其他地址或端口发送来的数据报文将被忽略。重点提示:连接到多播地址或广播地址的套接字只能发送数据报文,因为数据报文的源地址总是一个单播地址(见第4.3节)。注意,连接仅仅是本地操作,因为与TCP议不同,UDP中没有端对端的数据包交换。disconnect()方法用来清空远程地址和端口号,若存在的话。close()方法表明该套接字不再使用,之后任何发送或接收数据的尝试都将抛出异常。

DatagramSocket:地址处理

InetAddress getInetAddress()

int getPort()

SocketAddress getRemoteSocketAddress()

InetAddress getLocalAddress()

int getLocalPort()

SocketAddress getLocalSocketAddress()

第一个方法返回一个代表所连接的远程套接字地址的InetAddress实例,如果没有连接,则返回null。与之类似,getPort()方法返回所连接的套接字的端口号,若没有连接则返回-1第三个方法一个SocketAddress实例,其中包含了所连接的远程套接字的地址和端口号,如果没有连接,则返回null

 后面三个方法为本地地址和端口提供了类似的服务。如果该套接字没有与本地地址绑定,getLocalAddress()方法将返回通配符地址("任何本地地址")。getLocalPort()方法总是会返回一个本地端口号。如果调用这个方法前该套接字还没有绑定端口号,getLocalPort()方法将选择任意一个可以本地端口与之绑定。getLocalSocketAddress()在套接字没有绑定本地地址时返回null 

DatagramSocket:发送和接收

void send(DatagramPacket packet)

void receive(DatagramPacket packet)

send()方法用来发送DatagramPacket实例。一旦建立连接,数据包将发送到该套接字所连接的地址,除非DatagramPacket实例中已经指定了不同目的地址,这将抛出一个异常。如果没有创建连接,数据包将发送到DatagramPacket实例中指定的目的地址。该方法不阻塞等待。

 receive()方法将阻塞等待,直到接收到数据报文,并将报文中的数据复制到指定的DatagramPacket实例中。如果套接字已经创建了连接,该方法也阻塞等待,直到接收到从所连接的远程套接字发来的数据报文。

 DatagramSocket:选项

int getSoTimeout()

void setSoTimeout(int timeoutMillis)

 以上方法分别获取和设置该套接字中receive()方法调用的最长阻塞时间。如果在接收到数据之前超时,则抛出InterruptedIOException异常。超时时间以毫秒为单位。

 Socket类和ServerSocket类一样,DatagramSocket类也还有许多其他选项,这些内容将在第4.4节更加完整地介绍。

 

2.3.3 UDP服务器端

TCP服务器一样,UDP服务器的工作是建立一个通信终端,并被动等待客户端发起

连接。但由于UDP是无连接的,UDP通信通过客户端的数据报文初始化,并没有TCP

建立连接那一步。典型的UDP服务器要执行以下三步:

1. 创建一个DatagramSocket实例,指定本地端口号,并可以选择指定本地地址。此时,服务器已经准备好从任何客户端接收数据报文。 

2. 使用DatagramSocket类的receive()方法来接收一个DatagramPacket实例。当receive()方法返回时,数据报文就包含了客户端的地址,这样我们就知道了回复信息应该发送到什么地方。

 3. 使用DatagramSocket类的send()      receive()方法来发送和接收DatagramPackets例,进行通信。 

下一个示例程序,UDPEchoServer.java,实现了一个UDP版本的回馈服务器。这个服务器非常简单:它不停地循环,接收数据报文后将相同的数据报文返回给客户端。实际上我们的服务器只接收和发送数据报文中的前255ECHOMAX)个字符,超出的部分将在套接字的具体实现中无提示地丢弃。

UDPEchoServer.java

0 import java.io.IOException;

1 import java.net.DatagramPacket;

2 import java.net.DatagramSocket;

3

4 public class UDPEchoServer {

5

6 private static final int ECHOMAX = 255; // Maximum size

of echo datagram

7

8 public static void main(String[] args) throws

IOException {

9

10 if (args.length != 1) { // Test for correct argument

list

11 throw new IllegalArgumentException("Parameter(s):

<Port>");

12 }

13

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

15

16 DatagramSocket socket = new DatagramSocket(servPort);

17 DatagramPacket packet = new DatagramPacket(new

byte[ECHOMAX], ECHOMAX);

18

19 while (true) { // Run forever, receiving and echoing

datagrams

20 socket.receive(packet); // Receive packet from client

21 System.out.println("Handling client at " +

packet.getAddress().getHostAddress()

22 + " on port " + packet.getPort());

23 socket.send(packet); // Send the same packet back to

client

24 packet.setLength(ECHOMAX); // Reset length to avoid

shrinking buffer

25 }

26 /* NOT REACHED */

27 }

28 }

  

UDPEchoServer.java

1. 应用程序设置和参数解析:第0-14

UDPEchoServer只接收一个参数,即该回馈服务器套接字的本地端口号。 

2. 创建和设置数据报文套接字:第16

UDP客户端不同的是,UDP服务器必须显式地设置它的本地端口号,并使客户端知道该端口,否则客户端将不知道应该把回馈请求数据报文发送到什么目的地址。服务器从客户端接收到了回馈数据报文后,能从中获取客户端的地址和端口号。

3. 创建数据报文:第17

UDP消息包含在数据报文中。我们构建了一个DatagramPacket实例,其缓存区最多ECHOMAX)可容纳255个字节。这个数据报文将同时用来接收回馈请求和发送回馈信息。

4. 迭代处理收到的回馈请求:第19-25

UDP服务器为所有的通信使用同一个套接字,这点与TCP服务器不同,TCP服务器为每个成功返回的accept()方法创建一个新的套接字。

 接收回馈请求数据报文,打印其源地址信息:第20-22

 DatagramSocket类的receive()方法将阻塞等待,直到接收到从客户端发来的数据报文(或超时)。由于没有连接,每个数据报文都可能发送自不同的客户端。而数据报文自身就包含了其发送者的(客户端的)源地址和端口号。

 发送回馈信息:第23

 数据包(packet)中已经包含了回馈字符串和回馈目的地址及端口,因此DatagramSocket类的send()方法只是简单地传输之前接收到的数据报文。注意,当我们接收数据报文时,将其地址和端口解释为源地址和端口,而在发送数据报文时,则将其地址和端口称为目的地址和端口。

重置缓存区大小:第24

处理了接收到的消息后,数据包的内部长度将设置为刚处理过的消息的长度,而这可能比缓冲区的原始长度短。如果接收新消息前不对内部长度进行重置,后续的消息一旦长于之前消息,就会被截断。

相关下载:

Java_TCPIP_Socket编程(doc)

http://download.csdn.net/detail/undoner/4940239

 

文献来源:

UNDONER(小杰博客) :http://blog.csdn.net/undoner

LSOFT.CN(琅软中国) :http://www.lsoft.cn

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 在Qt中,可以使用QUdpSocket类来实现UDP客户端接收服务器端数据。首先创建一个QUdpSocket对象: QUdpSocket *udpSocket = new QUdpSocket(this); 然后使用bind()函数将该udpSocket绑定到一个本地端口: udpSocket->bind(localPort); 其中localPort是一个本地端口号,用于接收服务器端发送的数据。 接下来,可以使用readyRead()信号和QByteArray类来接收数据。readyRead()信号在有数据到达时触发,我们可以使用信号槽机制连接它: connect(udpSocket, SIGNAL(readyRead()), this, SLOT(processPendingDatagrams())); 接收数据的具体实现可以在processPendingDatagrams()槽函数中完成: void MyClass::processPendingDatagrams() { while (udpSocket->hasPendingDatagrams()) { QByteArray datagram; datagram.resize(udpSocket->pendingDatagramSize()); udpSocket->readDatagram(datagram.data(), datagram.size()); // 处理接收到的数据 } } 在该槽函数中,我们利用hasPendingDatagrams()函数判断是否有待处理的数据包,如果有,就使用readDatagram()函数读取数据包的内容。 最后,可以在处理接收到的数据的部分添加逻辑来完成对接收到的数据的处理。 以上就是在Qt中实现UDP客户端接收服务器端数据的方法。 ### 回答2: QT是一种开发工具,UDP是一种网络通信协议,客户端指的是对UDP服务器进行请求并接收数据的一方,服务器端是负责接收请求并提供数据的一方。 在QT中,开发客户端来接收UDP服务器端的数据可以按照以下步骤进行: 1. 创建一个QT项目,并在项目中添加UDP相关的头文件和库文件。 2. 使用QT提供的QUdpSocket类来创建一个UDP套接字对象。套接字对象是用来进行网络通信的对象。 3. 配置套接字对象的相关参数,如绑定端口号。 4. 使用套接字对象的bind()函数将套接字绑定到特定的主机和端口号上。 5. 使用套接字对象的readyRead()信号和对应的槽函数来接收服务器端的数据。 6. 在槽函数中调用套接字对象的readDatagram()函数来读取接收到的数据,并进行处理。 7. 根据具体需求,可以在接收到数据后对数据进行解析、展示或者其他操作。 8. 可以使用套接字对象的writeDatagram()函数向服务器端发送数据。 9. 在必要的情况下,可以在客户端服务器端的通信过程中使用一些错误处理机制,如超时重发等。 通过以上步骤,就可以在QT中实现UDP客户端接收服务器端的数据。接收到的数据可以根据需求进行处理和展示,以满足具体的业务需求。 ### 回答3: Qt是一个跨平台的C++应用程序开发框架,支持多种网络通信协议,其中包括UDP协议。 在Qt中,可以通过QUdpSocket类来实现UDP客户端。首先,需要创建一个QUdpSocket对象,并通过bind函数绑定本地地址和端口号。然后,可以使用receiveDatagram函数来接收服务器端发送过来的数据。 接收数据的代码示例: ```cpp QUdpSocket *udpSocket = new QUdpSocket(this); udpSocket->bind(QHostAddress::AnyIPv4, 1234); // 绑定本地地址和端口号 QByteArray datagram; // 用于存储接收到的数据 datagram.resize(udpSocket->pendingDatagramSize()); QHostAddress senderAddress; // 存储数据发送者的地址 quint16 senderPort = 0; // 存储数据发送者的端口号 udpSocket->readDatagram(datagram.data(), datagram.size(), &senderAddress, &senderPort); // 使用datagram中的数据进行后续处理 delete udpSocket; ``` 在以上代码中,通过udpSocket->bind函数指定了本地地址AnyIPv4和端口号1234,表示接收来自任意地址的UDP数据包。通过udpSocket->readDatagram函数接收数据,并将数据存储在datagram中,同时获取发送者的地址和端口号。 需要注意的是,接收数据的操作是阻塞的,即在调用udpSocket->readDatagram函数时,如果没有接收到数据,程序将一直等待,直到接收到数据或发生错误。 总之,通过以上代码,我们可以在Qt中实现UDP客户端接收服务器端发送的数据。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值