网络编程及多线程-TCP实现聊天(一)

TCP案例-网络编程实现聊天(1)

1.实现单个客户端可以接收和发送多条数据

客户端可以发送给服务端多条消息,服务端将消息重新响应给客户端。

客户端代码:

public class MultiClient {
	public static void main(String[] args) throws UnknownHostException, IOException {
		System.out.println("-----Client-----");
		//1、建立连接: 使用Socket创建客户端 +服务的地址和端口
		Socket client =new Socket("localhost",8888);
		//2、客户端发送消息
		BufferedReader console =new BufferedReader(new InputStreamReader(System.in));
		DataOutputStream dos =new DataOutputStream(client.getOutputStream());		
		DataInputStream dis =new DataInputStream(client.getInputStream());
		boolean isRunning = true;
		while(isRunning) {
			String msg = console.readLine();
			dos.writeUTF(msg);
			dos.flush();
			//3、获取消息
			msg =dis.readUTF();
			System.out.println(msg);
		}
		dos.close();
		dis.close();
		client.close();
	}
}

服务端代码:

public class MultiServer {
	public static void main(String[] args) throws IOException {
		System.out.println("-----Server-----");
		// 1、指定端口 使用ServerSocket创建服务器
		ServerSocket server =new ServerSocket(8888);
		// 2、阻塞式等待连接 accept
		Socket  client =server.accept(); 
		System.out.println("一个客户端建立了连接");
		
		DataInputStream dis =new DataInputStream(client.getInputStream());
		DataOutputStream dos =new DataOutputStream(client.getOutputStream());		
		boolean isRunning = true;
		while(isRunning) {
			//3、接收消息
			String msg =dis.readUTF();
			//4、返回消息
			dos.writeUTF(msg);
			//释放资源
			dos.flush();
		}
		dos.close();
		dis.close();
		client.close();
	}
}

但是这种情况我们只能用于单个客户端的,我们想如何能够实现多客户端的收发消息。

2.实现多个客户端的收发消息

猜想:我们可以在服务端使用while(true)循环去处理多个客户端的情况,代码如下:

客户端代码:

public class MultiClient {
	public static void main(String[] args) throws UnknownHostException, IOException {
		System.out.println("-----Client-----");
		//1、建立连接: 使用Socket创建客户端 +服务的地址和端口
		Socket client =new Socket("localhost",8888);
		//2、客户端发送消息
		BufferedReader console =new BufferedReader(new InputStreamReader(System.in));
		DataOutputStream dos =new DataOutputStream(client.getOutputStream());		
		DataInputStream dis =new DataInputStream(client.getInputStream());
		boolean isRunning = true;
		while(isRunning) {
			String msg = console.readLine();
			dos.writeUTF(msg);
			dos.flush();
			//3、获取消息
			msg =dis.readUTF();
			System.out.println(msg);
		}
		dos.close();
		dis.close();
		client.close();
	}
}

服务端:

public class MultiServer {
	public static void main(String[] args) throws IOException {
		System.out.println("-----Server-----");
		// 1、指定端口 使用ServerSocket创建服务器
		ServerSocket server =new ServerSocket(8888);
		// 2、阻塞式等待连接 accept
		while(true) {
			System.out.println("建立连接之前");
			Socket  client =server.accept(); 
			System.out.println("一个客户端建立了连接");
			DataInputStream dis =new DataInputStream(client.getInputStream());
			DataOutputStream dos =new DataOutputStream(client.getOutputStream());	
			boolean isRunning = true;
			while(isRunning) {
				//3、接收消息
				String msg =dis.readUTF();
				//4、返回消息
				dos.writeUTF(msg);
				//释放资源
				dos.flush();
			}
			dos.close();
			dis.close();
			client.close();
		}
	}
}

奇怪的是,当我们启动了两次客户端以后,服务端运行结果如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2wTSrzpT-1593613816917)(E:\MarkDown\10.网络编程\images\img1.png)]

可以看到,多次运行客户端代码,却只建立了一个连接,这是什么原因呢?

当我们在服务端去掉如下代码后:

DataInputStream dis =new DataInputStream(client.getInputStream());
DataOutputStream dos =new DataOutputStream(client.getOutputStream());	
boolean isRunning = true;
........

运行结果如下:

网络编程及多线程-TCP实现聊天(一)_第1张图片

可以看到建立了两次连接,由此我们可以找到,是由于上一次的客户端的连接建立的输入输出流占用了连接通道导致,所以必须想办法使得每个客户端都需要有一个属于自己的流通道,这个时候自然想到了多线程

3.使用多线程实现多个客户端的接发功能

代码如下:

客户端代码不变:

public class MutiClient {
	public static void main(String[] args) throws IOException{
		Socket socket = new Socket("localhost", 8888);
		BufferedReader reader=new BufferedReader(new InputStreamReader(System.in));
		
		DataInputStream dis=new DataInputStream(socket.getInputStream());
		DataOutputStream dos=new DataOutputStream(socket.getOutputStream());
		boolean flag=true;
		while (flag) {
			String readLine = reader.readLine();
			dos.writeUTF(readLine);
			dos.flush();
			
			//接收服务器返回数据
			String readUTF = dis.readUTF();
			System.out.println(readUTF);
			
		}
		socket.close();
	}
}

服务端代码引入多线程:

public class TMultiChat {
	public static void main(String[] args) throws IOException {
		System.out.println("-----Server-----");
		// 1、指定端口 使用ServerSocket创建服务器
		ServerSocket server =new ServerSocket(8888);
		// 2、阻塞式等待连接 accept
		while(true) {
			Socket  client =server.accept(); 
			System.out.println("一个客户端建立了连接");
			//通过线程的引入使得每个连接都有自己的一个流通道
			new Thread(()->{
				DataInputStream dis=null;
				DataOutputStream dos=null;
				try {
					dis = new DataInputStream(client.getInputStream());
					dos =new DataOutputStream(client.getOutputStream());
				} catch (IOException e1) {
					e1.printStackTrace();
				}					
				boolean isRunning = true;
				while(isRunning) {
					//3、接收消息
					String msg;
					try {
						msg = dis.readUTF();
						//4、返回消息
						dos.writeUTF(msg);
						//释放资源
						dos.flush();
					} catch (IOException e) {
						//e.printStackTrace();
						isRunning = false; //停止线程
					}					
				}
				try {
					if(null==dos) {
						dos.close();
					}
				} catch (IOException e) {
					e.printStackTrace();
				}
				try {
					if(null==dis) {
						dis.close();
					}
				} catch (IOException e) {
					e.printStackTrace();
				}
				try {
					if(null==client) {
						client.close();
					}
				} catch (IOException e) {
					e.printStackTrace();
				}	
			}).start(); 		
			}			
		}
}

这样通过线程的引入使得每个客户端都有自己的一个流通道来实现输入输出。

但是上面的代码仍然存在一些问题:代码冗余,客户端的发送信息和接收都写在了一起,不好维护。想办法来解决:

4.使用多线程实现客户端的读入和写出,同时在服务端使用一个线程去处理读入和写出。同时对代码进行封装利于维护

客户端代码:

/*
 * 1.Send线程专门处理写入到服务器
 * 2.Receive线程处理从服务端返回的数据
 */
public class MutiClient {
	public static void main(String[] args) throws IOException {
		Socket socket=new Socket("localhost",8888);
		//创建两个线程来分别发送和接收消息
		new Thread(new Send(socket)).start();
		new Thread(new Receive(socket)).start();
	}
}

Send线程实现:

public class Send implements Runnable {
	private Socket client;
    //定义输出流
	private DataOutputStream dos;
    //获取键盘录入
	private BufferedReader reader;
    //循环标识
	private boolean running;
    
	public Send(Socket socket) {
		this.client=socket;
		this.running=true;
		try {
            //初始化流对象和键盘缓冲对象
			dos=new DataOutputStream(client.getOutputStream());
			reader=new BufferedReader(new InputStreamReader(System.in));
			
		} catch (IOException e) {
			
			e.printStackTrace();
		}
		
	}

    //线程方法
	@Override
	public void run() {
		while (running) {
			String msg=getConsoleInput();
			if (!msg.equals("")) {
				send(msg);
			}

		}

	}

	//获得键盘输入
	private String getConsoleInput() {
		try {
			String readLine = reader.readLine();
			return readLine;
		} catch (IOException e) {
			System.out.println("获取键盘输入时出错。");
			running=false;
			SxtUtils.close(dos,client);
		}
		return "";
	}
	
	//发送信息至服务端
	private void send(String msg) {
		try {
			dos.writeUTF(msg);
			dos.flush();
		} catch (IOException e) {
			System.out.println(e);
			System.out.println("===3==");
			release();
		}
	}

	//封装释放socket和流对象
	private void release() {
		this.running=false;
		SxtUtils.close(dos,client);
		
	}

}

receive线程实现:

/*
 * 接受服务端响应
 */
public class Receive implements Runnable {
	private Socket client;
	DataInputStream dis;
	boolean running;
	public Receive(Socket client) {
		this.client=client;
		this.running=true;
		try {
			dis=new DataInputStream(client.getInputStream());
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

    //线程方法
	@Override
	public void run() {
		while (running) {
            //接受响应
			String msg=receive();
			if (!msg.equals("")) {
				System.out.println(msg);
			}
		}

	}

    //接受服务端响应数据
	private String receive() {
		try {
			String msg = dis.readUTF();
			return msg;
		} catch (IOException e) {
			System.out.println("读取响应信息的时候出错");
			realase();
		}
		return "";
	}

    //异常处理
	private void realase() {
		this.running=false;
		SxtUtils.close(dis,client);	
	}
}

封装了一个静态方法用于关闭流对象:

public class SxtUtils {
	public static void close(Closeable... targets) {
		for (Closeable target : targets) {
			try {
				if(null!=target) {
					target.close();
				}
			}catch(Exception e) {
				
			}
		}	
	}
}

服务端方法:

/*
 * 服务端代码
 */
public class MutiServer {
	public static void main(String[] args) throws IOException {
		ServerSocket serverSocket = new ServerSocket(8888);
		while (true) {
			Socket server = serverSocket.accept();
			//新建一个线程来处理读取和写入客户端
			new Thread(new Mychannle(server)).start();
		}
	}
}

服务端通过一个线程处理接收和响应数据:

/*
 * 服务端通过一个线程类处理每个客户端的数据接收和响应问题
 */
public class Mychannle implements Runnable {
	private Socket server;
	private DataInputStream dis;
	private DataOutputStream dos;
	private boolean running;
	public Mychannle(Socket server) {
		this.server=server;
		this.running=true;
		
		try {
			dis=new DataInputStream(server.getInputStream());
			dos=new DataOutputStream(server.getOutputStream());
		} catch (IOException e) {
			System.out.println("服务端初始化流失败");
			realese();
		}
	}

	@Override
	public void run() {
		while (running) {
			String msg=getInputMsg();
			if (!msg.equals("")) {
				send(msg);
			}
		}

	}

	private void send(String msg) {
		try {
			dos.writeUTF(msg);
			dos.flush();
		} catch (IOException e) {
			System.out.println("服务端写回客户端失败");
			realese();
			
		}
		
	}
	private void realese() {
		this.running=false;
		SxtUtils.close(dis,dos,server);
		
	}

	private String getInputMsg() {
		try {
			String msg = dis.readUTF();
			return msg;
		} catch (IOException e) {
			System.out.println("服务端获取传入数据失败");
			realese();
		}
		return "";
	}

}

至此我们完成了多个客户端可以同时启动,并且每个客户端可以接收自己发送给服务端的数据,通过线程的引入解决了客户端需要排队的现象。

接下来我们应该考虑如何实现群聊的功能

下一篇实现群聊及私聊的实现

你可能感兴趣的:(网络编程及多线程)