菜鸟学习笔记:Java提升篇10(网络2——UDP编程、TCPSocket通信、聊天室案例)

23 篇文章 0 订阅
23 篇文章 1 订阅

菜鸟学习笔记:Java提升篇10(网络2——UDP编程、TCPSocket通信)

UDP编程

在上一篇中讲解了UDP协议是非面向连接的不安全但效率较高的通信协议。在了解完概念之后我们用Java来实现UDP编程。
Java中通过DatagramSocket和DatagramPacket来实现UDP通信,通信过程一般分为以下几步:
发送端(客户端):

  1. 创建客户端DatagramSocket类+端口
  2. 准备数据
  3. 打包DatagramPacket+地址及端口
  4. 发送
  5. 释放资源
    接收端(服务端):
  6. 创建服务端DatagramSocket类+端口
  7. 准备接收容器
  8. 接收数据包
  9. 分析
  10. 释放资源
    依据这个过程我们直接通过代码来理解UDP发送过程:

首先构建服务端:

public class MyServer {
	public static void main(String[] args) throws IOException {
		//1、创建服务端 +端口
		DatagramSocket server = new DatagramSocket(8888);
		//2、准备接受容器
		byte[] container = new byte[1024];
		//3、封装成 包 DatagramPacket(byte[] buf, int length) 		
		DatagramPacket packet =new DatagramPacket(container, container.length) ;
		//4、接受数据
		server.receive(packet);
		//5、分析数据
		byte[] data =packet.getData();
		int len =packet.getLength();
		System.out.println(new String(data,0,len));
		//6、释放
		server.close();
		
	}

}

这时服务端运行就会进入阻塞状态等待数据:
服务端

然后构建客户端

public class MyClient {

	public static void main(String[] args) throws IOException {
		//1、创建客户端 +端口
		DatagramSocket client = new DatagramSocket(6666);
		//2、准备数据
		String msg ="udp编程";
		byte[] data =msg.getBytes();
		//3、打包(发送的地点 及端口) DatagramPacket(byte[] buf, int length, InetAddress address, int port)
		DatagramPacket packet = new DatagramPacket(data,data.length,new InetSocketAddress("localhost",8888));
		//4、发送
		client.send(packet);
		//5、释放
		client.close();
	}
}

运行客户端发送数据后服务端就会得到响应:
接收数据
UDP的特点就是不管有没有服务器等待数据,客户端都可以发送数据,上例中不启动服务客户端运行依然不会报错,但数据会丢失。

TCP编程(Socket通信)

单个客户端的连接

TCP相对UDP而言,需要建立连接、安全可靠但效率较低。它在通信时两端必须连接才能进行通信。
何为Socket,比如用浏览器访问一个网站,点击访问后,浏览器会申请与服务器进行连接,连接后才能进行数据交互操作,在关闭网页后连接会自动断开。我们把客户端与服务端之间建立的连接就称之为Socket。
Java中Socket通信主要使用ServerSocket类来建立连接,通过Socket类来进行数据交互,我们直接通过代码来讲解,注意,交互过程采用的时数据处理流,如果大家忘了可以回去看看,链接
服务端:

public class Server {

	public static void main(String[] args) throws IOException {
		//1、创建服务器  指定端口   ServerSocket(int port) 
		ServerSocket server = new ServerSocket(8888);
		//2、接收客户端连接   阻塞式
		Socket socket =server.accept();
		System.out.println("一个客户端建立连接");
		//3、发送数据
		String msg ="欢迎使用";
		//输出流
		/*
		BufferedWriter bw = new BufferedWriter(
				new OutputStreamWriter(
				socket.getOutputStream()));
		
		bw.write(msg);
		bw.newLine();
		bw.flush();
		*/
		//socket.getOutputStream()方法可以和之前的FileOutputStream()
		//对应起来它表示socket流,下面的write操作会在流中写入数据
		DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
		dos.writeUTF(msg);
		dos.flush();
	}
}

与UDP协议不同,在连接之前必须先启动服务器,在建立连接后服务端也可以向客户端发送数据。
客户端:

public class Client {
	public static void main(String[] args) throws UnknownHostException, IOException {
		//1、创建客户端   必须指定服务器+端口    此时就在连接
		Socket client = new Socket("localhost",8888);
		//2、接收数据
		/*
		BufferedReader br = new BufferedReader(new InputStreamReader(client.getInputStream()));
		String echo =br.readLine(); //阻塞式方法
		System.out.println(echo);
		*/
		//这里采用之前讲的数据处理流进行交互
		DataInputStream dis = new DataInputStream(client.getInputStream());
		String echo = dis.readUTF();
		System.out.println(echo);
	}
}

现在如果直接运行客户端,那么会报如下错误:
TCP
需要启动服务器才可以进行数据交互:
客户端接收到的数据
服务端

多个客户端的连接(聊天室案例)

与多用户交互最简单的方式就是将交互代码写道死循环中,所以服务器端可以改成如下代码:

	public static void main(String[] args) throws IOException {
		//1、创建服务器  指定端口   ServerSocket(int port) 
		ServerSocket server = new ServerSocket(8888);
		//2、接收客户端连接   阻塞式
		while(true){ //死循环  一个accept()一个客户端
			Socket socket =server.accept();
			System.out.println("一个客户端建立连接");
			//3、发送数据
			String msg ="欢迎使用";
			//输出流
			
			DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
			dos.writeUTF(msg);
			dos.flush();
		}

但是这又带来了一个问题,客户端只能一个一个的连接,如果循环体中出现阻塞(比如死循环、等待用户输入等操作),那么后面的客户端就无法建立连接,这里就需要我们之前所学的多线程的知识来解决这个问题。为全面说明这个问题我们采用一个聊天室的案例来讲解。

聊天室案例
首先说明我们要实现的聊天室的功能,多个用户可以通过控制台进行交互,自己输入的内容在其他用户的控制台上会显示。
为实现这个功能,我们需要一个Server端,让其他Client端都与其建立连接,当用户发送消息时,服务器端会把消息转发给其他Client端。依照这个思路我们来完成聊天室。
为便于我们关闭线程,首先创建一个用于关闭线程的工具:

public class CloseUtil {
//Closeable... 表示可以接受不定数量的Closeable类型的参数,它必须数函数接受参数的最后一个
	public static void closeAll(Closeable... io){
		for(Closeable temp:io){
			try {
				if (null != temp) {
					temp.close();
				}
			} catch (Exception e) {
				// TODO: handle exception
			}
		}
	}
}

针对客户端,我们思考聊天室需要实时的接受和发送数据,这就需要多线程来实现,为此我们创建Send和Receive两个线程类:
Send类

public class Send implements Runnable{
	//控制台输入流
	private BufferedReader console;
	//管道输出流
	private DataOutputStream dos;
	//控制线程
	private boolean isRunning =true;
	//无参构造,初始化打印流
	public Send() {
		console =new BufferedReader(new InputStreamReader(System.in));
	}
	//有参构造传入与服务器建立的Socket连接
	public Send(Socket client){
		this();
		try {
		//初始化输出流
			dos =new DataOutputStream(client.getOutputStream());
		} catch (IOException e) {
			//e.printStackTrace();
			isRunning =false;
			CloseUtil.closeAll(dos,console);
			
		}
	}
	//1、从控制台接收数据
	private String getMsgFromConsole(){
		try {
		//接受控制台输入
			return console.readLine();
		} catch (IOException e) {
			//e.printStackTrace();
		}
		return "";
	}
	/**
	 * 1、从控制台接收数据
	 * 2、发送数据
	 */
	public void send(){
	//接收控制台输入
		String msg = getMsgFromConsole();
		try {
			if(null!=msg&& !msg.equals("")){
			//发送输入给服务器
				dos.writeUTF(msg);
				dos.flush(); //强制刷新
			}
		} catch (IOException e) {
			//e.printStackTrace();
			isRunning =false;
			CloseUtil.closeAll(dos,console);
		}
	}
	@Override
	public void run() {
		//线程体
		while(isRunning){
			send();
		}
	}

}

Receive类:

public class Receive implements Runnable {
	//输入流
	private  DataInputStream dis ;
	//线程标识
	private boolean isRunning = true;
	public Receive() {
	}
	public Receive(Socket client){
		try {
		//初始化输入流
			dis = new DataInputStream(client.getInputStream());
		} catch (IOException e) {
			e.printStackTrace();
			isRunning =false;
			CloseUtil.closeAll(dis);
		}
	}
	 //接收数据
	public String  receive(){
		String msg ="";
		try {
			msg=dis.readUTF();
		} catch (IOException e) {
			e.printStackTrace();
			isRunning =false;
			CloseUtil.closeAll(dis);
		}
		return msg;
	}
	@Override
	public void run() {
		//线程体
		while(isRunning){
			System.out.println(receive());
		}
	}
}

在Client中我们启动这两个线程:

public class Client {

	public static void main(String[] args) throws UnknownHostException, IOException {
		Socket client = new Socket("localhost",9999);
		new Thread(new Send(client)).start(); //一条路径
		new Thread(new Receive(client)).start(); //一条路径
		
	}

}

由于存在多个用户,所以建立连接过程(server.accept())和用户发送消息过程必须并行处理,所以还需要一个Channel线程来为各个Client端发送数据,为了方便访问Server类中的private属性,我们采用内部类的方式对Channel进行管理,并且还需要建立一个容器来存放已有用户。综上Server端的代码如下:

public class Server {
	private List<MyChannel> all = new ArrayList<MyChannel>();
	/**
	 * @param args
	 * @throws IOException 
	 */
	public static void main(String[] args) throws IOException {
		new Server().start();	
		
	}
	
	public void start() throws IOException{
		ServerSocket server =new ServerSocket(9999);
		while(true){
			Socket client =server.accept();		
			MyChannel channel = new MyChannel(client);
			all.add(channel);//统一管理
			new Thread(channel).start(); //一条道路
		}
	}
	
	
	/**
	 * 一个客户端 一条道路
	 * 1、输入流
	 * 2、输出流
	 * 3、接收数据
	 * 4、发送数据
	 * @author Administrator
	 *
	 */
	private class MyChannel implements Runnable{
		private DataInputStream dis ;
		private DataOutputStream dos ;
		private boolean isRunning =true;
		public MyChannel(Socket client ) {
			try {
				dis = new DataInputStream(client.getInputStream());
				dos = new DataOutputStream(client.getOutputStream());
			} catch (IOException e) {
				//e.printStackTrace();
				CloseUtil.closeAll(dis,dos);
				isRunning =false;
			}
		}
		/**
		 * 读取数据
		 * @return
		 */
		private String receive(){
			String msg ="";
			try {
				msg=dis.readUTF();
			} catch (IOException e) {
				//e.printStackTrace();
				CloseUtil.closeAll(dis);
				isRunning =false;
				all.remove(this); //移除自身
			}
			return msg;
		}
		
		/**
		 * 发送数据
		 */
		private void send(String msg){
			if(null==msg ||msg.equals("")){
				return ;
			}
			try {
				dos.writeUTF(msg);
				dos.flush();
			} catch (IOException e) {
				//e.printStackTrace();
				CloseUtil.closeAll(dos);
				isRunning =false;
				all.remove(this); //移除自身
			}
		}
		
		/**
		 * 发送给其他客户端
		 */
		private void sendOthers(){
			String msg = this.receive();
			//遍历容器
			for(MyChannel other:all){
				if(other ==this){
					continue;
				}
				//发送其他客户端
				other.send(msg);
			}
		}
		@Override
		public void run() {
			while(isRunning){
				sendOthers();
			}
		}
	}
	
}

这样就完成了聊天室的功能,我们创建两个客户Tom和Bob(可以有多个),代码测试结果如下:
客户端1
客户端2
以上就是Java网络的所有内容,这也是第一次展示综合案例,可能代码一多大家对逻辑理解起来会困难一些,建议大家多看几遍,结合聊天室也对之前的IO流、线程、容器好好的做做复习。相信大家要是能搞明白这段代码Java基础的内容差不多就没什么问题了。
上一篇:菜鸟学习笔记:Java提升篇9(网络1——网络基础、Java网络编程)
下一篇:菜鸟学习笔记:Java提升篇11(Java动态性1——注解与反射)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值