上學期學校開設了創新項目的課程,我選擇了《基於手機定位的Android考勤系統》,在整個開發過程中,總的來說,真的是學到了很多,尤其是客戶端和服務器端通信這一塊。對Socket通信,多線程等有了一定的認識,所以在此記錄一下,一起學習,我的認識還是很淺的,如有錯誤,歡迎指出。
服務器端
(我這裏是把自己的電腦當做服務器,也可以申請雲服務器)
主要步驟:
- 1、在服務器端,用一個端口來實例化一個ServerSocket對象。當服務器端開始運行時,就可以用這個端口時刻監聽從客戶端發來的連接請求。
- 2、調用ServerSocket的accept方法,接收從端口上發送來的連接請求,返回客戶端的socket對象,用來進行讀寫IO的操作。
- 3、利用客戶端socket的isConnected()方法,來獲取客戶端連接的狀態,連接成功可以做相應的操作,比如支持多用戶併發訪問的時候,可以將客戶端的socket添加到線程池中。(isConnected()方法獲取的並不是實時的客戶端的連接狀態,可以通過心跳包機制來獲取實時的連接狀態)。
- 4、通訊完成後,關閉打開的流和Socket對象。
服務器代碼(Server.java)
public class Server {
private ExecutorService executorService;// 線程池
private ServerSocket serverSocket = null;
private Socket socket = null;
private boolean isStarted = true;//判斷服務是否啓動
public Server() {
try {
// 創建線程池,池中具有(cpu個數*50)條線程
executorService = Executors.newFixedThreadPool(Runtime.getRuntime()
.availableProcessors() * 50);
//實例化ServerSocket對象 注意客戶端端口號需要和服務器端端口號保持一致
serverSocket = new ServerSocket(8090);
} catch (IOException e) {
e.printStackTrace();
//quit();
}
}
public void start() {
try {
while (isStarted) {
System.out.println("等待連接");
socket = serverSocket.accept();
String ip = socket.getInetAddress().toString();
System.out.println("客戶端已連接");
// 爲支持多用戶併發訪問,採用線程池管理每一個用戶的連接請求
if (socket.isConnected())
{
//new Thread(new HeartBeatMonitor(socket)).start();
executorService.execute(new SocketTask(socket));// 添加到線程池
}
}
if (socket != null)
socket.close();
if (serverSocket != null)
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 退出
*/
public void quit() {
try {
this.isStarted = false;
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
new Server().start();//啓動服務
// new Server().quit();
}
//單個Socket線程
final class SocketTask implements Runnable {
private Socket socket = null;
private ServerThread in;
private OutputThread out;
private OutputThreadMap outputThreadMap;
public SocketTask(Socket socket) {
this.socket = socket;
outputThreadMap = OutputThreadMap.getInstance();
//socketThreadMap = SocketThreadMap.getInstance();
}
@Override
public void run() {
out = new OutputThread(socket, outputThreadMap);// 先實例化寫消息線程,(把對應用戶的寫線程存入map緩存器中)
in = new ServerThread(socket, out, outputThreadMap);// 再實例化讀消息線程
out.setStart(true);
in.setStart(true);
in.start();
out.start();
}
}
}
服務器讀消息線程代碼
private Socket socket;
private Gson gson;
private OutputThread out;// 傳遞進來的寫消息線程,因爲我們要給用戶回覆消息啊
private OutputThreadMap map;// 寫消息線程緩存器
private SocketThreadMap socketThreadMap;// 寫消息線程緩存器
private DataInputStream inputStream;// 對象輸入流
private InputStreamReader iReader;
private boolean isStart = true;// 是否循環讀消息
public long lastReceiveHeart;//上次接收心跳包時間
public ServerThread(Socket socket, OutputThread out, OutputThreadMap map) {
// TODO Auto-generated constructor stub
this.socket = socket;
this.out = out;
this.map = map;
try {
inputStream = new DataInputStream(socket.getInputStream());// 實例化對象輸入流
} catch (IOException e) {
e.printStackTrace();
}
}
public void setStart(boolean isStart) {// 提供接口給外部關閉讀消息線程
this.isStart = isStart;
}
@Override
public void run() {
// TODO Auto-generated method stub
try {
while (isStart) {
iReader = new InputStreamReader(inputStream, "UTF-8");
char[] buffer = new char[1024];
int count = 0;
String phone = null;
StringBuffer sBuilder = new StringBuffer();
while ((count = iReader.read(buffer, 0, buffer.length)) > -1) {
sBuilder.append(buffer, 0, count);
if (count < 1024 && count != 0) {
break;
}
}
gson = new GsonBuilder().setPrettyPrinting() // 格式化輸出(序列化)
.setDateFormat("yyyy-MM-dd HH:mm:ss") // 日期格式化輸出
.create();
JsonReader jsonReader = new JsonReader(new StringReader(sBuilder.toString()));// 其中jsonContext爲String類型的Json數據
jsonReader.setLenient(true);
TranObject readObject = gson.fromJson(jsonReader, TranObject.class);
if (readObject != null )
{
lastReceiveHeart = System.currentTimeMillis();
phone = readObject.getFromUser();//手機號作爲用戶的標識
}
//如果距離接收心跳包的時間超過5分鐘 說明用戶掉線
if(System.currentTimeMillis() - lastReceiveHeart > 300000) {
try {
if (phone != null) {//更新用戶狀態
new UserDao().updateStatus(0, phone);
}
socket.close();
} catch (IOException e) {
// TODO: handle exception
e.printStackTrace();
}
}
TranObject serverResult = execute(readObject);
pushMessage(readObject);// 執行推送的消息
if (serverResult != null) {
out.setMessage(serverResult);
}
}
if (iReader != null) {
iReader.close();
}
if (inputStream != null) {
inputStream.close();
}
if (socket != null) {
socket.close();
}
} catch (IOException e) {
// TODO: handle exception
}
}
// 處理客戶端發送過來的消息
private TranObject execute(TranObject readObject) {
//...省略代碼...
}
/**
* 處理需要互相推送的消息
*
* @param readObject
*/
private void pushMessage(TranObject readObject) {
// ...省略代碼...
}
服務器寫消息線程
public class OutputThread extends Thread{
@SuppressWarnings("unused")
private OutputThreadMap map;
private SocketThreadMap socketThreadMap;
//private ObjectOutputStream oos;
private OutputStreamWriter oStreamWriter;
private DataOutputStream dataOutputStream;
private TranObject object;
private boolean isStart = true;// 循環標誌位
private Socket socket;
public OutputThread(Socket socket, OutputThreadMap map) {
this.socket = socket;
this.map = map;
try {
dataOutputStream = new DataOutputStream(socket.getOutputStream());// 在構造器裏面實例化對象輸出流
} catch (IOException e) {
e.printStackTrace();
}
}
public void setStart(boolean isStart) {
this.isStart = isStart;
}
// 調用寫消息線程,設置了消息之後,喚醒run方法,可以節約資源
public void setMessage(TranObject object) {
this.object = object;
synchronized (this) {
notify();
}
}
@Override
public void run() {
try {
while (isStart) {
// 沒有消息寫出的時候,線程等待
synchronized (this) {
wait();
}
if (object != null) {
Gson gson = new GsonBuilder()
.setPrettyPrinting() //格式化輸出(序列化)
.setDateFormat("yyyy-MM-dd HH:mm:ss") //日期格式化輸出
.create();
oStreamWriter = new OutputStreamWriter(dataOutputStream, "UTF-8");
String outputString = gson.toJson(object);
//dataOutputStream.writeInt(outputString.length());
//dataOutputStream.write(outputString.getBytes());
//dataOutputStream.flush();
StringBuffer sBuilder = new StringBuffer();
sBuilder.append(outputString);
oStreamWriter.write(sBuilder.toString());
oStreamWriter.flush();
if(object != null && object.getType()!=TranObjectType.HEART_TEST)
{
System.out.println(outputString);
}
}
}
if(oStreamWriter != null)
{
oStreamWriter.close();
}
if (dataOutputStream != null)// 循環結束後,關閉流,釋放資源
dataOutputStream.close();
if (socket != null)
socket.close();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}