(二)網路程式設計:聊天室(2)
第五步:既然是聊天室,那麼僅僅只能一個使用者自己和自己聊天,顯然該該程式是有瑕疵的。那麼我們就需要支援多使用者同時線上聊天。這一步中,我們就需要用到多執行緒的概念。為什麼要用到多執行緒?執行緒可以通俗的理解為每有一個新運動員便多建造一條跑道,以便所有運動員可以經歷同樣的從頭到尾的全部過程。那如果放到程式中便是每有一個新的客戶端登入,便重新經歷一遍從連線成功到傳送訊息的過程,同時,為了能夠讓服務端迴圈的接受客戶端,需要將客戶端連線連線邏輯放入到迴圈中。而這些程式碼僅與服務端有關,客戶端不必修改。
服務端:
/**
* 主要執行邏輯
*/
public void start() {
try {
while(true) {
Socket socket=server.accept();
System.out.println(socket.getInetAddress()+"連線成功!");
moreClientHandler mch=new moreClientHandler(socket);
Thread t=new Thread(mch);
t.start();
}
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 多客戶端處理內部類
*/
class moreClientHandler implements Runnable{
private Socket socket;
public moreClientHandler(Socket socket) {
this.socket=socket;
}
public void run() {
BufferedReader br;
try {
br = new BufferedReader(new InputStreamReader(socket.getInputStream(),"UTF-8"));
PrintWriter pw=new PrintWriter(new OutputStreamWriter(socket.getOutputStream(),"UTF-8"),true);
String Read="";
while((Read=br.readLine())!=null) {
pw.println(Read);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
第六步:實現了可以同時連線多個客戶端後,便需要使得各個客戶端之間可以相互通訊,因為僅僅完成第五步的話,我們會發現每個客戶端僅僅能夠與自己對話,無法實現聊天室的基本功能,所以我們需要實現互相之間的通訊。客戶端與服務端資訊互發時,我們是通過傳送資訊客戶端通過自己的輸出流將資訊傳送至服務端後,服務端通過自己的輸入流讀取到客戶端傳送來的資訊,再通過自己的輸出流將資訊傳送回客戶端,我們通過給每個客戶端一個服務端輸入輸出流來實現客戶端與服務端的互動,那麼便可以將客戶端看做是key,將客戶端對應的服務端輸出流看做value,由此我們可以定義一個map集合,來儲存客戶端與其對應的服務端輸出流,當某一客戶端傳送資訊時,可以通過遍歷map集合中的所有value即所有服務端輸出流,將某一客戶端發到服務端的資訊逐一發送到各個客戶端,即實現了群聊,所以我們現在需要給每一個客戶端一個名字,以方便用於分辨是哪個客戶端,在客戶端連線服務端時,應將自己的名字同時傳送至服務端。所以,服務端也需要一個資訊傳送方法。同時,可以在客戶端窗體設定方法塊內設定窗體名字,使得自己的客戶端名字可見。
服務端:
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
public class Server {
//成員變數
ServerSocket server;
Map<String,PrintWriter> map;
/**
* 服務端構造方法
*/
public Server() {
try {
server=new ServerSocket(8888);
map=new HashMap<String,PrintWriter>();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 多客戶端處理內部類
*/
class moreClientHandler implements Runnable{
private Socket socket;
public moreClientHandler(Socket socket) {
this.socket=socket;
}
public void run() {
try {
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream(),"UTF-8"));
PrintWriter pw=new PrintWriter(new OutputStreamWriter(socket.getOutputStream(),"UTF-8"),true);
String ClientName=br.readLine();
map.put(ClientName,pw);
String Read="";
while((Read=br.readLine())!=null) {
sendMessage(ClientName,Read);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* 服務端反饋資訊至客戶端
*/
public void sendMessage(String str1,String str2) {
Set<Entry<String,PrintWriter>> entrys=map.entrySet();
Date date=new Date();
SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
for(Entry<String,PrintWriter> entry:entrys) {
entry.getValue().println(str1+"("+sdf.format(date.getTime())+")"+"\n"+str2);
}
}
客戶端:
public class Client extends JFrame{
private String name;
public Client(String name) {
this.name=name;
try {
socket=new Socket("localhost",8888);
panel=new JPanel();
showMsg=new JTextArea(15,42);
sendMsg=new JTextArea(5,42);
inIt();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 主執行邏輯
*/
public void start() {
sendMessage(name);
KeyListener key=new KeyAdapter() {
public void keyReleased(KeyEvent e) {
String str=sendMsg.getText();
if(e.getKeyCode()==KeyEvent.VK_ENTER) {
sendMessage(str);
}
}
};
sendMsg.addKeyListener(key);
try {
BufferedReader br=new BufferedReader(new InputStreamReader(socket.getInputStream(),"UTF-8"));
String backMsg="";
while((backMsg=br.readLine())!=null) {
showMsg.append(backMsg+"\n");
}
} catch (IOException e1) {
e1.printStackTrace();
}
}
public static void main(String[] args) {
Client client=new Client("zyd"+(int)(Math.random()*100));
client.start();
}
}
第七步:現在群聊已經完全實現,接下來就是實現私聊,也就是當@某個客戶端時,僅被@客戶端與本客戶端可見訊息內容,其他客戶端均不可見。
服務端:
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
public class Server {
//成員變數
ServerSocket server;
Map<String,PrintWriter> map;
/**
* 服務端構造方法
*/
public Server() {
try {
server=new ServerSocket(8888);
map=new HashMap<String,PrintWriter>();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 多客戶端處理內部類
*/
class moreClientHandler implements Runnable{
private Socket socket;
public moreClientHandler(Socket socket) {
this.socket=socket;
}
public void run() {
try {
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream(),"UTF-8"));
PrintWriter pw=new PrintWriter(new OutputStreamWriter(socket.getOutputStream(),"UTF-8"),true);
String ClientName=br.readLine();
map.put(ClientName,pw);
String Read="";
while((Read=br.readLine())!=null) {
sendMessage(ClientName,Read);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* 服務端反饋資訊至客戶端
*/
public void sendMessage(String str1,String str2) {
Set<Entry<String,PrintWriter>> entrys=map.entrySet();
Date date=new Date();
SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
if(!str2.substring(0,1).equals("@")) {
for(Entry<String,PrintWriter> entry:entrys) {
entry.getValue().println(str1+"("+sdf.format(date.getTime())+")"+"\n"+str2);
}
}else {
map.get(str1).println(str1+"("+sdf.format(date.getTime())+")"+"\n"+str2);
map.get(str2.substring(str2.indexOf('@')+1,str2.indexOf(' '))).println(str1+"("+sdf.format(date.getTime())+")"+"\n"+str2);
}
}
總結:聊天室至此已經完成,其中主要運用了多執行緒及輸入輸出流及網路程式設計,實現了人與人之間通過機器進行互動,通過再一次的編寫寫聊天室,更加通透的瞭解了流的使用以及對多執行緒的理解,希望能夠在以後的程式設計到路上更上一層樓。與所有看到本系列博文的程式猿共勉。