【网络编程2】Java数据报套接字

这篇博文是本文学习《Java网络程序设计》书中第5章数据报套接字的学习总结。初学者网友学习这篇Java数据报套接字文章,如果难于理解文章前面理论部分,可以先运行后面的程序,边看运行后面的程序边理解前面的原理,这对初学者是最好的方法。所有源代码都在文章后面我的github链接代码中。
——惠州学院13网络工程 吴成兵 20160609

目录 1

文章目录

  • 目录 [^footnote]
  • 一 数据报套接字概述
  • 二 DatagramPacket
    • 2.1 创建DatagramPacket对象
      • 2.1.1 创建的DatagramPacket对象用于接收数据
      • 2.1.2 创建的DatagramPacket对象用于发送数据##
    • 2.2 DatagramPacket常用方法
  • 三 DatagramSocket
    • 3.1 创建DatagramSocket对象
    • 3.2 DatagramSocket常用方法
  • 四 DatagramSocket编程示例
    • 4.1 利用DatagramSocket查询端口占用情况
    • 4.2 利用数据报通信的C/S程序
      • 4.2.1 数据接收端
      • 4.2.2 数据发送端
    • 4.3 用UDP实现的聊天程序

一 数据报套接字概述

流套接字的每个连接均要花费一定的时间,为了减少这种开销,网络API提供了第二种套接字——数据报套接字(DatagramSocket),又称自寻址套接字
【网络编程2】Java数据报套接字_第1张图片
  数据报套接字基于的协议是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查询端口占用情况

【网络编程2】Java数据报套接字_第2张图片

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程序

【网络编程2】Java数据报套接字_第3张图片
这里写图片描述
这里写图片描述

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实现的聊天程序

【网络编程2】Java数据报套接字_第4张图片

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;
	}
}


文中所有源代码链接

Wu_Being博客声明:本人博客欢迎转载,请标明博客原文和原链接!谢谢!
《Java数据报套接字》:
http://blog.csdn.net/u014134180/article/details/51636009


  1. 返回到目录 ↩︎

你可能感兴趣的:(Java网络编程)