这篇博文是本文学习《Java网络程序设计》书中第5章数据报套接字的学习总结。初学者网友学习这篇Java数据报套接字文章,如果难于理解文章前面理论部分,可以先运行后面的程序,边看运行后面的程序边理解前面的原理,这对初学者是最好的方法。所有源代码都在文章后面我的github链接代码中。
——惠州学院13网络工程 吴成兵 20160609
目录 1
- 目录 1
- 一 数据报套接字概述
- 二 DatagramPacket
- 21 创建DatagramPacket对象
- 211 创建的DatagramPacket对象用于接收数据
- 212 创建的DatagramPacket对象用于发送数据
- 22 DatagramPacket常用方法
- 21 创建DatagramPacket对象
- 三 DatagramSocket
- 31 创建DatagramSocket对象
- 32 DatagramSocket常用方法
- 四 DatagramSocket编程示例
- 41 利用DatagramSocket查询端口占用情况
- 42 利用数据报通信的CS程序
- 421 数据接收端
- 422 数据发送端
- 43 用UDP实现的聊天程序
一 数据报套接字概述
流套接字的每个连接均要花费一定的时间,为了减少这种开销,网络API提供了第二种套接字——数据报套接字(DatagramSocket),又称自寻址套接字。
数据报套接字基于的协议是UDP协议,采用的是一种尽力而为(Best-Effort)的传送数据的方式,它只是把数据的目的地记录在数据报包(DatagramPacket)中,然后就直接放在网络上,系统不保证数据是否能安全送到,或者什么时候可以送到,也就是说它并不保证传送的质量。UDP在每一个自寻址包中包含了错误检测信息,在每个自寻址包到达目的地之后UDP进行简单的错误检查,如果检查失败,UDP将抛弃这个自寻址包,也不会从发送者那里重新请求替代者。这与通过邮局发送信件相似,发信人在发信这前不需要与收信人建立连接,同样也不能保证信件能到达发信人那里。
可见,UDP的优点是效率高,并且比较灵活,一般用于质量和实时性要求不是很高的情况,比如实时音频和视频应用中。
DatagramSocket本身只是码头,不维护状态,不能产生I/O流,它的唯一作用就是接收和发送数据报包,这个数据报包在Java中是使用DatagramPacket对象实现的。
数据报套接字编程中主要使用下面三个类:DatagramPacket 、DatagramSocket和MulticastSocket。
- DatagramPacket对象描绘了自寻址包的地址信息;
- DatagramSocket表示客户端程序与服务器程序自寻址套接字;
- MulticastSocket 描绘了能进行多点传送的自寻址套接字。
二 DatagramPacket
2.1 创建DatagramPacket对象
2.1.1 创建的DatagramPacket对象用于接收数据
以一个空数组来创建DatagramPacket对象,该对象的作用是接收DatagramSocket中的数据。
- public DatagramPacket(byte buf[], int length) :接收到的数据从buf[0]开始存放,直到整个数据包接收完毕或者将length的字节写入buf为止。
- public DatagramPacket(byte buf[], int offset, int length):接收到的数据从buf[offset]开始存放,如果数据包长度超出了length,则会触发IllegalArgument-Exception。
不过这是RuntimeException,不需要用户代码捕获。
示范代码如下:
byte[] buffer = new byte[8912];
DatagramPacket datap = new DatagramPacket(buffer, buffer.length);
2.1.2 创建的DatagramPacket对象用于发送数据
以一个包含数据的数组 buf[]来创建DatagramPacket对象,该对象作为DatagramSocket发送数据的载体,并指定该DatagramPacket目的IP地址和端口号。
- public DatagramPacket(byte buf[], int length, InetAddress address, int port)
- public DatagramPacket(byte buf[], int offset, int length, InetAddress address, int port)
示范代码是要发送一串字符串:
String string = new String("java networking");
byte[] data=string.getBytes();
int port=1024;
InetAddress inetA=null;
try {
inetA = InetAddress.getByName("127.0.0.1");
DatagramPacket datap = new DatagramPacket(data,data.length,inetA,port);
} catch (UnknownHostException e) {}
2.2 DatagramPacket常用方法
获取DatagramPacket对象属性的方法如下:
- public synchronized InetAddress getAddress()
- public synchronized int getPort()
- public synchronized byte[] getData()
- public synchronized int getLength()
- public synchronized int getOffset()
设置DatagramPacket对象属性的方法如下:
- public synchronized void setAddress(InetAddress iaddr)
- public synchronized void setPort(int iport)
- public synchronized void setData(byte[] buf, int offset, int length)
- public synchronized void setData(byte[] buf)
- public synchronized void setLength(int length)
- public synchronized void setSocketAddress(SocketAddress address)
部分方法说明:
- getAddress()是返回该DatagramPacket的IP地址。如果DatagramPacket是发送出来的数据报包,这个方法则返回目的主机的IP地址。如果DatagramPacket是接收到的数据报包,这个方法则返回远程主机的IP地址。
- getSocketAddress()是返回要将此包发送到的或此数据报包的远程主机的SocketAddress(通常是IP地址+端口号)。
- setAddress()是设置该DatagramPacket要发送往的目的主机的IP地址。
- setSocketAddress()是设置要将此包发送到的或此数据报包的远程主机的SocketAddress(通常是IP地址+端口号)。
三 DatagramSocket
3.1 创建DatagramSocket对象
- public DatagramSocket() throws SocketException :系统随机产生一个端口,创建一个数据报套接字。这种构造方法没有指定端口号,可以用在发送端,如果构造不成功则触发SocketException异常。
- public DatagramSocket(int port) throws SocketException :用一个指定端口号port创建一个数据报套接字。如果指定端口已被占用或者是试图连接低于1024的端口但又没具备权限。
- public DatagramSocket(int port, InetAddress laddr) throws SocketException :创建一个数据报套接字,并将该对象绑定到指定的IP地址和端口。
通常用指定端口的方式创建发送端数据报套接字。一旦得到了DatagramSocket对象之后,就可以通过如下两个方法来接收和发送数据:
- public void send(DatagramPacket p) throws IOException :从该DatagramSocket中接收数据报包。
- public synchronized void receive(DatagramPacket p) throws IOException :使用该DatagramSocket对象向外发送数据报包。
从上面两个方法可以看出,使用DatagramSocket发送数据报包时,DatagramSocket并不知道将该数据报包发送到哪里,而是由DatagramPacket自身决定数据报包的目的地,所有的端口、目的地址和数据,需要由DatagramPacket来指定。就像码头并不知道每个集装箱发送到哪里,码头只是将这些集装箱发送出去,而集装箱本身包含了该集装箱的目的地。
DatagramSocket在接收数据之前,应该采用public DatagramPacket(byte buf[], int length)或public DatagramPacket(byte buf[], int offset, int length)构造方法创建一个DatagramPakcet对象,给出接收数据的字节及其长度。然后调用DatagramSocket的方法receive()等待数据报包的到来,receive()将一直等待(也就是说会阻塞调用该方法的线程),直到收到一个数据报包为止,如下代码所示:
//创建要接收数据的DatagramPacket对象
DatagramPacket packet =new DatagramPacket(buf,buf.length);
//接收数据
receiveSocket.receive(packet);
发送数据之前,调用public DatagramPacket(byte buf[], int length, InetAddress address, int port)或public DatagramPacket(byte buf[], int offset, int length, InetAddress address, int port)构造方法创建DatagramPacket对象,此时的字节数组存放了要发送的数据。除此之外,还要给出完整的目的地址,包括IP地址和端口号。发送数据是通过DatagramSocket的方法send()实现的,send()根据数据报包的目的地址来寻径以传递数据报包,如下代码所示:
//创建一个要发送数据的DatagramPacket对象
DatagramPacket packet = new DatagramPacket(buf,length,address, port);
//发送数据
sendSocket.send(packet);
3.2 DatagramSocket常用方法
- public InetAddress getInetAddress()
- public int getPort()
- public InetAddress getLocalAddress()
- public int getLocalPort()
- public SocketAddress getRemoteSocketAddress()
- public SocketAddress getLocalSocketAddress()
- public void send(DatagramPacket p) throws IOException
- public synchronized void receive(DatagramPacket p) throws IOException
四 DatagramSocket编程示例
4.1 利用DatagramSocket查询端口占用情况
package _5_2DatagramSocket编程示例._5_2_1利用DatagramSocket查询端口占用情况;
import java.net.DatagramSocket;
import java.net.SocketException;
/**
*
* Copyright ? 2016 Authors. All rights reserved.
*
* FileName: .java
* @author : Wu_Being <[email protected]>
* Date/Time: 2016-6-10/上午12:36:45
* Description: 查询端口的占用情 ?
*/
public class UDPScan {
/**
* @param args
*/
public static void main(String[] args) {
//2014~65535
for (int port = 1024; port < 65536; port++) {
try {
DatagramSocket server = new DatagramSocket(port);
server.close();
} catch (SocketException e) {
System.out.println("there is a server in port:" + port + ".");
}
}
}
}
4.2 利用数据报通信的C/S程序
4.2.1 数据接收端
package _5_2DatagramSocket编程示例._5_2_2利用数据报通信的CS程序;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
/**
*
* Copyright ? 2016 Authors. All rights reserved.
*
* FileName: .java
* @author : Wu_Being <[email protected]>
* Date/Time: 2016-6-10/下午10:36:26
* Description:
*/
public class UDPReceiver {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
try {
DatagramSocket receiveSocket =new DatagramSocket(5000);
byte buf[]=new byte[1000];
DatagramPacket receivepPacket=new DatagramPacket(buf,buf.length);
System.out.println("starting to receive packet...");
while(true){
receiveSocket.receive(receivepPacket);
String receiveData=new String(receivepPacket.getData(),0,receivepPacket.getLength());
String name=receivepPacket.getAddress().getHostName();
int port=receivepPacket.getPort();
System.out.print("来自主机:"+name);
System.out.print(",的端口:"+port);
System.out.println("的数据:"+receiveData);
}
} catch (SocketException e) {
System.out.println("不能打开数据报Socket,或数据报Socket无法与指定端口连接");
System.exit(1);
} catch (IOException e) {
System.out.println("网络通信出现问题,问题在于:"+e.toString());
}
}
}
4.2.2 数据发送端
package _5_2DatagramSocket编程示例._5_2_2利用数据报通信的CS程序;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.util.Scanner;
/**
*
* Copyright ? 2016 Authors. All rights reserved.
*
* FileName: .java
*
* @author : Wu_Being <[email protected]> Date/Time: 2016-6-10/下午10:36:20
* Description:
*/
public class UDPSender {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
while (true) {
Scanner scanner = new Scanner(System.in);
try {
System.out.print("请输入要发送的数据:");
String string = scanner.nextLine();
DatagramSocket sendSocket = new DatagramSocket();// 端口号随机
// byte[] databyte=new byte[100]
byte[] databyte = string.getBytes();
DatagramPacket sentPacket = new DatagramPacket(databyte,
databyte.length, InetAddress.getByName("127.0.0.1"),
5000);
sendSocket.send(sentPacket);
System.out.println("send the data:" + string);
} catch (SocketException e) {
System.out.println("不能打开数据报Socket,或数据报Socket无法与指定端口连接");
} catch (IOException e) {
System.out.println("网络通信出现问题,问题在于:" + e.toString());
}
}
}
}
4.3 用UDP实现的聊天程序
package _5_2DatagramSocket编程示例._5_2_3用UDP实现的聊天程序;
import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
public class UDPChat implements ActionListener, Runnable {
JTextArea showArea;
JLabel lbl1, lbl2, lbl3;
JTextField msgTextField, sendPortTextField, receivePorttTextField, IPAddressTextField;
JFrame mainFrame;
JButton sendButton, startButton;//,resetbButton;
JScrollPane JSPane;
JPanel panel1, panel2;
Container container;
Thread thread = null;
DatagramSocket sendSocket, receiveSocket;
DatagramPacket sendPacket, receivePacket;
private InetAddress sendIP;
private int sendPort, receivePort;// 存储发送端口和接收端口
private byte inbuf[], outbuf[];
public static final int BUFSIZE = 1024;
public UDPChat() {
mainFrame = new JFrame("聊天——UDP协议");
container = mainFrame.getContentPane();
showArea = new JTextArea();
showArea.setEditable(false);
showArea.setLineWrap(true);
lbl1 = new JLabel("接收端口号:");
lbl2 = new JLabel("发送端口号:");
lbl3 = new JLabel("对方的地址:");
sendPortTextField = new JTextField();
sendPortTextField.setColumns(4);
receivePorttTextField = new JTextField();
receivePorttTextField.setColumns(4);
IPAddressTextField = new JTextField();
IPAddressTextField.setColumns(8);
startButton = new JButton("开始");
startButton.addActionListener(this);/**/
// resetbButton=new JButton("重置");
// resetbButton.addActionListener(this);/**/
panel1 = new JPanel();
panel1.setLayout(new FlowLayout());
panel1.add(lbl1);
panel1.add(receivePorttTextField);
panel1.add(lbl2);
panel1.add(sendPortTextField);
panel1.add(lbl3);
panel1.add(IPAddressTextField);
panel1.add(startButton);
// panel1.add(resetbButton);
JSPane = new JScrollPane(showArea);
msgTextField = new JTextField();
msgTextField.setColumns(40);
msgTextField.setEditable(false);
msgTextField.addActionListener(this);/**/
sendButton = new JButton("发送");
sendButton.setEnabled(false);
sendButton.addActionListener(this);/**/
panel2 = new JPanel();
panel2.setLayout(new FlowLayout());
panel2.add(msgTextField);
panel2.add(sendButton);
container.add(panel1, BorderLayout.NORTH);
container.add(JSPane, BorderLayout.CENTER);
container.add(panel2, BorderLayout.SOUTH);
mainFrame.setSize(600, 400);
mainFrame.setVisible(true);
mainFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
@Override
public void actionPerformed(ActionEvent e) {
try {
if (e.getSource() == startButton) {/* 按下了开始按键 */
inbuf = new byte[BUFSIZE];
receivePort = Integer.parseInt(receivePorttTextField.getText());
sendPort = Integer.parseInt(sendPortTextField.getText());
sendIP = InetAddress.getByName(IPAddressTextField.getText());
//创建发送和接收DatagramSocket和DatagramPacket
sendSocket = new DatagramSocket();
receiveSocket = new DatagramSocket(receivePort);
receivePacket = new DatagramPacket(inbuf, BUFSIZE);
thread = new Thread(this);
thread.setPriority(Thread.MIN_PRIORITY);
thread.start();
startButton.setEnabled(false);
sendButton.setEnabled(true);
msgTextField.setEditable(true);
// } else if (e.getSource() == resetbButton) {
//
// sendPortTextField.setText(null);
// receivePorttTextField.setText(null);
// IPAddressTextField.setText(null);
// msgTextField.setText(null);
// showArea.setText(null);
//
// startButton.setEnabled(true);
// sendButton.setEnabled(false);
// msgTextField.setEditable(false);
//
// thread.interrupt();
// if(receivePacket!=null) {
// receiveSocket.close();
// }
} else {/* 按下了发送按键或回车键 */
outbuf = msgTextField.getText().getBytes();
// 组装要发送的的数据包
sendPacket = new DatagramPacket(outbuf, outbuf.length, sendIP, sendPort);
// 发送数据
sendSocket.send(sendPacket);
showArea.append("我说(" + msgTextField.getText() + ")" + getDateTime() + "\n");
msgTextField.setText(null);
}
} catch (UnknownHostException e1) {
showArea.append("getByName无法连接到指定地址!!!" + getDateTime() + "\n");
} catch (SocketException e1) {
showArea.append("DatagramSocket无法打开指定端口!!!" + getDateTime() + "\n");
} catch (IOException e1) {
showArea.append("send数据数据失败!!!" + getDateTime() + "\n");
} catch (NumberFormatException e1) {
showArea.append("设置端口数据格式不对!!!" + getDateTime() + "\n");
}
}
@Override
public void run() {
String msgstr;
while (true) {
try {// 注意try的位置
receiveSocket.receive(receivePacket);
msgstr = new String(receivePacket.getData(), 0, receivePacket.getLength());
showArea.append("对方说(" + msgstr + ")" + getDateTime() + "\n");
} catch (Exception e) {
showArea.append("receive接收数据出错!!!" + getDateTime() + "\n");
}
}
}
public static void main(String[] args) {
new UDPChat();
}
/**
* Java代码中获得当前时间
*
* @return 当前时期时间
*/
private String getDateTime() {
Date date = new Date();
DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String time = format.format(date);
return time;
}
}
- 返回到目录 ?