天天看点

网络编程----TCP实现聊天室项目前言

文章目录

  • 前言
    • 客户端
    • 服务端
    • 服务端的源代码:
    • 实现效果

前言

在学TCP的时候就做了一个多用户登录并得到响应的一个小项目,这个聊天室也是基于TCP通信原理的交互,在服务端对接收的Socket对象封装成线程类,开启多线程实现多用户同时接发数据。

不同的点是:

客户端

对接收和发送的代码进行了封装,封装成独立出来的两个线程类,就可以实现读写分离,不再是请求响应式的交流。

public class Client {
 public static void main(String[] args) throws UnknownHostException, IOException {
		System.out.println("------client------");
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		System.out.println("请输入用户名");
		String name = br.readLine();
		//建立连接:socket创建客户端
		Socket client = new Socket("localhost",8888);
		
        //客户端发送信息
		new Thread(new ClientSend(client,name)).start();  //导入发送线程类
		//获取消息
		new Thread(new ClientReceive(client)).start(); //导入接收线程类
}
}
           

上面导入name变量之后在创建线程体时会先把name导入,这样在服务端就可以接收到这个线程对象的name属性,方便后面群发消息可以调用说话的客户端的名字。

下面就是客户端的接收和发送的两个线程类。

接收:

public class ClientReceive implements Runnable {
	DataInputStream dis ;
	Socket client;
	boolean isRunning = true;
	public ClientReceive(Socket client){
		this.client = client;
		try {
			dis = new DataInputStream(client.getInputStream());
		} catch (IOException e) {
			release();
		}
	
	}
	public void run() {
		while(isRunning) {
		//接收消息
        String datas;
		try {
			datas = dis.readUTF();
			System.out.println(datas);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		}
       
	}
	
	//释放资源
		private void release() {
			this.isRunning = false;
			try {
				dis.close();
				client.close();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}


}

           

发送类:

public class ClientSend implements Runnable {
	BufferedReader console;
	DataOutputStream dos;
	Socket client;
	boolean isRunning = true;
	String name;
	public ClientSend(Socket client,String name) {
		this.name = name;
		this.client = client;
		console = new BufferedReader(new InputStreamReader(System.in));
		try {
			dos = new DataOutputStream(client.getOutputStream());
			//发送名称
			dos.writeUTF(name);
		    dos.flush();
		} catch (IOException e) {
			release();
		}
	}
	
public void run() {         //线程体
	while(isRunning) {
		//客户端发送信息
				String msg;
				try {
					msg = console.readLine();
					dos.writeUTF(msg);
				    dos.flush();
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}	
	}
}

	//释放资源
	private void release() {
		this.isRunning = false;
		try {
			dos.close();
			client.close();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}
           

服务端

1.服务器是请求响应式的,所以不需要用到上面创建的接收发送线程类。

2.对Socket进行封装成线程类,在类内加入输入输出流和发送接收消息的两个类,然后在run线程体中执行。

static class Channel implements Runnable{
		private DataInputStream dis = null;
		private DataOutputStream dos= null;
		private Socket client;
		private boolean isRunning = true;
		private String name;
		public Channel(Socket client) throws IOException {
			this.client = client;
			try {
				dis = new DataInputStream(client.getInputStream());
				dos = new DataOutputStream(client.getOutputStream());
				this.name = receive();
				this.send("欢迎来到聊天室");
				this.sendOther(this.name+"来到了聊天室",true);
			} catch (IOException e) {
				release();
			}
		}
		
		//接收消息
		private String receive(){		
			String msg ="";
			try {
				msg = dis.readUTF();
				
			} catch (IOException e) {
				release();
			}
			return  msg;
		}
		
		//发送消息
				private void send(String msg) {
					try {
						dos.writeUTF(msg);
						dos.flush();
					} catch (IOException e) {
					    release();
					}
				}
					//线程体
		public void run() {
			while(isRunning) {
				String msg = receive();
				//send(msg);   自己不用发给自己了
				sendOther(msg,false);   //发给其他人
			}
		}
	}
           

3.要想把一个客户端发送的数据发送给其他流的客户端,需要先建立一个容器,存放每一个新建立的socket。

static private CopyOnWriteArrayList all = new CopyOnWriteArrayList();

//比较安全,发生并发时数据不会混乱

ServerSocket socket = new ServerSocket(8888);
		
		//阻塞式的连接
		while(true) {
		Socket client = socket.accept();
		System.out.println("客户端建立了连接");
		Channel c = new Channel(client);
		all.add(c);  //管理所有的成员
		//利用多线程实现多个客户端同时运行
		new Thread(c).start();
		
		}
           

然后在线程对象中加入sendOther的关键方法。这里面还实现了私聊的功能。

//发送消息给其他人,实现群聊
		//私聊约定数据格式:@xxx:msg
		private void sendOther(String msg,boolean isSys) {
			if(msg.startsWith("@")) {  //判断是不是私聊的语句
				
				String name = msg.substring(1,msg.indexOf(":"));
				msg = msg.substring(msg.indexOf(":")+1);  //分离名称和数据
				
				for(Channel other:all) {
					if(other.name.equals(name)) {  //找到目标
						other.send(this.name+"悄悄地对您说:"+msg);
					}
				}
		}else {    //不是私聊那就是群聊了
			for(Channel other:all) {
				if(other == this) { //自己不发送给自己
					continue;
				}
				if(!isSys)
				other.send(this.name+"对所有人说:"+msg);   //群聊消息
				else other.send(msg);                   //系统消息
			}
			}
		}
           

服务端的源代码:

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * 在线聊天室:服务端
 * 目标  加入容器实现群聊 私聊在sendOther里面
 * 
 * @author Little Black
 *
 */
public class Chat {
	
	static private CopyOnWriteArrayList<Channel> all = new CopyOnWriteArrayList<Channel>();//比较安全,发生并发时数据不会混乱
	
	public static void main(String[] args) throws IOException {
		System.out.println("------server------");
		//指定端口
		ServerSocket socket = new ServerSocket(8888);
		
		//阻塞式的连接
		while(true) {
		Socket client = socket.accept();
		System.out.println("客户端建立了连接");
		Channel c = new Channel(client);
		all.add(c);  //管理所有的成员
		//利用多线程实现多个客户端同时运行
		new Thread(c).start();
		
		}
}
	//一个客户代表一个channel
	static class Channel implements Runnable{
		private DataInputStream dis = null;
		private DataOutputStream dos= null;
		private Socket client;
		private boolean isRunning = true;
		private String name;
		public Channel(Socket client) throws IOException {
			this.client = client;
			try {
				dis = new DataInputStream(client.getInputStream());
				dos = new DataOutputStream(client.getOutputStream());
				this.name = receive();
				this.send("欢迎来到聊天室");
				this.sendOther(this.name+"来到了聊天室",true);
			} catch (IOException e) {
				release();
			}
		}
		
		//接收消息
		private String receive(){		
			String msg ="";
			try {
				msg = dis.readUTF();
				
			} catch (IOException e) {
				release();
			}
			return  msg;
		}
		
		//发送消息
				private void send(String msg) {
					try {
						dos.writeUTF(msg);
						dos.flush();
					} catch (IOException e) {
					    release();
					}
				}
				
		//发送消息给其他人,实现群聊
		//私聊约定数据格式:@xxx:msg
		private void sendOther(String msg,boolean isSys) {
			if(msg.startsWith("@")) {  //判断是不是私聊的语句
				
				String name = msg.substring(1,msg.indexOf(":"));
				msg = msg.substring(msg.indexOf(":")+1);  //分离名称和数据
				
				for(Channel other:all) {
					if(other.name.equals(name)) {  //找到目标
						other.send(this.name+"悄悄地对您说:"+msg);
					}
				}
		}else {
			for(Channel other:all) {
				if(other == this) { //自己不发送给自己
					continue;
				}
				if(!isSys)
				other.send(this.name+"对所有人说:"+msg);   //群聊消息
				else other.send(msg);                   //系统消息
			}
			}
		}
		
		//释放资源
		private void release() {
			try {
				dos.close();
			    dis.close();
			   
				isRunning = false;
				all.remove(this);
				sendOther(this.name+"离开了聊天室", true) ;
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}

		//线程体
		public void run() {
			while(isRunning) {
				String msg = receive();
				//send(msg);   自己不用发给自己了
				sendOther(msg,false);   //发给其他人
			}
		}
	}
}

           

实现效果

网络编程----TCP实现聊天室项目前言