Java网络通信

本文详细介绍了Java中的TCP和UDP网络通信。在TCP部分,讲解了InetAddress类的功能以及ServerSocket类的使用,包括不同构造方法和accept()方法的作用。在UDP部分,阐述了基于UDP通信的基本模式,以及DatagramPacket和DatagramSocket类的运用,展示了发送和接收数据包的步骤。

一、TCP程序设计

1、InetAddress类

       java.net包中的InetAddress类是与IP地址相关的类,利用该类可以获取IP地址、主机地址等信息。InetAddress类的常用方法如下表所示。

注:InetAddress类的方法会抛出UnknownHostException 异常,所以必须进行异常处理。这个异常在主机不存在或网络连接错误时发生。

2、ServerSocket类

       java.net包中的ServerSocket类用于表示服务器套接字,其主要功能是等待来自网络上的“请求”,它可通过指定的端口来等待连接的套接字。服务器套接字一次可以与一个套接字连接。如果多台客户机同时提出连接请求,服务器套接字会将请求连接的客户机存入列队中,然后从中取出一个套接字,与服务器新建的套接字连接起来。若请求连接数(backlog)大于最大容纳数,则多出的连接请求被拒绝。队列的默认大小是50。

       ServerSocket类的构造方法都抛出IOException异常,分别有以下几种形式。

  • ServerSocket():创建非绑定服务器套接字。
  • ServerSocket(int port):创建绑定到特定端口的服务器套接字。
  • ServerSocket(int port, int backlog);利用指定的backlog(连接数)创建服务器套接字并将其绑定到指定的本地端口号。
  • ServerSocket(int port, int backlog, InetAddress binddress);使用指定的端口、侦听backlog和要绑定到的本地IP地址创建服务器。这种情况适用于计算机上有多块网卡和多个IP 地址的情况,用于可以明确规定ServerSocket在哪块网卡或IP地址上等待客户的连接请求。

ServerSocket类的常用方法如下表所示。

       调用ServerSocket类的accept()方法会返回一个和客户端Socket对象相连接的Socket对象,服务器端的Socket对象使用getOutputStream()方法获得的输出流将指向客户端Socket对象使用getInputStream()方法获得的那个输入流;同样,服务器端的Socket对象使用getInputStream()方法获得的输入流将指向客户端Socket对象使用getOutputStream()方 法获得的那个输出流。也就是说,当服务器向输出流写入信息时,客户端通过相应的输入流就能读取,反之亦然。accept()方法会阻塞线程的继续执行,直到接收到客户的请求。

例:TCP网络程序

(1)服务端程序

import java.io.*;
import java.net.*;

public class MyTcp {
	private BufferedReader reader;
	private ServerSocket server;
	private Socket socket;
	
	void getserver() {
		try {
			server = new ServerSocket(8998);	// 1.实例化socket对象
			System.out.println("服务器套接字已经创建成功");
			while (true) {
				System.out.println("等待客户机的连接");
				socket = server.accept();		// 2.实例化socket对象			
				reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));// 3.读/写数据	
				getClientMessage();
			}
		} catch (Exception e) {
			e.printStackTrace(); // 异常输出
		}
	}
	
	private void getClientMessage() {
		try {
			while (true) {
				if (reader.ready()) {
					// 获得客户端信息
					System.out.println("客户机:" + reader.readLine());
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		
		// 注意关闭
		try {
			// 4.关闭流
			if (reader != null) {
				reader.close();
			}
			// 5.关闭套接字
			if (socket != null) {
				socket.close();
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	public static void main(String[] args) {
		MyTcp tcp = new MyTcp();
		tcp.getserver();
	}

}

输出:

(2)客户端程序

import java.net.*;
import java.io.*;
import java.awt.*;
import java.awt.event.*;

import javax.swing.*;
import javax.swing.border.*;

public class MyClien extends JFrame{
	Socket socket;
	private PrintWriter writer;
	
	Container cc;	// 物件容器
	private JTextArea ta = new JTextArea();
	private JTextField tf = new JTextField();
	
	public MyClien(String title) {
		super(title);	// 调用父类的构造方法
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		cc = this.getContentPane();		// 实例化对象
		
		final JScrollPane scrollPane = new JScrollPane();
		scrollPane.setBorder(new BevelBorder(BevelBorder.RAISED));
		getContentPane().add(scrollPane, BorderLayout.CENTER);
		scrollPane.setViewportView(ta);
		cc.add(tf, "South");	// 将文本框放在窗体的下部
		
		tf.addActionListener(new ActionListener() {
			// 绑定事件 
			public void actionPerformed(ActionEvent e) {
				writer.println(tf.getText());	// 将文本框中的信息写入流
				ta.append(tf.getText() + "\n"); // 将文本框中的信息显示在文本域
				ta.setSelectionEnd(ta.getText().length());
				tf.setText(""); 				// 将文本框清空
			}
		});
	}
	
	private void connect() {
		ta.append("尝试连接\n");
		try {
			socket = new Socket("127.0.0.1", 8998);						// 1.实例化socket
			writer = new PrintWriter(socket.getOutputStream(), true);	// 2.读/写数据
			ta.append("完成连接\n");
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
		
	public static void main(String[] args) {
		MyClien client = new MyClien("向服务器送数据");
		client.setSize(200, 200);
		client.setVisible(true);
		client.connect();
	}
}

输出:

       注:当一台机器上安装了多个网络应用程序时,很可能指定的端口号已被占用。还可能遇到以前运行良好的网絡程序突然运行不了的情况,这种情况很可能也是由于端口被别的程序占用了。此时可以运行netstat-help来获得帮助,使用命令netstat-an 来查看该程序所使用的端口。

 

二、UDP程序设计

1、基于UDP通信的基本模式如下:

  • 将数据打包(称为数据包),然后将数据包发往目的地。
  • 接收别人发来的数据包, 然后查看数据包。

下面是总结的UDP程序的步骤。.

发送数据包的步骤如下:

(1)使用DatagramSocket()创建一个数据 包套接字。

(2)使用DatagramPacket(byte[] buf, int offset, int length, InetAddress address, int port)创建要发送的数据包。

(3)使用DatagramSocket类的send()方法发送数据包。

接收数据包的步骤如下

(1)使用DatagramSocket(int port)创建数据包套接字,绑定到指定的端口。

(2)使用DatagramPacket(byte[] buf, int length)创建字节数组来接收数据包。

(3)使用DatagramPacket类的receive()方法接收UDP包。

       注:DatagramSocket类的receive()方法接收数据时,如果还没有可以接收的数据,在正常情况下receive()方法将阻塞,一直等到网络上有数据传来,receive()方法接收该数据并返回,如果网络上没有数据发送过来,receive()方法 也没有阻塞,肯定是程序有问题,大多数是使用了一个被其他程序占用的端口号。

2、DatagramPacket类

       java.net包的DatagramPacket类用来表示数据包。DatagramPacket 类的构造函数有:

  • DatagramPacket(byte[] buf, int length)。
  • DatagramPacket(byte[] buf, int length, InetAddress address, int port)。

       第一种构造函数创建DatagramPacket对象,指定了数据包的内存空间和大小。第二种构造函数不仅指定了数据包的内存空间和大小,还指定了数据包的目标地址和端口。在发送数据时,必须指定接收方的Socket地址和端口号,因此使用第二种构造函数可创建发送数据的DatagramPacket对象。

3、DatagramSocket类

       java.net包中的DatagramSocket类用于表示发送和接收数据包的套接字。该类的构造函数有:

  • DatagramSocket()。
  • DatagramSocket(int port)。
  • DatagramSocket(int port, InetAddress addr)。

       第一种构造函数创建DatagramSocket对象,构造数据报套接字并将其绑定到本地主机上任何可用的端口。第二种构造函数创建DatagramSocket对象,创建数据报套接字并将其绑定到本地主机上的指定端口。第三种构造函数创建DatagramSocket对象,创建数据报套接字并将其绑定到指定的本地地址。第三种构造函数适用于有多块网卡和多个IP地址的情况。

       在接收程序时,必须指定一个端口号,不要让系统随机产生,此时可以使用第二种构造函数。比如有个朋友要你给他写信,可他的地址不确定是不行的。在发送程序时,通常使用第一种构造函数,不指定端口号,这样系统就会为我们分配-一个端口号。就像寄信不需要到指定的邮局去寄-样。

例:UDP网络程序

(1)广播主机程序不断地向外播出信息

import java.net.*;

public class Weather extends Thread{
	String weather = "大家好";
	int port = 9898;
	InetAddress iaddress = null;	
	MulticastSocket socket = null;
	
	public Weather() {
		try {
			iaddress = InetAddress.getByName("224.255.10.0");	// 1.实例化InetAddress
			socket = new MulticastSocket(port);	                // 2.实例化MulticastSocket多点广播套接字
			socket.setTimeToLive(1);                        // 指定发送范围是本地网络
			socket.joinGroup(iaddress);                     // 加入广播组
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	public void run() {
		while (true) {
			DatagramPacket packet = null;		
			byte data[] = weather.getBytes();
			packet = new DatagramPacket(data, data.length, iaddress, port);	//3.实例化DatagramPacket,将数据打包
			System.out.println(new String(data));			// 将广播信息输出	
			try {
				socket.send(packet);                                  // 4、发送数据
				sleep(3000);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}
	
	public static void main(String[] args) {
		Weather w = new Weather();
		w.start();
	}
}

输出:

(2)接收广播程序

import java.net.*;
import java.awt.*;
import java.awt.event.*;

import javax.swing.*;

public class Receive extends JFrame implements Runnable, ActionListener{
	int port;
	InetAddress group = null;
	MulticastSocket socket = null;
	JButton ince = new JButton("开始接收");
	JButton stop = new JButton("停止接收");
	JTextArea inceAr = new JTextArea(10, 10);
	JTextArea inced = new JTextArea(10, 10);
	Thread thread;
	boolean b =false;
	
	public  Receive() {
		super("广播数据报");
		setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
		thread = new Thread(this);
		ince.addActionListener(this); 		// 绑定按钮ince的单击事件
		stop.addActionListener(this); 		// 绑定按钮stop的单击事件
		inceAr.setForeground(Color.blue); 	// 指定文本域中文字的颜色
		JPanel north = new JPanel(); 		// 创建JPanel对象
		north.add(ince);
		north.add(stop);
		add(north, BorderLayout.NORTH);
		JPanel center = new JPanel();
		center.setLayout(new GridLayout(1, 2));
		center.add(inceAr);
		center.add(inced);
		add(center, BorderLayout.CENTER);
		validate(); 			// 刷新
		port = 9898;
		try {
			group = InetAddress.getByName("224.255.10.0")    // 1.指定接收地址
			socket = new MulticastSocket(port);	         // 2. 绑定多点广播套接字
			socket.joinGroup(group); 						// 加入广播组
		} catch (Exception e) {
			e.printStackTrace();
		}
		setBounds(100, 50, 360, 380);		// 设置布局
		setVisible(true); 					//  将窗体设置为显示状态
	}
	
	@Override
	public void run() {
		while (true) {
			byte data[] = new byte[1024];	// 创建byte数组
			DatagramPacket packet = null;   
			packet = new DatagramPacket(data, data.length, group, port); // 3.待接收包内容
			try {
				socket.receive(packet); 								 // 4.接收数据包
				String message = new String(packet.getData(), 0, packet.getLength()); 	// 获取数据包中内容
				inceAr.setText("正在接收的内容:\n" + message);  // 将接收内容显示在文本域中
				inced.append(message + "\n"); 				// 每条信息为一行
			} catch (Exception e) {
				e.printStackTrace();
			}
			if (b == true) {
				break;
			}
		}
		
		
	}

	@Override
	// 单击事件
	public void actionPerformed(ActionEvent e) {
		// 单击按钮ince触发的事件
		if (e.getSource() == ince) {
			ince.setBackground(Color.red);
			stop.setBackground(Color.yellow);
			if (!(thread.isAlive())) {		// 如线程不处于“新建状态”
				thread = new Thread(this);	// 实例化Thread对象
			}
			thread.start();
			b = false;
		}
		
		// 单击按钮stop触发的事件
		if (e.getSource() == stop) {
			ince.setBackground(Color.yellow); 
			stop.setBackground(Color.red);
			b = true; 
		}		
	}

	public static void main(String[] args) {
		Receive rec = new Receive();
		rec.setSize(460, 200);
	}
}

输出:

       注:要广播或接收广播的主机地址必须加入到一个组内,地址范围为224.0.0.0~224.255.255.255,这类地址并不代表某个特定主机的位置。加入到同一个组的主机可以在某个端口上广播信息。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值