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的快捷键的方式退出这段程序。