【Java】UDP Socket编程案例——文件传输&聊天工具

UDP(用户数据报协议)就像日常生活中的邮件投递,是不能保证可靠地寄到目的地。UDP是无连接的,对系统资源的要求较少,UDP可能丢包,也不保证数据顺序。但是对于网络游戏和在线视频等要求传输快,实时性高,质量可稍差一点的数据传输,UDP还是非常不错的。UDP是无连接协议,不需要像TCP一样监听端口,建立连接,然后才能进行通信。

java.net包中提供了两个类:DatagramSocket和DatagramPacket,用来支持UDP通信。

DatagramSocket用于在程序之间建立传送数据报的通信连接。

DatagramPacket用来表示数据报包,是数据传输的载体。DatagramPacket实现无连接数据包投递服务,每次投递数据包仅限根据该包中信息从一台机器路由到另一台机器。从一台机器发送到另一台机器的多个包可能选择不同的路由,也可能按不同的顺序到达,不保证包都能到达目的。

案例一:文件上传工具

服务端UploadServer代码如下:

package udp;

import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;

public class UploadServer {

	public static void main(String[] args) {
		System.out.println("服务器端运行...");
		//创建一个子线程
		Thread t=new Thread(()->{
			try (
					//创建DatagramSocket对象,指定本地主机端口8080。
                                        //【作为服务器一般应明确指定绑定的端口】
					DatagramSocket socket=new DatagramSocket(8080);
					FileOutputStream fout=new FileOutputStream("TestDir/subDir/coco.jpg");
					BufferedOutputStream out=new BufferedOutputStream(fout)
					){
				//准备一个缓冲区
				byte[] buffer=new byte[1024];
				//循环接收数据包
				while(true)
				{
					//创建数据包对象,用来接收数据
					DatagramPacket packet=new DatagramPacket(buffer, buffer.length);
					//接收数据包
					socket.receive(packet);
					//接收数据长度
					int len=packet.getLength();
					if(len==3)
					{
						//获得结束标志
						String flag=new String(buffer,0,3);
						//判断结束标志,如果是bye,则结束接收
						if(flag.equals("bye"))
						{
							break;
						}
					}
					//写入数据到文件输出流
					out.write(buffer,0,len);
				}
				System.out.println("接收完成!");
			} catch (IOException e) {
				e.printStackTrace();
			}
				});
		//启动线程
		t.start();
	}
}

上述代码创建一个子线程,由于客户端上传的数据分为很多数据包,因此需要一个循环接收数据包,另外,调用后receive()方法会导致线程阻塞,因此需要将接收数据的处理放到一个子线程中。

与TCP Socket不同,UDP Socket无法知道哪些数据包已经是最后一个了,因此需要发送方发出一个特殊的数据包,包中包含了一些特殊标志。然后提取并判断这个标志。

客户端UpdateClient代码如下:

package udp;

import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;

public class UploadClient {

	public static void main(String[] args) {
		System.out.println("客户端运行...");
		try (
				//创建DatagramSocket对象,由系统分配可以使用的端口
				DatagramSocket socket=new DatagramSocket();
				//创建文件输入流
				FileInputStream fin=new FileInputStream("TestDir/coco.jpg");
				//由文件输入流创建缓冲输入流
				BufferedInputStream in=new BufferedInputStream(fin)
				){
			//创建远程主机IP地址对象
			InetAddress address=InetAddress.getByName("localhost");
			
			//准备一个缓冲区
			byte[] buffer=new byte[1024];
			//首次读取文件
			int len=in.read(buffer);
			while(len!=-1)
			{
				//创建数据报包对象
				DatagramPacket packet=new DatagramPacket(buffer,len,address,8080);
				//发送数据包
				socket.send(packet);
				//再次读取文件
				len=in.read(buffer);
			}
			//创建数据报对象
			DatagramPacket packet=new DatagramPacket("bye".getBytes(),3,address,8080);
			//发送结束标志
			socket.send(packet);
			System.out.println("上传成功!");
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

}

上述代码是上传文件客户端,发送数据不会阻塞线程,因此没有使用子线程。客户端DatagramSocket对象经常自己不指定端口,而是由系统分配可以使用的端口。

在文件内容传输结束以后,需要发送一个结束标志,这个结束标志是字符串"bye",服务器端收到这个字符串则结束接收数据包。

案例二:聊天工具

服务器端ChatServer代码如下:

package udp;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;

public class ChatServer {

	public static void main(String[] args) {
		System.out.println("服务器运行...");
		Thread t=new Thread(()->{
				try(
						//创建DatagramSocket对象,指定端口8080
						DatagramSocket socket=new DatagramSocket(8080);
						//InputStreamReader将字节流转换为字符流
						BufferedReader keyboardIn=new BufferedReader(new InputStreamReader(System.in))
						) {
					while(true)
					{
						/*接收数据报*/
						//准备一个缓冲区
						byte[] buffer=new byte[128];
						DatagramPacket packet=new DatagramPacket(buffer,buffer.length);
						socket.receive(packet);
						//接收数据长度
						int len=packet.getLength();
						String str=new String(buffer,0,len);
						//打印接收的数据
						System.out.printf("从客户端接收的数据:【%s】\n",str);
						
						/*发送数据*/
						//从客户端传来的数据包中得到客户端地址
						InetAddress address=packet.getAddress();
						//从客户端传来的数据包中得到客户端端口号
						int port=packet.getPort();
						//读取键盘输入的字符串
						String keyboardInputString=keyboardIn.readLine();
						//读取键盘输入的字节数组
						byte[] b=keyboardInputString.getBytes();
						//创建DatagramPacket对象,用于向客户端发送数据
						packet=new DatagramPacket(b,b.length,address,port);
						//向客户端发送数据
						socket.send(packet);
					}
				} catch (IOException e) {
					e.printStackTrace();
				}
		});
		//启动线程
		t.start();
	}

}

上述代码创建一个子线程,因为socket.receive(packet)方法会阻塞主线程。服务器给客户端发送数据包,也需要知道它的IP地址和端口号,服务端根据接收的数据包获得客户端的地址和端口号。

客户端ChatClient代码如下:

package udp;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;

public class ChatClient {

	public static void main(String[] args) {
		System.out.println("客户端运行...");
		//创建一个子线程
		Thread t=new Thread(()->{
			try (
					//创建DatagramSocket对象,由系统分配可以使用的端口
					DatagramSocket socket=new DatagramSocket();
					BufferedReader keyboardIn=new BufferedReader(new InputStreamReader(System.in))
					 ){
				while(true)
				{
					/*发送数据*/
					//准备一个缓冲区
					byte[] buffer=new byte[128];
					//服务器IP地址
					InetAddress address=InetAddress.getByName("localhost");
					//服务器端口号
					int port=8080;
					//读取键盘输入的字符串
					String keyboardInpuStream=keyboardIn.readLine();
					//退出循环,结束线程
					if(keyboardInpuStream.equals("bye"))
					{
						break;
					}
					//读取键盘输入的字节数组
					byte[] b=keyboardInpuStream.getBytes();
					//创建DatagramPacket对象
					DatagramPacket packet=new DatagramPacket(b, b.length,address,port);
					//发送
					socket.send(packet);
					
					/*接收数据报*/
					packet=new DatagramPacket(buffer,buffer.length);
					socket.receive(packet);
					//接收数据长度
					int len=packet.getLength();
					String str=new String(buffer,0,len);
					//打印接收的数据
					System.out.printf("从服务器接收的数据:【%s】\n",str);
				}
			}catch (IOException e) {
				e.printStackTrace();
			}
		});
		//启动线程
		t.start();
	}
}

客户端可以通过键盘输入"bye",退出循环结束线程。

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值