java实现基于UDP协议网络Socket编程(C/S通信)

内容摘要
java实现基于UDP协议网络Socket编程(C/S通信)一、前言:认识UDPUDP,全称User Datagram Protocol(用户数据报协议),是Internet 协议集支持一个无连接的传输协议。UDP 为应用程
文章正文

java实现基于UDP协议网络Socket编程(C/S通信)

一、前言:认识UDP

UDP,全称User Datagram Protocol(用户数据报协议),是Internet 协议集支持一个无连接的传输协议。UDP 为应用程序提供了一种无需建立连接就可以发送封装的 IP 数据包的方法。

UDP主要用于不要求分组顺序到达的传输中,分组传输顺序的检查与排序由应用层完成,提供面向报文的简单不可靠信息传送服务。UDP 协议基本上是IP协议与上层协议的接口,适用端口分别运行在同一台设备上的多个应用程序。

二、UDP的特点(与TCP相比)

正是UDP提供不可靠服务,具有了TCP所没有的优势。无连接使得它具有资源消耗小,处理速度快的优点,所以音频、视频和普通数据在传送时经常使用UDP,偶尔丢失一两个数据包,也不会对接收结果产生太大影响。

UDP有别于TCP,有自己独立的套接字(IP+Port),它们的端口号不冲突。和TCP编程相比,UDP在使用前不需要进行连接,没有流的概念。

如果说TCP协议通信与电话通信类似,那么UDP通信就与邮件通信类似:不需要实时连接,只需要目的地址;

UDP通信前不需要建立连接,只要知道地址(ip地址和端口号)就可以给对方发送信息;

基于用户数据报文(包)读写;

UDP通信一般用于线路质量好的环境,如局域网内,如果是互联网,往往应用于对数据完整性不是过于苛刻的场合,例如语音传送等。

以上是对UDP的基本认识,与以前学习的理论相比,接下来的实践更加有趣,实践出真知。

三、UDP网络Socket编程(java实现)

首先,熟悉java中UDP编程的几个关键类:DatagramSocket(套接字类),DatagramPacket(数据报类),MulticastSocket。本篇主要使用前两个。

1、创建客户端

第一步,实例化一个数据报套接字,用于与服务器端进行通信。与TCP不同,UDP中只有DatagramSocket一种套接字,不区分服务端和客户端,创建的时候并不需要指定目的地址,这也是TCP协议和UDP协议最大的不同点之一。

public UDPClient(String remoteIP,String remotePort) throws IOException{

this.remoteIP=InetAddress.getByName(remoteIP);

this.remotePort=Integer.parseInt(remotePort);

//创建UDP套接字,系统随机选定一个未使用的UDP端口绑定

socket=new DatagramSocket();

}

第二步, 创建UDP数据报,实现发送和接收数据的方法。UDP发送数据是基于报文DatagramPacket,网络中传递的UDP数据都要封装在这种自包含的报文中。

实现DatagramPacket发送数据的方法:

//定义一个数据的发送方法

public void send(String msg){

try {

//将待发送的字符串转为字节数组

byte[] outData=msg.getBytes("utf-8");

//构建用于发送的数据报文,构造方法中传入远程通信方(服务器)的ip地址和端口

DatagramPacket outPacket=new DatagramPacket(outData,outData.length,remoteIP,remotePort);

//给UDP发送数据报

socket.send(outPacket);

}catch (IOException e){

e.printStackTrace();

}

}

DatagramPacket接收数据的方法:

//定义一个数据的发送方法

public void send(String msg){

try {

//将待发送的字符串转为字节数组

byte[] outData=msg.getBytes("utf-8");

//构建用于发送的数据报文,构造方法中传入远程通信方(服务器)的ip地址和端口

DatagramPacket outPacket=new DatagramPacket(outData,outData.length,remoteIP,remotePort);

//给UDP发送数据报

socket.send(outPacket);

}catch (IOException e){

e.printStackTrace();

}

}

可以看到,发送和接收数据中使用DatagramSocket的实例的send和receive方法,这就是数据报套接字的两个重要方法。

通信结束,销毁Socket的方法如下:

public void close(){

if (socket!=null)

socket.close();

}

到这里,客户端已全部完成,等待接下来与服务端的通信...

2、客户端图形界面

现在,设计客户端通信的简单界面,一方面可以更方便的和服务器连续对话通信,另一方面,有了图形界面,体验感更加!图形化界面主要使用JavaFX实现,代码容易看懂。

import javafx.application.Application;

import javafx.event.EventHandler;

import javafx.geometry.Insets;

import javafx.geometry.Pos;

import javafx.scene.Scene;

import javafx.scene.control.*;

import javafx.scene.input.KeyCode;

import javafx.scene.input.KeyEvent;

import javafx.scene.layout.BorderPane;

import javafx.scene.layout.HBox;

import javafx.scene.layout.Priority;

import javafx.scene.layout.VBox;

import javafx.stage.Stage;

import java.io.IOException;

public class UDPClientFX extends Application {

private Button btnExit=new Button("退出");

private Button btnSend = new Button("发送");

private TextField tfSend=new TextField();//输入信息区域

private TextArea taDisplay=new TextArea();//显示区域

private TextField ipAddress=new TextField();//填写ip地址

private TextField tfport=new TextField();//填写端口

private Button btConn=new Button("连接");

private UDPClient UDPClient;

private String ip;

private String port;

@Override

public void start(Stage primaryStage) {

BorderPane mainPane=new BorderPane();

//连接服务器区域

HBox hBox1=new HBox();

hBox1.setSpacing(10);

hBox1.setPadding(new Insets(10,20,10,20));

hBox1.setAlignment(Pos.CENTER);

hBox1.getChildren().addAll(new Label("ip地址:"),ipAddress,new Label("端口:"),tfport,btConn);

mainPane.setTop(hBox1);

VBox vBox=new VBox();

vBox.setSpacing(10);

vBox.setPadding(new Insets(10,20,10,20));

vBox.getChildren().addAll(new Label("信息显示区"),taDisplay,new Label("信息输入区"),tfSend);

VBox.setVgrow(taDisplay, Priority.ALWAYS);

mainPane.setCenter(vBox);

HBox hBox=new HBox();

hBox.setSpacing(10);

hBox.setPadding(new Insets(10,20,10,20));

hBox.setAlignment(Pos.CENTER_RIGHT);

hBox.getChildren().addAll(btnSend,btnExit);

mainPane.setBottom(hBox);

Scene scene =new Scene(mainPane,700,500);

primaryStage.setScene(scene);

primaryStage.show();

//连接服务器之前,发送bye后禁用发送按钮,禁用Enter发送信息输入区域,禁用下载按钮

btnSend.setDisable(true);

tfSend.setDisable(true);

//连接按钮

btConn.setOnAction(event - {

ip=ipAddress.getText().trim();

port=tfport.getText().trim();

try {

UDPClient = new UDPClient(ip,port);

//连接服务器之后未结束服务前禁用再次连接

btConn.setDisable(true);

//重新连接服务器时启用输入发送功能

tfSend.setDisable(false);

btnSend.setDisable(false);

} catch (IOException e) {

e.printStackTrace();

}

});

//发送按钮事件

btnSend.setOnAction(event - {

String msg=tfSend.getText();

UDPClient.send(msg);/LnnxWIC/向服务器发送一串字符

taDisplay.appendText("客户端发送:"+msg+"\n");

String Rmsg=null;

Rmsg=UDPClient.receive();

// System.out.println(Rmsg);

taDisplay.appendText(Rmsg+"\n");

if (msg.equals("bye")){

btnSend.setDisable(true);//发送bye后禁用发送按钮

tfSend.setDisable(true);//禁用http://Enter发送信息输入区域

//结束服务后再次启用连接按钮

btConn.setDisable(false);

}

tfSend.clear();

});

//对输入区域绑定键盘事件

tfSend.setOnKeyPressed(new EventHandler() {

@Override

public void handle(KeyEvent event) {

if(event.getCode()==KeyCode.ENTER){

String msg=tfSend.getText();

UDPClient.send(msg);//向服务器发送一串字符

taDisplay.appendText("客户端发送:"+msg+"\n");

String Rmsg=null;

Rmsg=UDPClient.receive();

taDisplay.appendText(Rmsg+"\n");

if (msg.equals("bye")){

tfSend.setDisable(true);//禁用Enter发送信息输入区域

btnSend.setDisable(true);//发送bye后禁用发送按钮

//结束服务后再次启用连接按钮

btConn.setDisable(false);

}

tfSend.clear();

}

}

});

btnExit.setOnAction(event - {

try {

exit();

} catch (InterruptedException e) {

e.printStackTrace();

}

});

//窗体关闭响应的事件,点击右上角的关闭,客户端也关闭

primaryStage.setOnCloseRequest(event - {

try {

exit();

} catch (InterruptedException e) {

e.printStackTrace();

}

});

//信息显示区鼠标拖动高亮文字直接复制到信息输入框,方便选择文件名

//taDispaly为信息选择区的TextArea,tfSend为信息输入区的TextField

//为taDisplay的选择范围属性添加监听器,当该属性值变化(选择文字时),会触发监听器中的代码

taDisplay.selectionProperty().addListener(((observable, oldValue, newValue) - {

//只有当鼠标拖动选中了文字才复制内容

if(!taDisplay.getSelectedText().equals(""))

tfSend.setText(taDisplay.getSelectedText());

}));

}

private void exit() throws InterruptedException {

if (UDPClient!=null){

//向服务器发送关闭连接的约定信息

UDPClient.send("bye");

UDPClient.close();

}

System.exit(0);

}

public static void main (String[] args) {

launch(args);

}

}

重点在各个控件的事件处理逻辑上,需避免要一些误操作导致异常抛出,如:连接服务器前禁用发送按钮,在连接服务器成功后禁用连接按钮,禁用输入区等。另外,实现了回车发送的快捷功能,详见代码的键盘事件绑定部分。

还有,约定发送"bye"或者退出按钮结束通信关闭Socket。

成功连接后:

3、创建服务器端

服务器端为客户端提供服务,实现通信。这里包括了几个方法Service(),udpSend()和udpReceive().

首先,我将UDP数据报发送和接收写成一个方法,作为整体方便多次调用。

public DatagramPacket udpReceive() throws IOException {

DatagramPacket receive;

byte[] dataR = new byte[1024];

receive = new DatagramPacket(dataR, dataR.length);

socket.receive(receive);

return receive;

}

public void udpSend(String msg,InetAddress ipRemote,int portRemote) throws IOException {

DatagramPacket sendPacket;

byte[] dataSend = msg.getBytes();

sendPacket = new DatagramPacket(dataSend,dataSend.length,ipRemote,portRemote);

socket.send(sendPacket);

}

与TCP的Socket通信不同,需要将数据转化成字节数据形式,封装成数据报进行传输,接收时解析数据为字节,再进行读取。

服务器端核心部分为Service()方法,实例化一个DatagramSocket类套接字,实现循环与客户端的通信。

与客户端约定的结束标志"bye"进行处理,结束对话。

public DatagramPacket udpReceive() throws IOException {

DatagramPacket receive;

byte[] dataR = new byte[1024];

receive = new DatagramPacket(dataR, dataR.length);

socket.receive(receive);

return receive;

}

public void udpSend(String msg,InetAddress ipRemote,int portRemote) throws IOException {

DatagramPacket sendPacket;

byte[] dataSend = msg.getBytes();

sendPacket = new DatagramPacket(dataSend,dataSend.length,ipRemote,portRemote);

socket.send(sendPacket);

}

四、服务器端和客户端完整代码

服务器端:

public DatagramPacket udpReceive() throws IOException {

DatagramPacket receive;

byte[] dataR = new byte[1024];

receive = new DatagramPacket(dataR, dataR.length);

socket.receive(receive);

return receive;

}

public void udpSend(String msg,InetAddress ipRemote,int portRemote) throws IOException {

DatagramPacket sendPacket;

byte[] dataSend = msg.getBytes();

sendPacket = new DatagramPacket(dataSend,dataSend.length,ipRemote,portRemote);

socket.send(sendPacket);

}

客户端:

//UDPClient.java

import java.io.IOException;

import java.net.DatagramPacket;

import java.net.DatagramSocket;

import java.net.InetAddress;

public class UDPClient {

private int remotePort;

private InetAddress remoteIP;

private DatagramSocket socket;

//用于接收数据的报文字节数组缓存最最大容量,字节为单位

private static final int MAX_PACKET_SIZE=512;

public UDPClient(String remoteIP,String remotePort) throws IOException{

this.remoteIP=InetAddress.getByName(remoteIP);

this.remotePort=Integer.parseInt(remotePort);

//创建UDP套接字,系统随机选定一个未使用的UDP端口绑定

socket=newLnnxWIC DatagramSocket();

}

//定义一个数据的发送方法

public void send(String msg){

try {

//将待发送的字符串转为字节数组

byte[] outData=msg.getBytes("utf-8");

//构建用于发送的数据报文,构造方法中传入远程通信方(服务器)的ip地址和端口

DatagramPacket outPacket=new DatagramPacket(outData,outData.length,remoteIP,remotePort);

//给UDP发送数据报

socket.send(outPacket);

}catch (IOException e){

e.printStackTrace();

}

}

public String receive(){

String msg;

//准备空的数据报文

DatagramPacket inPacket=new DatagramPacket(new byte[MAX_PACKET_SIZE],MAX_PACKET_SIZE);

try {

//读取报文,阻塞语句,有数据就装包在inPacket报文中,以装完或装满为止

socket.receive(inPacket);

//将接收到的字节数组转为对应的字符串

msg=new String(inPacket.getData(),0,inPacket.getLength(),"utf-8");

} catch (IOException e) {

e.printStackTrace();

msg=null;

}

return msg;

}

public void close(){

if (socket!=null)

socket.close();

}

}

五、效果展示

六、总结

这一篇详细记录学习运用java进行网络编程,基于UDP套接字(Socket)实现服务器与客户端间的通信,在实战案例中更深刻理解UDP的实现原理,掌握UDP实践应用步骤。

起初完成UDP通信时,遇到了几个问题,相比较TCP的实现,确实体会到数据传输的过程的不同,UDP服务和客户端共用了一个DatagramSocket,另外需要DatagramPacket数据报的协作。另外,UDP没有数据流的概念,所以读写不同于TCP,需要以字节数据进行读取。

代码注释
[!--zhushi--]

作者:喵哥笔记

IDC笔记

学的不仅是技术,更是梦想!