文章目录

  • 最终版本



第9个版本:这个版本的主要作用解决只能发一次消息的问题;上一个版本中我们将客户端输入的内容发送到服务器,但是运行后发现只能发送一次消息,后续消息就发不出去了,这次我们就是解决这个问题。

解决这个问题的思路是1.在服务端我们得一直不停的接受数据,所以得写个循环 2.是在客户端屏蔽掉写完数据就关闭输出流的语句,但这样会导致新的问题,所以我们还在客户端增加了两步:1,是增加了关闭窗口前的清理工作 2. 连接开始后的初始化工作。

//server.java
package Chat9;

import java.io.*;
import java.io.IOException;
import java.net.*; 

public class Server {
	
	
	
	public static void main(String[] args) {
		boolean started = false;
		try {
			ServerSocket sc = new ServerSocket(8088);
			started = true;//一旦连接就位true
			//接受客户端连接,不一定是一个连接,就用死循环
			while(started) {
				boolean bConnect = false;
				Socket s = sc.accept();
System.out.println("a client connected!");
				bConnect = true;//accept后变为true
				DataInputStream dis = new DataInputStream(s.getInputStream());
				//收也不能收只收一次,建个循环
				while(bConnect) {
					String str = dis.readUTF();//readUTF是转化成utf-8编码格式的
					System.out.println(str);
				}
				dis.close();
			}
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

}

//client.java	
package Chat9;

import java.io.*;
import java.net.*;
import java.awt.*;//Frame
import java.awt.event.*;//WindowAdapter,WindowEvent
import java.io.IOException;

//解决只能发一次消息的问题
public class Client extends Frame {// 继承框架

	Socket s = null;
	DataOutputStream dos = null;//每次发送都要调用,不如做成全局变量
	
	TextField tfText = new TextField();// 全局变量,方便后面使用
	TextArea taContext = new TextArea();

	public static void main(String[] args) { 
		new Client().launchFrame();
	}
	
	//窗口
	public void launchFrame() {
		setLocation(800, 300);// 位置
		this.setSize(400, 400);// 大小
		add(tfText, BorderLayout.SOUTH);// 显示框加到南边
		add(taContext, BorderLayout.NORTH);// 输入框加到北边
		pack();// 处理中间大部分空白
		this.addWindowListener(new WindowAdapter() {//WindowAdapter是个抽象类,里面对接口WindowListener的方法都进行了空实现。			这样编程时不需要自己再去实现全部接口WindowListener里的方法。只需覆写自己需要的方法,其他的方法			WindowAdapter都实现了
			
			//重写windowsClosing窗口关闭
			public void windowClosing(WindowEvent arg0) {
				disconnect();
				System.exit(0);//正常退出就行
			}
			
		});
		tfText.addActionListener(new TFListener());
		setVisible(true);
		connect();//发完信息后直接连接
	}
	
	//Connet
	public void connect() {
			try {
				s = new Socket("localhost",8088);//127.0.0.1
				dos = new DataOutputStream(s.getOutputStream());//连接上以后就进行初始化
System.out.println("connected!");
			} catch (UnknownHostException e) {//找不到主机
				e.printStackTrace();
			} catch (IOException e) {//网卡上有错
				e.printStackTrace();
			}
		}

	//得写一个清理资源的方法,这样在关掉的时候好清理,在窗口关闭时调用
	public void disconnect() {
		try {
			dos.close();//先清理资源
			s.close();//再关闭连接
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	//把tfText里面的内容通过Enter进入到taContext
	private class TFListener implements ActionListener{
		//处理enter以后的动作,我们现在想enter后把输入的东西发过去,得在这里进行处理。
		public void actionPerformed(ActionEvent e) {
			String str = tfText.getText().trim();//trim去掉空格
			//把输入的展示到展示框中。
			taContext.setText(str);
			tfText.setText("");//处理enter后的tfText,使其为空。
			//把输入的内容发送到Server中
			SendMessage(str);
		}
	}
	//把输入的内容发送到Server中
	public void SendMessage(String str) {
		try {
		//	DataOutputStream dos = new DataOutputStream(s.getOutputStream());//DataOutputStream:数据输出流允许应用程序以适当方式将基本 Java 数据类型写入输出流中。然后应用程序可以使用数据输入流将数据
			dos.writeUTF(str);//发送消息
			dos.flush();//你向输出流写入东西之后,执行flush(),目的是把缓冲区里的东西强行写入输出流.因为有些带缓冲区的输出流要缓冲区满的时候才输出.
			//dos.close();//关掉了以后就只能发送一次内容了,再发就close掉了
		} catch (IOException e1) {
			e1.printStackTrace();
		}
	}
	
}

第10个版本:这个版本的主要作用是 解决关闭窗口后,Server报错的情况,所以改变的只有服务器。

package Chat10;

import java.io.*;
import java.io.IOException;
import java.net.*; 

public class Server {
		
	public static void main(String[] args) {
		boolean started = false;
		ServerSocket sc = null;
		Socket s = null;
		DataInputStream dis = null;
		
		try {
			sc = new ServerSocket(8088);
		}catch(BindException e) {
			System.out.println("端口使用中");
			System.out.println("请关掉相关程序,重新启动");
			System.exit(0);//为的是启动两次服务器带来的错误
		}catch(IOException e){
			e.printStackTrace();
			//System.out.println("连接失败!");
		}
		
		try {
			started = true;//一旦连接就位true
			//接受客户端连接,不一定是一个连接,就用死循环
			while(started) {
				boolean bConnect = false;
				s = sc.accept();
System.out.println("a client connected!");
				bConnect = true;//accept后变为true
				dis = new DataInputStream(s.getInputStream());
				//收也不能收只收一次,建个循环
				while(bConnect) {
					String str = dis.readUTF();//readUTF是转化成utf-8编码格式的,是个阻塞的方法,会一直等待,当client窗口关闭的时候,会报错
					System.out.println(str);
				}
			  //dis.close();
			}
		}catch(EOFException e) {
			System.out.println("Client Closed");
		}catch (IOException e) {
			e.printStackTrace();
		}finally {//意思是万一中间产生什么错误,在这里处理
			try {
				if(dis != null) dis.close();//出错了就关闭掉
				if(s != null) s.close();//再关闭掉Socket
			} catch (IOException e1) {
				e1.printStackTrace();
			}
		}
	}

}

第11个版本:这个版本的主要作用是 实现多个Client聊天,实现思路是在Server里面创建多线程,主线程只负责连接,其他线程负责处理之后的问题,所以改变的也只有服务端

package Chat11;

import java.io.*;
import java.io.IOException;
import java.net.*; 

public class Server {
	boolean started = false;
	ServerSocket sc = null;
	
	public static void main(String[] args) {
		new Server().start();		
	}

	//接受客户端连接后起一个线程,进行处理
	public void start() {
		try {
			sc = new ServerSocket(8088);
			started = true;
		}catch(BindException e) {
			System.out.println("端口使用中");
			System.out.println("请关掉相关程序,重新启动");
			System.exit(0);//为的是启动两次服务器带来的错误
		}catch(IOException e){
			e.printStackTrace();
		}
		
		try {
			while(started) {
				Socket s = sc.accept();
				Client c = new Client(s);
System.out.println("a client connected!");
				new Thread(c).start();//新建Thread并启动
			}
		}catch (IOException e) {
			e.printStackTrace();
		}finally {
			try {
				sc.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
			
		}
		
	}
	//线程类(内部类还是外部类)
	class Client implements Runnable{//客户端那边在这边的一个包装
		private Socket s;
		private DataInputStream dis = null; 
		private boolean bConnected = false;
		
		public Client(Socket s) {
			this.s = s;
			try {
				dis = new DataInputStream(s.getInputStream());
				bConnected = true;
			} catch (IOException e) {
				e.printStackTrace();
			}
			
		}
		
		//线程里的东西
		public void run() {
			try {
				//收也不能收只收一次,建个循环
				while(bConnected) {
					String str = dis.readUTF();//readUTF是转化成utf-8编码格式的,是个阻塞的方法,会一直等待,当client窗口关闭的时候,会报错
					System.out.println(str);
				}
			}catch(EOFException e) {
					System.out.println("Client Closed");
			}catch (IOException e) {
					e.printStackTrace();
			}finally {
				try {
					if(dis != null) dis.close();//出错了就关闭掉
					if(s != null) s.close();//再关闭掉Socket
				} catch (IOException e1) {
					e1.printStackTrace();
				}
				
			}
		}
	}
}

第12个版本:这个版本的主要作用是 实现不同客户端之间的通讯,此时我们的服务器相当于消息中转站

//client.java
package Chat12;

import java.io.*;
import java.net.*;
import java.awt.*;//Frame
import java.awt.event.*;//WindowAdapter,WindowEvent
import java.io.IOException;

//Client接受其他客户端发送的内容
public class Client extends Frame {// 继承框架 

	Socket s = null;
	DataOutputStream dos = null;//每次发送都要调用,不如做成全局变量
	DataInputStream dis = null;
	private boolean bConnected = false;
	
	TextField tfText = new TextField();// 全局变量,方便后面使用
	TextArea taContext = new TextArea();
	
	Thread tRecv = new Thread(new RecvThread()); 

	public static void main(String[] args) { 
		new Client().launchFrame();
	}
	
	//窗口
	public void launchFrame() {
		setLocation(800, 300);// 位置
		this.setSize(400, 400);// 大小
		add(tfText, BorderLayout.SOUTH);// 显示框加到南边
		add(taContext, BorderLayout.NORTH);// 输入框加到北边
		pack();// 处理中间大部分空白
		
		//关闭窗口的操作
		this.addWindowListener(new WindowAdapter() {
			//重写windowsClosing窗口关闭
			public void windowClosing(WindowEvent arg0) {
				disconnect();
				System.exit(0);//正常退出就行
			}
			
		});
		
		tfText.addActionListener(new TFListener());
		setVisible(true);
		connect();//发完信息后直接连接
		
		//new Thread(new RecvThread()).start();
		tRecv.start();//将Thread提升到全局变量等级,让其他地方能访问到他
		
	}
	
	//Connect
	public void connect() {
			try {
				s = new Socket("localhost",8088);//127.0.0.1
				dos = new DataOutputStream(s.getOutputStream());//连接上以后就进行初始化
				dis = new DataInputStream(s.getInputStream());
System.out.println("connected!");
				bConnected = true;
			} catch (UnknownHostException e) {//找不到主机
				e.printStackTrace();
			} catch (IOException e) {//网卡上有错
				e.printStackTrace();
			}
		}

	//得写一个清理资源的方法,这样在关掉的时候好清理,在窗口关闭时调用
	public void disconnect() {
		
		try {
			dos.close();//先清理资源
			dis.close();
			s.close();//再关闭连接
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		//这里又有个问题,方法阻塞的话,窗口又管不了,嗯,报错就打印就打印,不管了
		/*try {
			bConnected = false;//线程结束
			
			tRecv.join();//合并到一个线程
		}catch (InterruptedException e) {//catch join的错误
			e.printStackTrace();
		}finally {
			try {
				dos.close();//先清理资源
				dis.close();
				s.close();//再关闭连接
			} catch (IOException e) {
				e.printStackTrace();
			}
		}*/
	}
	//把tfText里面的内容通过Enter进入到taContext
	private class TFListener implements ActionListener{
		//处理enter以后的动作,我们现在想enter后把输入的东西发过去,得在这里进行处理。
		public void actionPerformed(ActionEvent e) {
			String str = tfText.getText().trim();//trim去掉空格
			
			//taContext.setText(str);//把输入的展示到展示框中。为了窗口好看,删了这句话
			
			tfText.setText("");//处理enter后的tfText,使其为空。
			//把输入的内容发送到Server中
			SendMessage(str);
		}
	}
	
	//新建一个内部类,里面新启一个线程,负责接收其他客户端发过来的消息
	private class RecvThread implements Runnable{

		public void run() {
			try {
				while(bConnected) {
					String str = dis.readUTF();//这里因为是阻塞的,问题有点大,嗯。怎么改呢。。。。。。。气死我
//System.out.println(str);
					//taContext.setText(str);//这样写会冲掉之前写的
					taContext.setText(taContext.getText() + str + '\n');
					
				}
			}catch(SocketException e) {
				System.out.println("退出了,拜");				
			}catch(IOException e) {
				e.printStackTrace();
			}
			
		}
		
	}
	//把输入的内容发送到Server中
	public void SendMessage(String str) {
		try {
		//	DataOutputStream dos = new DataOutputStream(s.getOutputStream());//DataOutputStream:数据输出流允许应用程序以适当方式将基本 Java 数据类型写入输出流中。然后应用程序可以使用数据输入流将数据
			dos.writeUTF(str);//发送消息
			dos.flush();//你向输出流写入东西之后,执行flush(),目的是把缓冲区里的东西强行写入输出流.因为有些带缓冲区的输出流要缓冲区满的时候才输出.
			//dos.close();//关掉了以后就只能发送一次内容了,再发就close掉了
		} catch (IOException e1) {
			e1.printStackTrace();
		}
	}
	
}


//server.java
package Chat12;

import java.io.*;
import java.io.IOException;
import java.net.*; 
import java.util.*;

public class Server {
	boolean started = false;
	ServerSocket sc = null;
	List<Client> clients = new ArrayList<Client>();//新建一个集合装不同的客户端Socket,但是这里得装整个类Client
	
	public static void main(String[] args) {
		new Server().start();		
	}

	//接受客户端连接后起一个线程,进行处理
	public void start() {
		try {
			sc = new ServerSocket(8088);
			started = true;
		}catch(BindException e) {
			System.out.println("端口使用中");
			System.out.println("请关掉相关程序,重新启动");
			System.exit(0);//为的是启动两次服务器带来的错误
		}catch(IOException e){
			e.printStackTrace();
		}
		
		try {
			while(started) {
				Socket s = sc.accept();
				Client c = new Client(s);
System.out.println("a client connected!");
				new Thread(c).start();//新建Thread并启动
				clients.add(c);//新建一个Client,得加到clients集合中
			}
		}catch (IOException e) {
			e.printStackTrace();
		}finally {
			try {
				sc.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
			
		}
		
	}
	//线程类(内部类还是外部类)
	class Client implements Runnable{//客户端那边在这边的一个包装
		private Socket s;//每一个客户端单独的连接
		private DataInputStream dis = null; //从Socket里面单独的管道连接
		private DataOutputStream dos = null;
		private boolean bConnected = false;
		
		public Client(Socket s) {
			this.s = s;
			try {
				dis = new DataInputStream(s.getInputStream());
				dos = new DataOutputStream(s.getOutputStream());
				bConnected = true;
			} catch (IOException e) {
				e.printStackTrace();
			}
			
		}
		
		//加一个方法,主要负责发东西
		public void send(String str) {
			try {
				dos.writeUTF(str);//当一个窗口关闭的时候,另一个窗口再输入消息的话就会报错,原因就在这里
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		
		//线程里的东西
		public void run() {
			try {
				//收也不能收只收一次,建个循环
				while(bConnected) {
					String str = dis.readUTF();//拿到字符串并打印出来
System.out.println(str);
					for(int i = 0; i<clients.size(); i++) {
						Client c = clients.get(i);
						c.send(str);
					}
				}
			}catch(EOFException e) {
					System.out.println("Client Closed");
			}catch (IOException e) {
					e.printStackTrace();
			}finally {
				try {
					if(dis != null) dis.close();//出错了就关闭掉
					if(dos != null) dos.close();
					if(s != null) 
						s.close();//再关闭掉Socket
					//	s = null;//方便垃圾回收器回收
				} catch (IOException e1) {
					e1.printStackTrace();
				}
				
			}
		}
	}



}

最终版本

这个版本的主要作用是 上一个版本中当关掉一个窗口的时候,输入会报错。要解决这个问题。

client.java

//client.java
package Chat14;

import java.io.*;
import java.net.*;
import java.awt.*;//Frame
import java.awt.event.*;//WindowAdapter,WindowEvent
import java.io.IOException;

//13版本的错误在于当关掉一个窗口的时候,在输入会报错。要解决这个
public class Client extends Frame {// 继承框架 

	Socket s = null;
	DataOutputStream dos = null;//每次发送都要调用,不如做成全局变量
	DataInputStream dis = null;
	private boolean bConnected = false;
	
	TextField tfText = new TextField();// 全局变量,方便后面使用
	TextArea taContext = new TextArea();
	
	Thread tRecv = new Thread(new RecvThread()); 

	public static void main(String[] args) { 
		new Client().launchFrame();
	}
	
	//窗口
	public void launchFrame() {
		setLocation(800, 300);// 位置
		this.setSize(400, 400);// 大小
		add(tfText, BorderLayout.SOUTH);// 显示框加到南边
		add(taContext, BorderLayout.NORTH);// 输入框加到北边
		pack();// 处理中间大部分空白
		
		//关闭窗口的操作
		this.addWindowListener(new WindowAdapter() {
			//重写windowsClosing窗口关闭
			public void windowClosing(WindowEvent arg0) {
				disconnect();
				System.exit(0);//正常退出就行
			}
			
		});
		
		tfText.addActionListener(new TFListener());
		setVisible(true);
		connect();//发完信息后直接连接
		
		//new Thread(new RecvThread()).start();
		tRecv.start();//将Thread提升到全局变量等级,让其他地方能访问到他
		
	}
	
	//Connect
	public void connect() {
			try {
				s = new Socket("localhost",8088);//127.0.0.1
				dos = new DataOutputStream(s.getOutputStream());//连接上以后就进行初始化
				dis = new DataInputStream(s.getInputStream());
System.out.println("connected!");
				bConnected = true;
			} catch (UnknownHostException e) {//找不到主机
				e.printStackTrace();
			} catch (IOException e) {//网卡上有错
				e.printStackTrace();
			}
		}

	//得写一个清理资源的方法,这样在关掉的时候好清理,在窗口关闭时调用
	public void disconnect() {
		
		try {
			dos.close();//先清理资源
			dis.close();
			s.close();//再关闭连接
		} catch (IOException e) {
			e.printStackTrace();
		}
				
	}
	//把tfText里面的内容通过Enter进入到taContext
	private class TFListener implements ActionListener{
		//处理enter以后的动作,我们现在想enter后把输入的东西发过去,得在这里进行处理。
		public void actionPerformed(ActionEvent e) {
			String str = tfText.getText().trim();//trim去掉空格
					
			tfText.setText("");//处理enter后的tfText,使其为空。
			//把输入的内容发送到Server中
			SendMessage(str);
		}
	}
	
	//新建一个内部类,里面新启一个线程,负责接收其他客户端发过来的消息
	private class RecvThread implements Runnable{

		public void run() {
			try {
				while(bConnected) {
					String str = dis.readUTF();//这里因为是阻塞的,问题有点大,嗯。怎么改呢,怎么改,怎么改,怎么改

					taContext.setText(taContext.getText() + str + '\n');
				}
			}catch(SocketException e) {
				System.out.println("退出了,拜");				
			}catch(EOFException e) {
				System.out.println("退出了,bye");
			}catch(IOException e) {
				e.printStackTrace();
			}
			
		}
		
	}
	//把输入的内容发送到Server中
	public void SendMessage(String str) {
		try {
		//	DataOutputStream dos = new DataOutputStream(s.getOutputStream());//DataOutputStream:数据输出流允许应用程序以适当方式将基本 Java 数据类型写入输出流中。然后应用程序可以使用数据输入流将数据
			dos.writeUTF(str);//发送消息
			dos.flush();//你向输出流写入东西之后,执行flush(),目的是把缓冲区里的东西强行写入输出流.因为有些带缓冲区的输出流要缓冲区满的时候才输出.
			//dos.close();//关掉了以后就只能发送一次内容了,再发就close掉了
		} catch (IOException e1) {
			e1.printStackTrace();
		}
	}
	
}

server.java

//server.java
package Chat14;

import java.io.*;
import java.io.IOException;
import java.net.*; 
import java.util.*;

public class Server {
	boolean started = false;
	ServerSocket sc = null;
	List<Client> clients = new ArrayList<Client>();//新建一个集合装不同的客户端Socket,但是这里得装整个类Client
	
	public static void main(String[] args) {
		new Server().start();		
	}

	//接受客户端连接后起一个线程,进行处理
	public void start() {
		try {
			sc = new ServerSocket(8088);
			started = true;
		}catch(BindException e) {
			System.out.println("端口使用中");
			System.out.println("请关掉相关程序,重新启动");
			System.exit(0);//为的是启动两次服务器带来的错误
		}catch(IOException e){
			e.printStackTrace();
		}
		
		try {
			while(started) {
				Socket s = sc.accept();
				Client c = new Client(s);
System.out.println("a client connected!");
				new Thread(c).start();//新建Thread并启动
				clients.add(c);//新建一个Client,得加到clients集合中
			}
		}catch (IOException e) {
			e.printStackTrace();
		}finally {
			try {
				sc.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
			
		}
		
	}
	//线程类(内部类还是外部类)
	class Client implements Runnable{//客户端那边在这边的一个包装
		private Socket s;//每一个客户端单独的连接
		private DataInputStream dis = null; //从Socket里面单独的管道连接
		private DataOutputStream dos = null;
		private boolean bConnected = false;
		
		public Client(Socket s) {
			this.s = s;
			try {
				dis = new DataInputStream(s.getInputStream());
				dos = new DataOutputStream(s.getOutputStream());
				bConnected = true;
			} catch (IOException e) {
				e.printStackTrace();
			}
			
		}
		
		//加一个方法,主要负责发东西
		public void send(String str) {
			try {
				dos.writeUTF(str);//当一个窗口关闭的时候,另一个窗口再输入消息的话就会报错,原因就在这里
			} catch (IOException e) {
				clients.remove(this);
				System.out.println("对方退出了,无法再进行聊天");
				//e.printStackTrace();
			}
		}
		
		//线程里的东西
		public void run() {
		//	Client c = null;
			try {
				//收也不能收只收一次,建个循环
				while(bConnected) {
					String str = dis.readUTF();//拿到字符串并打印出来
System.out.println(str);
					for(int i = 0; i<clients.size(); i++) {
						Client c = clients.get(i);
						c.send(str);
					}
				}
			}catch(EOFException e) {
					System.out.println("Client Closed");
			}catch (IOException e) {
					e.printStackTrace();
			}finally {
				try {
					if(dis != null) dis.close();//出错了就关闭掉
					if(dos != null) dos.close();
					if(s != null) s.close();//再关闭掉Socket
					
				} catch (IOException e1) {
					e1.printStackTrace();
				}
											
			}
		}
	}
}