先來講講 我實作的 Server Client 概念
我把SocketServer分為四個部分
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public static void main (String[] args) | |
{ | |
SocketServer socketServer=new SocketServer(); | |
socketServer.initLayout(); | |
socketServer.setIPAdrress(); | |
socketServer.initRequestListener(); | |
socketServer.serverReceiver(); | |
} |
第一部分initLayout(),我利用swing來繪製程式,主要是宣告一個panel,然後把繪圖元件都丟進去panel裡,再利用fame新增panel,在更新畫面(fame.ravalidate())
第二部分setIPAdrress(),利用InetAddress物件,把自身IP放入在LBIpAds.setText()。
第三部分initRequestListener(),把TFmsg TextField增加傾聽者(KeyListener),傾聽
TFmsgKeyListener物件,裡面主要功能是keyReleased(KeyEvent event),當按下按鈕放開時,會觸發這個功能。
第四部分是serverReceiver(),我宣告兩個變數ServerSocket和ExecutorService,ServerSocket放入自身接收Port,LISTEN_PORT=2525,而ExecutorService 則是接收當有ServerSocket回應(
我們來講解一下ServerReceiveThread部分,這是當有Client回應時,Server會專門創立一個Thread給它,一直傾聽有沒有訊息回來。而ServerRequestThread部分,則是Server想要發送訊息,或者是當有Client傳訊息到Serever再轉發給其他Client時,會使用的物件。
再來是SocketClient部分,也分為四個部分
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public static void main (String[] args) | |
{ | |
SocketClient socketClient=new SocketClient(); | |
socketClient.initLayout(); | |
socketClient.setIPAdrress(); | |
socketClient.initSocketClient("10.105.1.56", LISTEN_PORT); | |
socketClient.initRequestListener(); | |
} |
第三部分是initSocketClient
而initRequestListener()部分,也是和Server一樣,傾聽鍵盤回應,然後發送給ClientRequestThread,讓他傳送到Server。
以下是程式碼,可以直接執行
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.awt.event.KeyEvent; | |
import java.awt.event.KeyListener; | |
import java.io.DataInputStream; | |
import java.io.DataOutputStream; | |
import java.io.IOException; | |
import java.net.InetAddress; | |
import java.net.Socket; | |
import java.net.SocketTimeoutException; | |
import java.net.UnknownHostException; | |
import javax.swing.*; | |
public class SocketClient { | |
String[] test={"cat","dog","fish"}; | |
JFrame fame; | |
JPanel panel;//setLayout=null | |
JLabel LBmsg; | |
JTextField TFmsg; | |
JLabel LBIpAds; | |
JTextArea TAcontent; | |
JScrollPane SPcontent; | |
JButton BTdisconnectAll; | |
JButton BTdisconnectUser; | |
JComboBox CBuser; | |
public static final int LISTEN_PORT = 2525; | |
String msg=""; | |
Socket socket = null; | |
public static void main (String[] args) | |
{ | |
SocketClient socketClient=new SocketClient(); | |
socketClient.initLayout(); | |
socketClient.setIPAdrress(); | |
socketClient.initSocketClient("10.105.1.56", LISTEN_PORT); | |
socketClient.initRequestListener(); | |
} | |
public void initLayout() | |
{ | |
fame=new JFrame("Socket Client"); | |
panel=new JPanel(null);//setLayout=null | |
LBmsg=new JLabel("由此傳送訊息:"); | |
TFmsg=new JTextField(); | |
LBIpAds=new JLabel("IP Address:"); | |
TAcontent=new JTextArea(""); | |
SPcontent=new JScrollPane(TAcontent,ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS,ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); | |
BTdisconnectAll=new JButton("斷開Server"); | |
BTdisconnectUser=new JButton("斷開所選用戶"); | |
CBuser=new JComboBox(test); | |
fame.setBounds(0,0,600,500); | |
fame.setVisible(true); | |
fame.setResizable(false); | |
LBmsg.setLocation(10,10); | |
LBmsg.setSize(100,30); | |
TFmsg.setLocation(110,10); | |
TFmsg.setSize(450,30); | |
LBIpAds.setLocation(10,50); | |
LBIpAds.setSize(400,30); | |
TAcontent.setLineWrap(true); | |
SPcontent.setLocation(10,90); | |
SPcontent.setSize(400,350); | |
BTdisconnectAll.setLocation(420,90); | |
BTdisconnectAll.setSize(150,50); | |
BTdisconnectUser.setLocation(420,150); | |
BTdisconnectUser.setSize(150,50); | |
CBuser.setLocation(420,220); | |
CBuser.setSize(150, 20); | |
panel.add(LBmsg); | |
panel.add(TFmsg); | |
panel.add(LBIpAds); | |
panel.add(SPcontent); | |
panel.add(BTdisconnectAll); | |
panel.add(BTdisconnectUser); | |
panel.add(CBuser); | |
fame.add(panel); | |
fame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); | |
fame.revalidate() ; | |
} | |
public void setIPAdrress() | |
{ | |
InetAddress adr = null; | |
try { | |
adr = InetAddress.getLocalHost(); | |
} catch (UnknownHostException e) { | |
// TODO Auto-generated catch block | |
e.printStackTrace(); | |
} | |
LBIpAds.setText("IP Address:"+adr.getHostAddress()); | |
} | |
public void initSocketClient(String host,int port) | |
{ | |
try | |
{ | |
socket = new Socket( host, port ); | |
socket.setSoTimeout(100000);//處理時間過長,SocketTimeoutException | |
new Thread(new ClientReceiveThread()).start(); | |
new Thread(new ClientRequestThread("test1")).start(); | |
new Thread(new ClientRequestThread("test2")).start(); | |
} | |
catch ( IOException e ) | |
{ | |
e.printStackTrace(); | |
} | |
} | |
public void initRequestListener() | |
{ | |
TFmsg.addKeyListener(new TFmsgKeyListener()); | |
} | |
/** | |
* 處理Server端 User想要發送訊息。 | |
*/ | |
class TFmsgKeyListener implements KeyListener | |
{ | |
@Override | |
public void keyReleased(KeyEvent event) | |
{ | |
if(event.getKeyCode()==10) | |
{ | |
TAcontent.append("ClientSelf:"+TFmsg.getText()+"\t\n"); | |
new Thread(new ClientRequestThread(TFmsg.getText())).start();; | |
TFmsg.setText(""); | |
} | |
} | |
@Override | |
public void keyTyped(KeyEvent e) { | |
// TODO Auto-generated method stub | |
} | |
@Override | |
public void keyPressed(KeyEvent e) { | |
// TODO Auto-generated method stub | |
} | |
} | |
class ClientReceiveThread implements Runnable | |
{ | |
DataInputStream input = null; | |
public ClientReceiveThread() | |
{ | |
try | |
{ | |
input = new DataInputStream( socket.getInputStream() ); | |
} | |
catch (IOException e) | |
{ | |
// TODO Auto-generated catch block | |
System.out.println("ClientReceiveThread error:"+e); | |
} | |
} | |
@Override | |
public void run() { | |
String content = null; | |
try | |
{ | |
content=input.readUTF(); | |
while((content=input.readUTF()) !=null) //input.readUTF()會等待stream,直到傳東西回來 | |
{ | |
TAcontent.append(content +"\r\n"); | |
} | |
} | |
catch(SocketTimeoutException e) | |
{ | |
TAcontent.append("連線超時:"+ e +"\r\n"); | |
try | |
{ | |
socket.close(); | |
} catch (IOException e1) | |
{ | |
// TODO Auto-generated catch block | |
e1.printStackTrace(); | |
} | |
} | |
catch (IOException e) | |
{ | |
// TODO Auto-generated catch block | |
System.out.println("ClientReceiveThread run error:"+e); | |
try | |
{ | |
socket.close(); | |
} catch (IOException e1) | |
{ | |
// TODO Auto-generated catch block | |
e1.printStackTrace(); | |
} | |
} | |
} | |
} | |
class ClientRequestThread implements Runnable | |
{ | |
DataOutputStream output = null; | |
String msg; | |
public ClientRequestThread(String msg) | |
{ | |
try | |
{ | |
output = new DataOutputStream(socket.getOutputStream()); | |
this.msg=msg; | |
} catch (IOException e) | |
{ | |
// TODO Auto-generated catch block | |
e.printStackTrace(); | |
} | |
} | |
@Override | |
public void run() | |
{ | |
try | |
{ | |
output.writeUTF( msg ); | |
output.flush(); | |
} catch (IOException e) | |
{ | |
// TODO Auto-generated catch block | |
e.printStackTrace(); | |
} | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.awt.event.KeyEvent; | |
import java.awt.event.KeyListener; | |
import java.io.DataInputStream; | |
import java.io.DataOutputStream; | |
import java.io.IOException; | |
import java.net.Inet4Address; | |
import java.net.InetAddress; | |
import java.net.ServerSocket; | |
import java.net.Socket; | |
import java.net.UnknownHostException; | |
import java.util.ArrayList; | |
import java.util.Collections; | |
import java.util.List; | |
import java.util.concurrent.ExecutorService; | |
import java.util.concurrent.Executors; | |
import javax.swing.*; | |
public class SocketServer { | |
/*------------Layout Panel-------------*/ | |
JFrame fame; | |
JPanel panel; | |
JLabel LBmsg; | |
JTextField TFmsg; | |
JLabel LBIpAds; | |
JTextArea TAcontent; | |
JScrollPane SPcontent; | |
JButton BTdisconnectAll; | |
JButton BTdisconnectUser; | |
JComboBox CBuser; | |
/*------------Use Variable-------------*/ | |
public static final int LISTEN_PORT = 2525; | |
//define store all ArrayList of Socket and then package is sofe thread | |
public static List<Socket> socketList=Collections.synchronizedList(new ArrayList()); | |
public static void main (String[] args) | |
{ | |
SocketServer socketServer=new SocketServer(); | |
socketServer.initLayout(); | |
socketServer.setIPAdrress(); | |
socketServer.initRequestListener(); | |
socketServer.serverReceiver(); | |
} | |
public void initLayout() | |
{ | |
fame=new JFrame("Socket Server"); | |
panel=new JPanel(null);//setLayout=null | |
LBmsg=new JLabel("由此傳送訊息:"); | |
TFmsg=new JTextField(); | |
LBIpAds=new JLabel("IP Address:"); | |
TAcontent=new JTextArea(""); | |
SPcontent=new JScrollPane(TAcontent,ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS,ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); | |
BTdisconnectAll=new JButton("斷開Server"); | |
BTdisconnectUser=new JButton("斷開所選用戶"); | |
CBuser=new JComboBox(); | |
fame.setBounds(0,0,600,500); | |
fame.setVisible(true); | |
fame.setResizable(false); | |
LBmsg.setLocation(10,10); | |
LBmsg.setSize(100,30); | |
TFmsg.setLocation(110,10); | |
TFmsg.setSize(450,30); | |
LBIpAds.setLocation(10,50); | |
LBIpAds.setSize(400,30); | |
TAcontent.setLineWrap(true); | |
SPcontent.setLocation(10,90); | |
SPcontent.setSize(400,350); | |
BTdisconnectAll.setLocation(420,90); | |
BTdisconnectAll.setSize(150,50); | |
BTdisconnectUser.setLocation(420,150); | |
BTdisconnectUser.setSize(150,50); | |
CBuser.setLocation(420,220); | |
CBuser.setSize(150, 20); | |
panel.add(LBmsg); | |
panel.add(TFmsg); | |
panel.add(LBIpAds); | |
panel.add(SPcontent); | |
panel.add(BTdisconnectAll); | |
panel.add(BTdisconnectUser); | |
panel.add(CBuser); | |
fame.add(panel); | |
fame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); | |
fame.revalidate() ; | |
} | |
public void setIPAdrress() | |
{ | |
InetAddress adr = null; | |
try { | |
adr = InetAddress.getLocalHost(); //IPv6 | |
adr =Inet4Address.getLocalHost();//IPv4 | |
} catch (UnknownHostException e) { | |
// TODO Auto-generated catch block | |
e.printStackTrace(); | |
} | |
LBIpAds.setText("IP Address:"+adr.getHostAddress()); | |
} | |
public void initRequestListener() | |
{ | |
TFmsg.addKeyListener(new TFmsgKeyListener()); | |
} | |
public void serverReceiver() | |
{ | |
ServerSocket serverSocket = null; | |
ExecutorService threadExecutor = Executors.newCachedThreadPool(); //建立Thread Pool,讓Client可以連進來不斷線 | |
try | |
{ | |
serverSocket = new ServerSocket( LISTEN_PORT ); | |
TAcontent.append("Server listening requests..."+"\r\n"); | |
while ( true )// 不斷接收來自網路客戶端的連接請求 | |
{ | |
Socket socket = serverSocket.accept(); | |
synchronized(socketList) | |
{ | |
socketList.add(socket); | |
System.out.println("123sum:"+socketList.size()); | |
threadExecutor.execute( new ServerReceiveThread( socket ) ); | |
} | |
} | |
} | |
catch ( IOException e ) | |
{ | |
e.printStackTrace(); | |
} | |
finally | |
{ | |
if ( threadExecutor != null ) | |
threadExecutor.shutdown(); | |
if ( serverSocket != null ) | |
try | |
{ | |
serverSocket.close(); | |
} | |
catch ( IOException e ) | |
{ | |
e.printStackTrace(); | |
} | |
} | |
} | |
/** | |
* 處理Server端 User想要發送訊息。 | |
*/ | |
class TFmsgKeyListener implements KeyListener | |
{ | |
@Override | |
public void keyReleased(KeyEvent event) | |
{ | |
if(event.getKeyCode()==10) | |
{ | |
TAcontent.append("Server:"+TFmsg.getText()+"\t\n"); | |
new Thread(new ServerRequestThread(TFmsg.getText())).start();; | |
TFmsg.setText(""); | |
} | |
} | |
@Override | |
public void keyTyped(KeyEvent e) { | |
// TODO Auto-generated method stub | |
} | |
@Override | |
public void keyPressed(KeyEvent e) { | |
// TODO Auto-generated method stub | |
} | |
} | |
/** | |
* 處理Server端的Receive接收執行緒。 | |
*/ | |
class ServerReceiveThread implements Runnable | |
{ | |
private Socket clientSocket; | |
DataInputStream input = null; | |
public ServerReceiveThread( Socket clientSocket ) | |
{ | |
this.clientSocket = clientSocket; | |
} | |
@Override | |
public void run() | |
{ | |
TAcontent.append("有"+clientSocket.getRemoteSocketAddress() +"連線進來!"+"\r\n"); | |
UpdateCBuser(String.valueOf(clientSocket.getRemoteSocketAddress()),"add"); //CBuser新增user | |
try | |
{ | |
input = new DataInputStream( this.clientSocket.getInputStream() ); | |
while ( true ) | |
{ | |
//這部分,Server回應太快,Client還沒開啟所以訊息會loss | |
new Thread(new ServerRequestThread(String.format("Hi, %s!\n", clientSocket.getRemoteSocketAddress() ))).start(); | |
break; | |
} | |
String content=null; | |
while((content=readFromClient())!=null) | |
{ | |
TAcontent.append(content + "\r\n"); | |
new Thread(new ServerRequestThread(content)).start(); | |
} | |
} | |
catch ( IOException e ) | |
{ | |
e.printStackTrace(); | |
} | |
finally | |
{ | |
try | |
{ | |
if ( input != null ) | |
{ | |
input.close(); | |
System.out.println("input close"); | |
} | |
if ( this.clientSocket != null || !this.clientSocket.isClosed() ) | |
{ | |
this.clientSocket.close(); | |
System.out.println("clientSocket close"); | |
} | |
} | |
catch ( IOException e ) | |
{ | |
e.printStackTrace(); | |
} | |
} | |
} | |
private String readFromClient() | |
{ | |
try | |
{ | |
return input.readUTF(); | |
} | |
catch(IOException e) | |
{ | |
socketList.remove(clientSocket); | |
UpdateCBuser(String.valueOf(clientSocket.getRemoteSocketAddress()),"remove"); //CBuser新增user | |
} | |
return null; | |
} | |
} | |
/** | |
* 處理Client端的Request請求執行緒。 | |
*/ | |
class ServerRequestThread implements Runnable | |
{ | |
DataOutputStream output = null; | |
String msg=""; | |
public ServerRequestThread(String msg) | |
{ | |
this.msg=msg; | |
} | |
@Override | |
public void run() //這部分不知道Runnable run完會不會自動清除DataOutputStream output | |
{ | |
// TODO Auto-generated method stub | |
synchronized(socketList) | |
{ | |
System.out.println("sum:"+socketList.size()); | |
for(Socket socket:socketList) | |
{ | |
try | |
{ | |
output = new DataOutputStream( socket.getOutputStream() ); | |
output.writeUTF(this.msg); | |
output.flush(); | |
} catch (IOException e) { | |
// TODO Auto-generated catch block | |
e.printStackTrace(); | |
} | |
} | |
} | |
} | |
} | |
void UpdateCBuser(String User ,String action) | |
{ | |
if(action=="add") | |
{ | |
CBuser.addItem(User); | |
CBuser.setSelectedIndex(0); | |
System.out.println(CBuser.getSelectedIndex()); | |
} | |
else if(action=="remove") | |
{ | |
CBuser.removeItem(User); | |
} | |
CBuser.revalidate(); | |
CBuser.repaint(); | |
panel.revalidate(); | |
panel.repaint(); | |
fame.revalidate(); | |
fame.repaint(); | |
} | |
} |