本文中最重要的是執行緒,不管在 client 或是 server 端,都有做擷取系統輸入功能,所以必須要做多執行緒,否則將會被 scanner 卡死。
Server 端:
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package com.tp.transfer;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.EOFException;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Map.Entry;
import java.util.Scanner;
import java.util.concurrent.ConcurrentHashMap;
public class MasterServer {
//並行版 Hashmap ,用於單獨通信或廣播記錄使用者位置
public static ConcurrentHashMap<String, Socket> actionMapping = new ConcurrentHashMap<String,Socket>();
public static int onlineCount = 0; //上線人數統計
public void OpenServer(){
try{
ServerSocket server = new ServerSocket(3333);
System.out.println("Server Running in localhost:3333");
//Listen Command ,bordcast
// Server 端下擷取系統輸入命令系統做什麼,預設把所有字串廣播
Thread Commander = new Thread(new Commander());
Commander.start();
while (true) {
Socket s = server.accept();
actionMapping.put(s.getRemoteSocketAddress().toString(),s); //加入並行表
Thread Server = new Thread(new ServerRunner(s));
Server.start(); //處理每個連接者的單獨通信
}
}catch(Exception e){
System.out.println("Server port could be used!");
}
}
public static void main(String[] args) {
MasterServer m = new MasterServer();
m.OpenServer();
}
}
//拍謝多寫了一個執行緒,可自行砍掉ww
class ServerRunner extends Thread{
Socket s;
ServerRunner(Socket s){
this.s = s;
}
@Override
public void run(){
MasterServer.onlineCount ++;
try{
System.out.println(s.getRemoteSocketAddress() + " Device Connected!");
Thread MessageListener = new Thread(new MessageListener(s));
MessageListener.start();
}catch(Exception e){
}
}
}
//Commander 是 Server 專用的系統輸入擷取處理器,你可以加上一些特別的需求,如: 取得目前連接數量。
class Commander implements Runnable{
Socket s;
Commander(){
}
@Override
public void run() {
try{
Scanner scanner = new Scanner(System.in);
while(true){
System.out.print("司令官,請問發送什麼命令? ");
String command = scanner.nextLine();
//handler
bordcast(command);
}
}catch(Exception e){
System.out.println("Writting Failed! " + e);
}
}
// 針對所有連接者廣播
public void bordcast(String message) throws IOException{
System.out.println("發送廣播訊息!");
for(Entry<String, Socket> entry : MasterServer.actionMapping.entrySet()) {
String key = entry.getKey();
Socket s = entry.getValue();
PrintWriter out = new PrintWriter(new OutputStreamWriter(s.getOutputStream()), true);
out.println(message);
}
}
}
class MessageListener implements Runnable{
Socket s;
MessageListener(Socket s){
this.s = s;
}
@Override
public void run() {
try{
BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream()));
while (true) {
if(br.ready()){
String message = br.readLine();
System.out.println(s.getRemoteSocketAddress() + " 傳遞的訊息是:" + message);
respondents(message);
}
}
}catch(Exception e){
System.err.println(s.getRemoteSocketAddress() + " Disconnected!");
MasterServer.onlineCount --;
}
}
public void respondents(String message) throws IOException{
PrintWriter out = new PrintWriter(new OutputStreamWriter(s.getOutputStream()), true);
out.println("伺服器正確收到您的訊息。");
//可以改做一些字串判斷,讓使用者可以對伺服器要求其他資源來回覆。
}
}
如果你有單獨存取某些物件、資料的需求,請務必加上 Synchronized,不要用非同步去處理,亦可以使用 ReentrantLock 來鎖定目標物件操作 。
Client 端:
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package com.tp.transfer;
import java.io.*;
import java.net.InetAddress;
import java.net.Socket;
import java.util.Scanner;
import java.util.logging.Level;
import java.util.logging.Logger;
public class LetClient {
public static void main(String[] args) {
try {
Scanner scanner = new Scanner(System.in);
Socket s = new Socket(InetAddress.getByName("localhost"), 3333);
PrintWriter w = new PrintWriter(new OutputStreamWriter(s.getOutputStream()), true);
Thread t1 = new Thread(new Helper(s)); //只針對接收訊息做多執行緒,實現邊發訊息邊接收
t1.start();
//這裡沒有在送信加執行緒,如果有需求可以自行實作
while (true) {
System.out.print("Enter Message: ");
String command = scanner.nextLine();
w.println(command);
System.out.println("--------------------");
}
} catch (Exception e) {
System.out.println(e);
}
}
}
class Helper extends Thread{
Socket s = null;
Helper(Socket s){
this.s = s;
}
public void run(){
try {
BufferedReader r = new BufferedReader(new InputStreamReader(s.getInputStream()));
while(true){
if(r.ready()){
System.out.println("從伺服器傳來的資料:" + r.readLine());
}
}
} catch (IOException ex) {
System.out.println("Socket Problem");
}
}
}
目前測試過 ConcurrentHashmap 在多執行緒下還是會讓 CPU 快滿,還有優化空間。
沒有留言:
張貼留言