UDP协议一般应用在“群发信息”的场合,所以它可以利用多线程机制,实现多信息的同步发送。
为了改善代码结构,把一些业务逻辑的动作抽象成方法,并封装成类,这样,基于UDP功能的类就可以在其他应用项目里被轻易的重用。
如果把客户端的所有代码都写在一个文件中,那么代码的功能很有可能都聚集在一个方法里,代码的可维护性将会变得很差。所以专门设计ClientBean类,在其中封装了客户端通讯的一些功能方法,在此基础上,通过UDPClient.java文件,实现UDP客户端的功能。
首先,设计ClientBean类。
public class ClientBean { private DatagramSocket ds;//描述UDP通讯的DatagramSocket对象 private byte buffer[];//用来封装通讯字符串 private int clientport;//客户端的端口号 private int serverport;//服务器端的端口号 private String content;//通讯内容 private InetAddress ia;//描述通讯地址 public DatagramSocket getDs() { return ds; } public void setDs(DatagramSocket ds) { this.ds = ds; } public byte[] getBuffer() { return buffer; } public void setBuffer(byte[] buffer) { this.buffer = buffer; } public int getClientport() { return clientport; } public void setClientport(int clientport) { this.clientport = clientport; } public int getServerport() { return serverport; } public void setServerport(int serverport) { this.serverport = serverport; } public String getContent() { return content; } public void setContent(String content) { this.content = content; } public InetAddress getIa() { return ia; } public void setIa(InetAddress ia) { this.ia = ia; } public ClientBean() throws SocketException, UnknownHostException { buffer = new byte[1024]; clientport = 1985; serverport = 1986; content = ""; ds = new DatagramSocket(clientport); ia = InetAddress.getByName("localhost"); } public void sendToServer() throws IOException{ buffer = content.getBytes(); ds.send(new DatagramPacket(buffer, content.length(), ia, serverport)); } }
在上述的代码里定义了描述用来实现UDP通讯的DatagramSocket类型对象ds,
描述客户端和服务器端的端口号clientport和serverport,
用于描述通讯信息的buffer和content对象。buffer对象是byte数组类型的,可通过UDP的数据报文传输,而content是String类型的,在应用层面表示用户之间的通讯内容,
另外还定义了InetAddress类型的ia变量,用来封装通讯地址信息。
在构造函数里,给哥哥变量赋予了初始值,分别设置了客户端和服务端的端口号,设置了通讯链接地址为本地,并根据客户端的端口号初始化了DatagramSocket对象。当初始化ClientBean时,这段构造函数会自动执行,完成设置通讯各参数等工作。
向服务端发送消息的sendToServer()方法,根据String类型的表示通讯信息的content变量,初始化UDP数据报文,即DatagramPacket对象,并通过DatagramSocket类型对象的send方法,发送该UDP报文。
纵观ClientBean类,可以发现在其中封装了诸如通讯端口,通讯内容和通讯报文等对象以及以UDP方式发送信息的sendToServer方法,所以,在UDPClient类里,可以直接调用其中的借口,方便地实现通讯功能。
其次,设计UDPClient类
public class UDPClient implements Runnable{ public static String content; public static ClientBean client; @Override public void run() { try { client.setContent(content); client.sendToServer(); } catch (Exception e) { System.err.println(e.getMessage()); } } public static void main(String[] args) throws IOException { BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); client = new ClientBean(); System.out.println("客户端启动..."); while(true){ content = br.readLine();//接收用户输入 if(content == null||content.equalsIgnoreCase("end")||content.equalsIgnoreCase("")){ break; } new Thread(new UDPClient()).start();//开启新线程,发送消息 } } }由于要在 UDP 客户端里通过多线程的机制,同时开多个客户端,向服务器端发送通讯内容,所以我们的 UDPClient 类必须要实现 Runnable 接口,并在其中覆盖掉 Runnable 接口里的 run 方法。run方法里,我们主要通过了ClientBean类里封装的方法,设置了content内容,并通过了sentToServer方法,将content内容以数据报文的形式发送到服务器端。一旦线程被开启,系统会自动执行定义在run方法里的动作。
main方法里首先初始化了BufferedReader类型的br对象,该对象可以接收从键盘输入的字符串。随后启动一个while(true)的循环,在这个循环体里,接收用户从键盘的输入,如果用户输入的字符串不是“end”,或不是为空,则开启一个UDPClient类型的线程,并通过定义在run方法里的线程主体动作,发送接收到的消息。如果在循环体里,接收到“end”或空字符,则通过break语句,退出循环。对于每次UDP发送请求,UDPClient类都将会启动一个线程来发送消息。
同样的,我们把服务器端所需要的一些通用方法以类的形式封装,而在UDP的服务器端,通过调用封装在ServerBean类里的方法来完成信息的接收工作。
首先,设计ServerBean类
public class ServerBean { private DatagramSocket ds;//描述UDP通讯的DatagramSocket对象 private byte buffer[];//用来封装通讯字符串 private int clientport;//客户端的端口号 private int serverport;//服务器端的端口号 private String content;//通讯内容 private InetAddress ia;//描述通讯地址 public DatagramSocket getDs() { return ds; } public void setDs(DatagramSocket ds) { this.ds = ds; } public byte[] getBuffer() { return buffer; } public void setBuffer(byte[] buffer) { this.buffer = buffer; } public int getClientport() { return clientport; } public void setClientport(int clientport) { this.clientport = clientport; } public int getServerport() { return serverport; } public void setServerport(int serverport) { this.serverport = serverport; } public String getContent() { return content; } public void setContent(String content) { this.content = content; } public InetAddress getIa() { return ia; } public void setIa(InetAddress ia) { this.ia = ia; } public ServerBean() throws SocketException, UnknownHostException { buffer = new byte[1024]; clientport = 1985; serverport = 1986; content = ""; ds = new DatagramSocket(serverport); ia = InetAddress.getByName("localhost"); } public void listenClient() throws IOException { while(true){ //初始化DatagramPacket类型的变量 DatagramPacket dp = new DatagramPacket(buffer, buffer.length); //接收消息,并把消息通过dp参数返回 ds.receive(dp); content = new String(dp.getData(),0,dp.getLength()); print();//打印消息 } } private void print() { System.out.println(content); } }
在UDP的服务端里,为了同客户端对应,所以同样把clientport和serverport值设置为1985和1986,同时初始化了DatagramSocket对象,并把服务器的地址也设置成本地。
在listenClient()方法里,构造了一个while循环,循环体内部,调用了封装在DatagramSocket类型里的receive方法,接收客户端发送过来的UDP报文,并打印出来。
接着,来设计UDPServer类
public class UDPServer { public static void main(String[] args) throws IOException { System.out.println("服务端启动..."); //初始化ServerBean对象 ServerBean server = new ServerBean(); server.listenClient(); } }
在UDP的服务器端里,主要通过ServerBean类里提供的listenClient方法,监听从客户端发送过来的UDP报文,并通过解析得到其中包含的字符串,随后输出。
最后,进行测试。先开启服务端,然后开启客户端,在客户端通过键盘向服务器端输入通讯字符串,这些字符串将会以数据报文的形式发送到服务器端
每当我们在客户端发送一条消息,服务器端会收到并输出这条消息,从代码里我们可以得知,每条消息是通过为之新开启的线程发送到服务器端的。如果我们在客户端输入”end”或空字符串,客户端的UDPClient代码会退出。由于UDPServer.java代码里,我们通过一个while(true)的循环来监听客户端的请求,所以当程序运行结束后,可通过Ctrl+C的快捷键的方式退出这段程序。