1. 程式人生 > >java中利用TCP/IP協議演示聊天室

java中利用TCP/IP協議演示聊天室

客戶端部分客戶端部分對讀寫進行分離,避免讀寫(接發訊息互相干擾,進行讀操作時阻塞不能進行寫操作,寫時不能進行讀操作)

1、客戶端的執行主執行緒ClientTest

package com.work.socketTest.testmain;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.Socket;
import com.work.socketTest.createThread.AcceptThread;
import com.work.socketTest.createThread.SendThread;

public class ClientTest {
	@SuppressWarnings("resource")
	public static void main(String[] args) {
		System.out.println("歡迎來到聊天室,請輸入登入名");
		BufferedReader console = new BufferedReader(new InputStreamReader(System.in));//定義接收控制檯資料的流
		Socket socket = null;
		BufferedReader reader = null;
		BufferedWriter writer = null;
		
		try {
			socket = new Socket("127.0.0.1", 7777);
			
			String loginName = console.readLine();//對控制檯輸入的登入名進行讀取
			if(loginName == null || "".equals(loginName)) return;//如果沒有讀取到資料,則直接返回,結束程序
			
			/*
			  *建立用於客戶端與伺服器之間進行資料讀寫的流的實體 
			 */
			reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
			writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
			
			/**
			 * 將用於讀和寫的操作進行分離,用不同的執行緒去執行,避免讀寫操作互相干擾,也就是避免讀寫的等待,因為讀寫都是阻塞IO,避免在讀的時候不能進行寫的操作
			 *在建立本次傳送資料執行緒時就先把客戶端輸入的登入名傳送給伺服器,
		            * 在伺服器端會單獨為每個客戶端建立一個socket並將這個客戶端的連線加入執行緒池進行處理,並將登入名儲存在為其分配的執行緒中實現併發
			 */
			new Thread(new SendThread(writer,socket,loginName)).start();//建立一個傳送執行緒
			new Thread(new AcceptThread(reader,socket)).start();//建立一個接收執行緒
			
			
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}
}

2、客戶端進行讀寫分離後讀資料執行緒AcceptThread

package com.work.socketTest.createThread;
import java.io.BufferedReader;
import java.io.IOException;
import java.net.Socket;
import com.work.socketTest.util.CloseUtil;


public class AcceptThread implements Runnable{
	private BufferedReader reader = null;//建立用於接收本客戶端主執行緒建立的用於從伺服器讀取資料的流
	private boolean isRunning = true;//本次執行緒的狀態,ture表示執行,false表示關閉
	private Socket socket = null;//建立用於接收本次客戶端主執行緒建立的套接字,後面用於判斷套接字是否處於連線狀態
	
	public AcceptThread(BufferedReader reader,Socket socket) {
		super();
		this.reader = reader;//儲存本客戶端用於從伺服器讀取資料的流
		this.socket = socket;//儲存本客戶端用於客戶端與伺服器之間進行通訊的套接字
	}

	/**
	 * 讀取傳輸過來的資料方法並返回內容
	 * @return
	 */
	@SuppressWarnings("unused")
	private String accept() {
		String accept = "";
		try {
			accept = reader.readLine();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			isRunning = false;//出現異常將本執行緒關閉
			CloseUtil.close(reader);//關閉讀取伺服器發過來資料的流
		}
		return accept;
		
	}
	@Override
	public void run() {
		/**
		 * 當執行緒狀態為執行的時候執行while裡面的語句
		 */
		while (isRunning) {
			/**
			 * 判斷socket通道是否處於連線狀態
			 */
			if(socket.isConnected()) {
				System.out.println(accept());//控制檯列印從伺服器接收到的資料
			}else {
				isRunning = false;//當套接字處於斷開連線,則將本次傳送資料的執行緒關閉
				CloseUtil.close(reader);//同時關閉讀取伺服器發過來資料的流
			}
			
		}	
	}

}

3、客戶端進行讀寫分離後傳送資料執行緒SendThread

package com.work.socketTest.createThread;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;
import com.work.socketTest.util.CloseUtil;


public class SendThread implements Runnable {

	private BufferedWriter write = null;// 接收本客戶端用於向伺服器傳送的資料的寫入流
	private BufferedReader console = null;// 接收本客戶端的從控制檯讀取資料的流
	private Socket socket = null;// 接收本次客戶端建立的套接字,後面用於判斷套接字是否處於連線狀態
	private boolean isRunning = true;// 本次執行緒的狀態,ture表示執行,false表示關閉

	/**
	 * 當呼叫此執行緒時,將主執行緒的資料進行儲存
	 * 
	 * @param write
	 * @param socket
	 * @param loginName
	 */
	public SendThread(BufferedWriter write, Socket socket, String loginName) {
		super();
		this.write = write;// 將本客戶端用於向伺服器傳送資料的寫入流進行儲存
		this.socket = socket;// 將本次客戶端的套結字進行儲存
		console = new BufferedReader(new InputStreamReader(System.in));// 對本客戶端的從控制檯讀取資料的流進行賦值
		/*
		 * 在建立本次傳送資料執行緒時就先把客戶端輸入的登入名傳送給伺服器,
		 * 在伺服器端會單獨為每個客戶端建立一個socket並將這個客戶端的連線加入執行緒池進行處理,實現併發
		 */
		send(loginName);
	}

	/**
	 * 獲取控制檯的資料
	 * 
	 * @return
	 */
	private String getMgsConsole() {
		try {
			return console.readLine();// 返回控制檯獲取的資料
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			isRunning = false;// 發生異常將執行緒狀態關閉
			CloseUtil.close(console, write);// 發生異常將用於讀取控制檯資料的流,和向伺服器寫資料的流關閉
		}
		return null;
	}
    

    /**
	 * 向伺服器傳送資料方法
	 * 
	 */
	private void send(String send) {
		if (send == null && "".equals(send))
			return;
		try {
			write.write(send);
			write.newLine();
			write.flush();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			isRunning = false;// 發生異常將執行緒狀態關閉
			CloseUtil.close(console, write);// 發生異常將用於讀取控制檯資料的流,和向伺服器寫資料的流關閉
		}
	}

	@Override
	public void run() {
		/**
		 * 當本次傳送資料執行緒處於執行狀態,執行while裡面的操作
		 */
		while (isRunning) {
			if (socket.isConnected())
				send(getMgsConsole());// 如果套接字處於連線狀態,則向伺服器傳送控制檯資訊
			else {
				isRunning = false;// 當套接字處於斷開連線,則將本次傳送資料的執行緒關閉
				CloseUtil.close(console, write);// 同時關閉讀取控制檯資料的流和向伺服器寫資料的流
			}
		}
	}

}

伺服器部分:為每個客戶端建立單獨的Socket套接字,並開啟單獨的執行緒實現併發

伺服器執行主執行緒及分配的單獨執行執行緒,分配執行緒用的內部類實現

package com.work.socketTest.testmain;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import com.work.socketTest.util.CloseUtil;

@SuppressWarnings("all")
public class ServieceTest {

	ArrayList<ServerThread> serverThreadList = new ArrayList<ServerThread>();

	public static void main(String[] args) {
		new ServieceTest().start();
	}

	/**
	 * 主執行緒執行的內容的封裝
	 */
	private void start() {
		ServerSocket serverSocket = null;
		try {
			serverSocket = new ServerSocket(7777);// 設定監聽套接字監聽客戶端
			System.out.println("啟動服務,監聽中。。。");
			while (true) {
				Socket client = serverSocket.accept();// 為每個監聽到的客戶端設定socket
				ExecutorService executorService = Executors.newFixedThreadPool(20);// 建立一個擁有20個執行緒執行緒的執行緒池
				ServerThread severThread = new ServerThread(client);// 給每個客戶端建立一個執行緒
				serverThreadList.add(severThread);// 將建立好了的服務執行緒裝進集合中備用,因為需要向其他客戶端同時發訊息
				executorService.execute(severThread);// 將實現了Runable介面的ServerThread的物件扔進執行緒池執行
			}
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	/**
	 * 為每個客戶端分配單獨執行緒的內部類
	 * @author Administrator
	 *
	 */
	class ServerThread implements Runnable {
		private String loginName = null;
		private Socket socket = null;
		BufferedReader reader = null;
		BufferedWriter writer = null;
		boolean isRunning = true;

		public ServerThread(Socket socket) {
			super();
			this.socket = socket;
			try {
				// 建立讀取客戶端傳送過來資料的讀取流
				this.reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
				// 建立往客戶端傳送資料的寫入流
				this.writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
				loginName = reader.readLine();// 接收客戶端第一次傳送過來的登入名,並儲存起來
				send("歡迎進入聊天室");// 當傳送了登入名過來後,向客戶端傳送歡迎資訊
				sendOther(this.loginName + "進入聊天室", true);// 當傳送了登入名過來後,同時向其他客戶端傳送資訊
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}

		/**
		 * 向連線到伺服器的本客戶端傳送訊息
		 * 
		 * @param str
		 */
		private void send(String str) {
			if (str == null && "".equals(str))
				return;
			try {
				this.writer.write(str);
				this.writer.newLine();
				this.writer.flush();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
				isRunning = false;
				CloseUtil.close(writer);
			}

		}

		/**
		 * 傳送給其他人訊息方法,isSystemInformation判斷是不是系統訊息,也就是不是其他人發的訊息
		 * @param str
		 * @param isSystemInformation
		 */
		private void sendOther(String str, boolean isSystemInformation) {
			/**
			 * 以@xxx:sssssssssss的形式進行私聊操作,xxx表示要傳送人的姓名,sssssss表示內容
			 */
			if (str.contains(":") && str.startsWith("@")) {
				String name = str.substring(1, str.indexOf(":"));// 返回一個姓名的字串
				String info = str.substring(str.indexOf(":") + 1);// 返回傳送過來的內容的字串
				for (ServerThread serverThread : serverThreadList) {
					/**
					 * 當傳送給指定人的登入名配對成功,並且套結字處於連線狀態,執行操作
					 */
					if (serverThread.loginName.equals(name) && serverThread.socket.isConnected()) {
						serverThread.send(this.loginName + "悄悄對你說:" + info);
					}
				}

			}
			/**
			 * 這是向除了自己以外的其他人傳送訊息,就是群聊
			 */
			else {
				for (ServerThread serverThreads : serverThreadList) {
					if (serverThreads == this)
						continue;
					if (isSystemInformation)
						serverThreads.send("系統訊息  :" + str);
					else
						serverThreads.send(this.loginName + "對大家說:" + str);
				}
			}
		}

		/**
		 * 讀取資料方法,並返回內容
		 * 
		 * @return
		 */
		private String receive() {
			String msg = "";
			try {
				msg = reader.readLine();//讀取客戶端發過來的資料
			} catch (Exception e) {
				// TODO: handle exception
				e.printStackTrace();
				isRunning = false;
				serverThreadList.remove(this);// 移除自身
				CloseUtil.close(reader);
			}
			return msg;
		}

		public void run() {
			while (isRunning) {
				sendOther(receive(), false);
			}
		}

	}

}

工具類部分

這裡寫了個工具類用於關閉各種IO流,CloseUtil類

package com.work.socketTest.util;

import java.io.Closeable;
import java.io.IOException;


public class CloseUtil {

	public static void close(Closeable... close) {
		try {
			for (Closeable closeable : close) {
				if (closeable != null)
					closeable.close();
			}
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}