利用UDP实现双向聊天系统

Java笔记:《Java语言程序设计》–郭克华

案例介绍

  UDP是面向无连接的,但并不是没有客户端和服务端的区别。只是说,服务端运行之后,并不一定要等待客户端的连接才能通信,客户端可以直接和服务端通信,发送信息。
  这里介绍一个聊天应用最基本的程序:客户端和服务器相同,都可以给对方发送信息,也能够自动接收对方发过来的信息。本程序效果如图所示。
  利用UDP实现双向聊天系统_第1张图片      利用UDP实现双向聊天系统_第2张图片
  “服务器端”和“客户端”对话框中都有一个文本框,输入聊天信息。输入聊天信息之后,单击“发送”按钮,就能够将信息发送给对方,对方也能够自动收到之后显示。
  显然这个程序在利用TCP实现双向聊天博客中也有介绍,阅读本篇博客之后,可以比较两者之间的差异

服务器和客户端是如何交互的

  在聊天程序中,各聊天的界面叫做客户端,客户端之间如果要相互聊天,则可以将信息先发送到服务器端,然后由服务器端转发。因此,客户端先要连接到服务器。客户端连接到服务器的IP地址和端口。在服务器端,必须监听某个端口。在客户端,必须连接服务器的某个端口,这点没有太大区别。
  1.服务端怎样打开并监听端口?
  UDP编程中,端口的监听是由java.net.DatagramSocket进行管理的,打开java.net.DatagramSocket的文档,这个类有很多构造函数,最常见的构造函数为:
  public DatagramSocket(int port) throws SocketExceptin
  传入一个端口号,实例化DatagramSocket。
  ps:实例化DatagramSocket,就已经打开了端口号并进行监听。
  例如,如下代码就可以监听服务器上的9999端口,并返回连接对象ds。
  DatagramSocket ds=new DatagramSocket(9999);
  2.客户端是怎样连接到服务器端的某个端口?
  客户端连接到服务器端的某个端口也是由java.net.DatagramSocket进行管理的,打开java.net.DatagramSocket的文档,里面有一个重要函数:
  public void connect(InetAddress address,int port)
  传入一个封装了服务器IP地址的InetAddress对象和端口号。
  ps:java.net.InetAddress有一个函数:

public static InetAddress getByName(String host) throws UnknownHostException

这是一个静态函数,可以传入一个IP地址,返回InetAddress对象。
例如,如下代码就可以连接服务器218.197.118.80上的9999端口。

DatagramSocket ds=new DatagramSocket();
InetAddress add=InetAddress.getByteName("218.197.118.80");
ds.connect(add,9999);

  特别提醒
  到这里为止,会发现这岂不是和TCP编程一样?是的,看起来一样,但是本质却不一样。在TCP编程中,接下来的工作就是服务器要获得客户端的连接,然后通过这个连接来进行通信。但是,在UDP编程中,这个工作不要了!就可以直接通信了!也就是说,服务器端不需要获得客户端的连接,它们就直接通过地址来收发信息。因此服务器不需要知道客户端是否连上来了。或说的更加直接一些,客户端以下代码:

InetAddress add=InetAddress.getByName("218.197.118.80");
ds.connect(add,9999);

  实际上并没有连接服务器,只是将服务器的IP地址和端口保存起来,以后在客户端给服务器发送信息的时候,用这个IP地址和端口来寻找到服务器。
  当然,从表面上可以理解成连接到服务器。
  可以将TCP比喻成打电话,必须双方都拿起话机才能通话,并且连接要保持通畅。UDP比喻成寄信,在寄信的时候,对方根本不知道有信要寄过来,信寄到哪里去。靠信封上的地址。

如何收发信息

  以上所述还只是客户端连接到服务器端,接下来应该是客户端和服务器端的通信。通信包括读和写,对于客户端和服务器端,如果将数据传给对方,称为发送;反之,如果从对方处得到数据,为接收。
  注意,前面讲解的TCP编程中,编程过程要用到输入输出流;在UDP情况下,不使用输入输出流,而采用数据包(DatagramPacket)的形式进行通信。对于一方来说,发送数据包,就称为输出;反之,接收数据包,就称为输入。
  打开java.net.DatagramSocket文档,会发现里面有如下两个重要函数:
  1.接收数据包:
  public void receive(DatagramPacket p) throws IOException
  2.发送数据包:
  public void send(DatagramPacket p) throw IOException
  这两个函数都传入一个对象:java.net.DatagramPacket,即数据包。

  在文档中找到java.net.DatagramPacket,具有以下几个重要的构造函数:
  1.创建一个DatagramPacket对象,指定内容和大小:
  public DatagramPacket(byte[] buf,int length)
  2.创建一个DatagramPacket对象,指定内容和大小,以及要发送的目标地址和端口号:
  public DatagramPacket(byte[] buf,ing length,InetAddress address,int port)
  很显然,第2个构造函数相当于给信封上写了寄信地址。

  在这几个函数的参数中,已经发现,如果要发送或接收一个数据包,需要确定以下几个信息:
  1.数据包所含数据,一般是一个字节数组。
  2.数据包大小,可以确定为数据所占字节数。
  3.数据包的发送地址,通过地址才能知道数据包发到哪里去。

  如果要给对方发送数据包,数据包的发送地址必须是指定的,就如同寄信要指定收信人地址一样。怎样为数据包指定发送地址呢?规律如下:
  1.客户端在确定服务器端IP地址的情况下,所创建的DatagramPacket对象,不需要设置发送地址,数据包可以直接发送给服务器端。
  2.服务器事先不知道客户端的地址,因此,服务器必须手工指定发送地址,可以用前面讲解的第二个构造函数,也可以用DatagramPacket的如下函数:
  (1)将DatagramPacket的发送地址设定为发送地址:
  public void setAddress(InetAddress iaddr)
  (2)将DatagramPacket的发送端口设定为指定端口:
  public void setPort(int port)

  这也从侧面说明,如果服务器要想客服端发送信息,但是又不知道客户端的地址,怎么办呢?一般说来,在通信时,必须客户端首先给服务器发送一个DatagramPacket,让服务器端利用这个数据包作为参考,知道客户端的地址,然后和该客户端通信;否则,服务器端就无法给客户端信息。

  打开java.net.DatagramPacket文档,会发现还有如下函数:
  1.设定长度:
  public void setLength(int length)
  2.设定数据:
  public void setData(byte[] buf)
  3.得到数据包中对方的地址:
  public InetAddress getAddress()
  4.得到数据包中对方的端口:
  public int getPort()
  5.得到对方地址和端口的封装:
  public SocketAddress getSocketAddress()
  6.得到数据:
  public byte[] getData()
  7.得到长度:
  public int getLength()

  如下代码表示客户端向服务器端发送一个数据包:

DatagramSocket ds=new DatagramSocket();
InetAddress add=InetAddress.getByName("127.0.0.1");
ds.connect(add,9999);
String msg=("服务器,你好");
byte[] data=msg.getBytes();
DatagramPacket dp=new DatagramPacket(data,data.length);
ds.send(dp);

如下代码表示服务器获得客户端发送过来的数据包,并打印其内容:

DatagramSocket ds=new DatagramSocket(9999);
byte[] data=new byte[255];
DatagramPacket dp=new DatagramPacket(data,data.length);
ds.receive(dp);
String msg=new String(dp.getData(),0,dp.getLength());
System.out.println("已经收到:"+msg);

  从这里可以总结出UDP数据通信的过程:
  1.服务器端监听端口。
  2.客户端“连接”服务器端。
  3.一端创建一个DatagramPacket对象,设定其大小、数据和发送地址;然后用DatagramSocket的send函数发出。
  4.另一端用DatagramSocket的receive函数读取DatagramPacket对象。
  5.获取DatagramPacket中的数据。

  ps:值得一提的是,在客户端与服务器端之间传递信息时,DatagramSocket的receive函数是一个“死等函数”,如果客户端端连接上了,但是没有发送信息,它会一直等待。
  因此,客户端和服务器端如果需要自动读取对方传来的信息,就不能将receive函数放在主线程内,因为在不知道对方在什么时候会发出信息的情况,receive函数的死等,可能会造成程序的阻塞。所以,最好的方法是将读取信息的代码写在专门的线程内。

代码编写

  综合以上叙述,建立服务器端代码如下:

package practice;
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.net.*;
import javax.swing.*;

public class Server extends JFrame implements ActionListener,Runnable {
    private JTextArea taMsg=new JTextArea("以下是聊天记录\n");
    private JTextField tfMsg=new JTextField("请您输入信息\n");
    private JButton btSend=new JButton("发送");
    private DatagramSocket ds=null;
    //保存好客户端的地址和端口
    private SocketAddress cAddress=null;
    public Server() {
        this.setTitle("服务器端");
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.add(taMsg,BorderLayout.CENTER);
        tfMsg.setBackground(Color.yellow);
        this.add(tfMsg,BorderLayout.NORTH);
        this.add(btSend,BorderLayout.SOUTH);
        btSend.addActionListener(this);
        this.setSize(200,300);
        this.setVisible(true);
        try {
            ds=new DatagramSocket(9999);
            new Thread(this).start();
        }
        catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    @Override
    public void run() {
        try {
            while(true) {
                byte[] data=new byte[255];
                DatagramPacket dp=new DatagramPacket(data,data.length);
                ds.receive(dp);
                //保存客户端地址
                cAddress=dp.getSocketAddress();
                String msg=new String(dp.getData(),0,dp.getLength());
                taMsg.append(msg+"\n");//添加内容
            }
        }
        catch (Exception ex) {
        }
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        try {
            String msg="客户端说:"+tfMsg.getText();
            byte[] data=msg.getBytes();
            DatagramPacket dp=new DatagramPacket(data,data.length);
            ds.send(dp);
        }
        catch (Exception ex) {
        }
    }

    public static void main(String[] args) {
        new Server();
    }
}

  运行这个程序就可以得到服务器的效果。
  接下来是客户端程序,代码如下:

package practice;
import java.awt.*;
import java.awt.event.*;
import java.net.*;
import javax.swing.*;

public class Client extends JFrame implements ActionListener,Runnable {
    private JTextArea taMsg=new JTextArea("以下是聊天记录\n");
    private JTextField tfMsg=new JTextField("请您输入信息");
    private JButton btSend=new JButton("发送");
    private DatagramSocket ds=null;
    public Client(){
        this.setTitle("客户端");
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.add(taMsg,BorderLayout.CENTER);
        tfMsg.setBackground(Color.yellow);
        this.add(tfMsg,BorderLayout.NORTH);
        this.add(btSend,BorderLayout.SOUTH);
        btSend.addActionListener(this);
        this.setSize(200,300);
        this.setVisible(true);
        try {
            ds=new DatagramSocket();
            InetAddress add=InetAddress.getByName("127.0.0.1");
            ds.connect(add,9999);
            String msg="客户端连接";
            byte[] data=msg.getBytes();
            DatagramPacket dp=new DatagramPacket(data,data.length);
            ds.send(dp);
            new Thread(this).start();
        }
        catch (Exception ex) {
        }
    }

    @Override
    public void run() {
        try {
            while (true) {
                byte[] data=new byte[255];
                DatagramPacket dp=new DatagramPacket(data,data.length);
                ds.receive(dp);
                String msg=new String(dp.getData(),0,dp.getLength());
                taMsg.append(msg+"\n"); //添加内容
            }
        }
        catch (Exception ex) {
        }
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        try {
            String msg="客户端说:"+tfMsg.getText();
            byte[] data=msg.getBytes();
            DatagramPacket dp=new DatagramPacket(data,data.length);
            ds.send(dp);
        }
        catch (Exception ex) {
        }
    }

    public static void main(String[] args) {
        new Client();
    }
}

  运行得到客户端界面,两者即可进行聊天。

  注意
  1.必须要先运行服务器端,在运行客户端。
  2.如果客户端关掉之后重新开启,也可以继续聊天。在TCP的双向聊天中,这是办不到的。

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