利用UDP实现用户聊天程序

   UDP是面向非连接的,UDP传输的是数据报只负责传输信息,不保证信息一定收到,虽然安全性不如TCP(面向连接、用Socket进行通信),但是性能较好。

从简单到复杂,首先简单介绍一下怎么利用UDP实现客服端发送消息给服务器端吧:

发送、接受消息过程大概如下:

服务器端创建DatagramSocket对象用于打开指定端口并监听,然后用创建一个DatagramPacket,利用DatagramSocket中的receive(ds)方法接收数据并放到刚创建的DatagramPacket对象中;
这样就完成了简单的发送,接收过程。

客户端:创建DatagramSocket对象,创建好IP地址和端口号后,利用DatagramSocket中的connect(ip,port)方法和服务端建立连接,然后利用DatagramSocket中的send(dp)方法发送早已准备好的数据。

服务器端代码:
package Socket;

import java.net.DatagramPacket;
import java.net.DatagramSocket;

public class server {
	private DatagramSocket ds = null;
	private int Port = 9999; 
	public server(){	
		try{
			ds = new DatagramSocket(Port);
			byte[] data = new byte[255];
			DatagramPacket dp = new DatagramPacket(data,data.length);
			ds.receive(dp);
			String str = new String(dp.getData());
			System.out.println(str);
		}catch(Exception e){			
		}	
	}
	public static void main(String[] args){
		new server();
	}
}

客户端代码:
package Socket;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;

public class client{	
	private DatagramSocket ds;
	private int Port = 9999;
	public client(){		
		try{		
			ds = new DatagramSocket();
			InetAddress address = InetAddress.getByName("Localhost");
			ds.connect(address,Port);
			String str = "客户端连接";
			byte[] data = str.getBytes();
			DatagramPacket dp = new DatagramPacket(data,data.length);
			ds.send(dp);			
		}catch(Exception e){		
		}
	}
	public static void main(String[] args){
		new client();
	}
}

现在都了解了UDP是怎么实现发送和接收信息的了,怎样实现客户端和服务器端之间的相互聊天呢?这就需要服务器端和客户端能够自动的读取对方传过来的信息了,接受消息是通过调用receive函数,该函数的特点是如果未收到消息就会死等,这样就容易造成程序的阻塞,解决方法是将读取信息的代码写到专门的线程内,这里用Runnable接口。 为了使聊天过程更加清楚易懂,加入GUI。

客户端和服务器端聊天过程大概如下:


服务器端:编写一个简单的聊天图形界面,并在构造函数中用已经创建好的DatagramSocket对象打开特点的端口监听,并用Thread开启Runnable线程。重写run()函数,将接受数据的receive函数写在这里,使程序在任何时候都能够接收客户端发送的数据,注意数据中包含客户端的IP地址,将它保存起来。另外加一个按钮实现服务器端发送数据,过程和上述客户端发送数据差不多,区别在于上述客户端和服务器端已经connect而现在没有,所以创建DatagramPacket对象使需要在后面加上已经保存的客户端IP地址,然后用send发送数据。

客户端:客户端和服务器端都差不多,区别在于,客户端需要在构造函数中先和服务器端建立连接,给服务器端发送一个数据包,表示已经建立连接(其实是告诉服务器端自己的IP地址)然后再打开线程。在run()函数中不用保存服务器地址。在发送消息按钮处不需要给DatagramPacket对象加IP地址,因为客户端早已和服务器端建立连接。

服务器端代码:
package alone;

import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketAddress;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JTextArea;
import javax.swing.JTextField;

public class Server extends JFrame implements Runnable,ActionListener{
	
	private SocketAddress clientip = null;
	private DatagramSocket DS;
	private int Port = 9999;
	
	private JButton JB = new JButton("发送");
	private JTextField field = new JTextField();
	private JTextArea area = new JTextArea("聊天内容:\n");
	
	public Server(){
		
		this.setTitle("服务器");
		this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		this.add(JB,BorderLayout.SOUTH);
		JB.addActionListener(this);
		this.add(field, BorderLayout.NORTH);
		this.add(area, BorderLayout.CENTER);
		this.setSize(180, 220);
		this.setVisible(true);
		
		try {
			
			DS = new DatagramSocket(Port);
			new Thread(this).start();
	
		} catch (Exception ex) {
			ex.printStackTrace();
		} 
	}
	
	public void run(){
		try{
			while(true){	
				
				byte[] data = new byte[255];
				DatagramPacket DP = new DatagramPacket(data,data.length);				
				DS.receive(DP);			
				clientip = DP.getSocketAddress(); 				
				String str = new String(DP.getData(),0,DP.getLength());
				area.append(str + '\n');
			}			
		}catch(Exception ex){			
		}		
	}
	public void actionPerformed(ActionEvent e){
		try{
			String str = "服务端说:" + field.getText();
			byte[] dd = str.getBytes();
			DatagramPacket Data = new DatagramPacket(dd,dd.length,clientip);
			DS.send(Data);
		}catch(Exception ex){			
		}
	}
	public static void main(String[] args){
		new Server();
	}
}

客服端代码:
package alone;

import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JTextArea;
import javax.swing.JTextField;

public class Client extends JFrame implements Runnable,ActionListener {
	
	private JButton JB = new JButton("发送");
	private JTextField field = new JTextField();
	private JTextArea area = new JTextArea("聊天内容:\n");
	
	private int Port = 9999;
	private DatagramSocket DS;
	
	public Client(){
		
		this.setTitle("客户端");
		this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		this.add(area, BorderLayout.CENTER);
		this.add(JB, BorderLayout.SOUTH);
		JB.addActionListener(this);
		this.add(field, BorderLayout.NORTH);
		this.setSize(180, 220);
		this.setVisible(true);
		
		try {
			DS = new DatagramSocket();
			InetAddress address = InetAddress.getByName("Localhost");
			DS.connect(address,Port);
			
			String str = "客户连接";
			byte[] data = str.getBytes();
			DatagramPacket DP = new DatagramPacket(data,data.length);
			
			DS.send(DP);
			new Thread(this).start();
			
		} catch (Exception e) {			
		}		
	}
	public void run(){
		try{			
			while(true){
				byte[] data = new byte[255];
				DatagramPacket DP = new DatagramPacket(data,data.length);				
				DS.receive(DP);				
				String str = new String(DP.getData());
				area.append(str + '\n');
			}			
		}catch(Exception ex){				
		}
	}
	public void actionPerformed(ActionEvent e){
		try{
			String str = "客户端说:" + field.getText();
			byte[] dd = str.getBytes();
			DatagramPacket Data = new DatagramPacket(dd,dd.length);
			DS.send(Data);
		}catch(Exception ex){			
		}	
	}
	public static void main(String[] args){
		new Client();
	}
}

演练一下:

利用UDP实现用户聊天程序_第1张图片

过程:先打开服务器端,然后在打开客户端,这时候服务器GUI中会显示客户连接,然后就可以在两边的输入框中输入聊天信息。


现在我们已经了解了服务器端和单个客户端是怎么样通信的,但是实际应用中应该是客户端与客户端进行聊天通信的啊,那怎样实现呢?客户端与客户端聊天的本质就是服务器端将收到的信息转发给每一个客户端。

客户端和客户端聊天过程大概如下:


服务器端:服务器端不需要知道有多少客户端要连接,所以就不需要多个线程负责接收客户端连接。服务器端不知道客户端什么时候需要连接,所以就需要开启一个线程来接受客户端发送的消息,注意只需要一个线程即可。服务器端收到消息后,需要发给各个客户端,这就需要发送客户端的IP地址,我们知道用receive()方法可以接受数据,DatagramPacket中就包含了IP地址,由于是多个客户端就需要一个集合专门保存各个客户端的IP地址。
由于在这里服务器端只进行转发数据的作用,所以我减去了不必要的GUI,所以运行完客户端后需要手动关闭服务端程序。

客户端:因为是多个客户端之间进行聊天,所以就需要一个name来区别每一个客户端,打开客户端需要输入昵称。连接服务器、发送消息、接收服务器传来的消息,这些操作和上述的类似,这里不在介绍。(彩蛋)在客户端GUI中我实现了用回车发送消息,并清空输入框的效果。

服务器端代码:
package group;

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketAddress;
import java.util.ArrayList;

public class gServer implements Runnable{
	
	private DatagramSocket DS;
	private int Port = 9998;
	
	private ArrayList clients = new ArrayList(); //保存客户端IP地址
	
	public gServer() throws Exception{		
		try {			
			DS = new DatagramSocket(Port);
			new Thread(this).start();	
		} catch (Exception ex) {
		} 
	}	
	public void run(){
		try{
			while(true){			
				byte[] data = new byte[255];
				DatagramPacket DP = new DatagramPacket(data,data.length);				
				DS.receive(DP);
				
				SocketAddress clientip = DP.getSocketAddress(); 
				
				if(!clients.contains(clientip)){
					clients.add(clientip);
				}
				this.sendAll(DP);
			}		
		}catch(Exception ex){			
		}		
	}	
	private void sendAll(DatagramPacket dp) throws Exception {
		for(SocketAddress sa : clients){
			DatagramPacket dd = new DatagramPacket(dp.getData(),dp.getLength(),sa);				
			DS.send(dd);				
		}
	}
	public static void main(String[] args) throws Exception{
		new gServer();
	}
}

客户端代码:
package group;

import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;

import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;

public class gClient extends JFrame implements Runnable,ActionListener {
	
	private JTextField field = new JTextField();
	private JTextArea area = new JTextArea("聊天内容:\n");
	
	private String name = null;
	
	private int Port = 9998;
	private DatagramSocket DS;
	
	public gClient(){
		
		this.setTitle("客户端");
		this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		this.add(area, BorderLayout.CENTER);
		this.add(field, BorderLayout.SOUTH);
		field.addActionListener(this);
		this.setSize(220, 290);
		this.setVisible(true);
		
		name = JOptionPane.showInputDialog("输入昵称");
		
		try {
			DS = new DatagramSocket();
			InetAddress address = InetAddress.getByName("Localhost");
			DS.connect(address,Port);
			
			String str = name + "登录!";
			byte[] data = str.getBytes();
			DatagramPacket DP = new DatagramPacket(data,data.length);
			
			DS.send(DP);
			new Thread(this).start();
			
		} catch (Exception e) {
		}		
	}	
	public void run(){
		try{			
			while(true){
				byte[] data = new byte[255];
				DatagramPacket DP = new DatagramPacket(data,data.length);				
				DS.receive(DP);				
				String str = new String(DP.getData(),0,DP.getLength());
				area.append(str + '\n');
			}			
		}catch(Exception ex){				
		}
	}
	public void actionPerformed(ActionEvent e){
		try{
			String str = name + "说:" + field.getText();
			byte[] dd = str.getBytes();
			DatagramPacket Data = new DatagramPacket(dd,dd.length);
			DS.send(Data);
			field.setText("");
		}catch(Exception ex){			
		}		
	}
	public static void main(String[] args){
		new gClient();
	}
}


演练一下:

利用UDP实现用户聊天程序_第2张图片
利用UDP实现用户聊天程序_第3张图片

过程:先打开服务器端,然后逐次打开客户端,弹出输入框输入昵称这里是Dkangel、yc、xx,然后在各自的GUI输入框中输入想发送的消息,回车就可以了。

到这里我所要分享的内容就已经完了,由于自己比较懒所以代码基本没有什么注释,就这样吧。good night!

你可能感兴趣的:(java)