天天看點

網絡程式設計----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實作聊天室項目前言