您的位置:首页 > 理论基础 > 计算机网络

Java网络编程实践和总结 --- 基于UDP的Socket编程

2015-04-02 19:54 621 查看
前面的blog介绍和总结基于TCP协议的网络编程。TCP提供了一种可靠的传输方式。如果数据在传输过程丢失或者损坏了,TCP会保证数据会再次发送,直到数据完整接受。如果数据包以乱序到达目的地,TCP也会根据数据包的原始顺序将其正常排序。如果传输的速度过快而导致数据丢包,TCP也会自动地调整速度,保证稳定不丢包。因此程序不用担心数据的问题,但往往这也意味着TCP协议的网络程序需要牺牲性能来实现这些功能。建立和销毁一个TCP连接往往都是需要付出很大的性能代价的。

今天我们要讲的用户数据报协议(User Datagram Protocol, UDP)协议是建立在IP协议之上的另一种协议。特点是速度快,但不稳定可靠。UDP不需要想TCP那样建立稳定的连接,不关心数据是否在传输的过程丢失,不关心数据在传输过程中顺序,不关心数据能够被对方接受的到。

在实际的需求中往往就需要用到UDP数据,如在线实时语音和视频时,在保证速度的前提下部分的数据丢失所造成的影响不大。但如果使用TCP协议,为了等待个别数据包而导致请求重传或等待数据包所造成的停顿是无法接受的。

TCP和UDP的区别就好比打电话和寄邮件的区别。打电话必须要双方确认的情况下才能进行通话,而寄邮件不管所寄对方是否在线或是否存在,一旦寄出就可以不用管了。

Java中UDP通过两个类来实现:

DatagramPacket类 和 DatagramSocket类

DatagramPacket类用于将数据字节填充到其中的UDP包中,用于发送和解包接受的数据包。

DatagramSocket类用于将数据UDP包进行发送和接受操作等。

之前学习TCP Socket编程时实现了Echo回显操作网络程序,现在我们通过UDP来重新实现:

以下为客户端程序:

public class UDPEchoClient {

public static final String END_FLAG = "over";

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

EchoSenderThread senderThread = new EchoSenderThread();
EchoReceiverThread receiverThread = new EchoReceiverThread(senderThread.getTheSocket());

senderThread.start();
receiverThread.start();
}
}

//客户端的发送线程,用于从标准输入获取信息,通过UDP发送到服务端
class EchoSenderThread extends Thread {

//连接的UDP Socket
DatagramSocket theSocket;

public EchoSenderThread() throws SocketException {
this.theSocket = new DatagramSocket();
//连接到默认的服务器
theSocket.connect(new InetSocketAddress(UDPEchoServer.DEFAULT_HOSTNAME, UDPEchoServer.DEFAULT_PORT));
}

@Override
public void run() {
//标准输入流
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
while (true) {
try {
System.out.print("to " + theSocket.getInetAddress().getHostName() + "/" + theSocket.getPort() + " : ");
String inputLine = br.readLine();
byte[] data = inputLine.getBytes();
//将数据封装到udp数据包中,包括字节数据,服务端的地址和端口
DatagramPacket dp = new DatagramPacket(data, data.length, theSocket.getInetAddress(), theSocket.getPort());
//通过UDP套接字连接到服务端
theSocket.send(dp);
//如果输入为over则结束
if (UDPEchoClient.END_FLAG.equals(inputLine)) {
break;
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println("EchoSenderThread end : " + theSocket.getInetAddress());
}

//获取UDP Socket的方法
public DatagramSocket getTheSocket() {
return theSocket;
}

public void setTheSocket(DatagramSocket theSocket) {
this.theSocket = theSocket;
}

}

//客户端的接受线程,用于接受服务端发送的Echo信息并且显示
class EchoReceiverThread extends Thread {

DatagramSocket theSocket;

public EchoReceiverThread(DatagramSocket theSocket) {
this.theSocket = theSocket;
}

@Override
public void run() {
//初始化接受的数据包,长度设置为最大的64KB
byte[] data = new byte[65536];
DatagramPacket dp = new DatagramPacket(data, 0, data.length);
//循环接受信息
while (true) {
try {
//阻塞式接受数据包
theSocket.receive(dp);
//获取数据包的数据并打印
String content = new String(dp.getData(), 0, dp.getLength());
System.out.println("from " + dp.getAddress().getHostName() + "/" + dp.getPort() + " : " + content);
//如果为over则退出
if (UDPEchoClient.END_FLAG.equalsIgnoreCase(content)) {
break;
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println("EchoReceiverThread end : " + theSocket.getInetAddress());
}

}


以下为服务端程序:

public class UDPEchoServer {

//默认的主机地址
public static String DEFAULT_HOSTNAME = "localhost";
//默认的端口
public static int DEFAULT_PORT = 10000;
//用于连接的UDP Socket
private DatagramSocket serverSocket;

public UDPEchoServer() throws SocketException {
//将UDP连接绑定到默认的主机以及端口
serverSocket = new DatagramSocket(new InetSocketAddress(DEFAULT_HOSTNAME, DEFAULT_PORT));
}

public static void main(String[] args) throws SocketException {
//创建服务端的对象
UDPEchoServer server = new UDPEchoServer();
byte[] data = new byte[65535];
while (true) {
try {
//初始化接受数据包,此处缓冲区使用最大的64KB
DatagramPacket dp = new DatagramPacket(data, 0, data.length);
//阻塞接受数据包
server.getServerSocket().receive(dp);
System.out.println("receive a datagram packet");
//打印数据包内容
System.out.println("from hostname : " + dp.getAddress() + ", port : " + dp.getPort() + " : " + new String(dp.getData(), 0, dp.getLength()));
//将数据包传递给处理线程处理,提供服务器的处理效率
Thread echoHandlerThread = new EchoHandlerThread(dp, server.getServerSocket());
echoHandlerThread.start();
//放弃,让处理线程优先执行一下
Thread.yield();

} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}

}

public DatagramSocket getServerSocket() {
return serverSocket;
}

public void setServerSocket(DatagramSocket serverSocket) {
this.serverSocket = serverSocket;
}

}

class EchoHandlerThread extends Thread {

private DatagramPacket dp;
private DatagramSocket serverSocket;

public EchoHandlerThread(DatagramPacket dp, DatagramSocket serverSocket) {
this.dp = dp;
this.serverSocket = serverSocket;
}

@Override
public void run() {
//获取服务端接受的数据并且处理用于回显操作
String replyString = new String(dp.getData(), 0, dp.getLength()).toUpperCase();
byte[] replyData = replyString.getBytes();
//初始化数据包,包括填充数据,地址和端口
DatagramPacket ndp = new DatagramPacket(replyData, 0, replyData.length, dp.getAddress(), dp.getPort());
System.out.println("sent to hostname : " + ndp.getAddress() + ", port : " + ndp.getPort() + " : " + new String(ndp.getData(), 0, dp.getLength()));
try {
//通过UDP Socket发送数据包
serverSocket.send(ndp);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

}

}

测试结果:

分别打开服务端和客户端,客户端输入信息。

以下为客户端显示结果:

to localhost/10000 : hello

to localhost/10000 : from 127.0.0.1/10000 : HELLO

world

to localhost/10000 : from 127.0.0.1/10000 : WORLD

test

to localhost/10000 : from 127.0.0.1/10000 : TEST

over

EchoSenderThread end : localhost/127.0.0.1

from 127.0.0.1/10000 : OVER

EchoReceiverThread end : localhost/127.0.0.1

以下为服务端显示结果:

receive a datagram packet

from hostname : /127.0.0.1, port : 50730 : hello

sent to hostname : /127.0.0.1, port : 50730 : HELLO

receive a datagram packet

from hostname : /127.0.0.1, port : 50730 : world

sent to hostname : /127.0.0.1, port : 50730 : WORLD

receive a datagram packet

from hostname : /127.0.0.1, port : 50730 : test

sent to hostname : /127.0.0.1, port : 50730 : TEST

receive a datagram packet

from hostname : /127.0.0.1, port : 50730 : over

sent to hostname : /127.0.0.1, port : 50730 : OVER

客户端发送信息后可以收到服务端大写的回显。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: