初窺Socket:與本身聊次天

什麼是Socket

網絡上的兩個程序經過一個雙向的通信鏈接實現數據的交換,這個雙向鏈路的一端稱爲一個Socket。Socket一般用來實現客戶方和服務方的鏈接。Socket是TCP/IP協議的一個十分流行的編程界面,一個Socket由一個IP地址和一個端口號惟一肯定java

可是,Socket所支持的協議種類不只TCP/IP一種,所以二者之間是沒有必然聯繫的。在Java環境下,Socket編程主要是指基於TCP/IP協議的網絡編程git

PS:雖然湊字數這種技能早就點滿了,但關於更多Socket及TCP/IP相關概念,還請各位看官自行/先行了解,這裏再也不多作贅述github

本次Demo預覽

eclispe.gif

工具準備

  1. Eclipse(若你沒有Eclipse也沒事兒,後邊告訴你用命令行編譯運行!)編程

  2. AndroidStudio(若你自己就是用Eclipse開發安卓程序,那Eclipse就夠了)網絡

服務端

OK,話很少說,開幹socket

首先在Eclipse新建一個Java項目,就叫SocketDemo吧ide

接下來我們要監聽是否有客戶端發送鏈接請求,若是有,則鏈接並處理工具

SocketDemo.java:佈局

public class SocketDemo {
    /**
     * 端口號 注意:0~1023爲系統所保留端口號,選擇端口號時應大於1023,具體隨便你取
     */
    public static int PORT = 2345;

    public static void main(String[] args) {
        try {
            //serverSocket用於監聽是否有客戶端發送鏈接請求
            ServerSocket serverSocket = new ServerSocket(PORT);
            System.out.println("服務啓動...");
            //serverSocket.accept():若是有客戶端發送鏈接請求,
            //則返回一個socket供處理與客戶端的鏈接,不然一直阻塞監聽
            Socket socket = serverSocket.accept();
            System.out.println("與客戶端鏈接成功...");
            //這個MySocket是啥呢?是一個對socket的封裝,方便操做
            MySocket mySocket = new MySocket(socket);
            //因爲MySocket繼承於Thread,因此須要start()一下
            //致於爲啥要繼承於Thread來封裝socket,請看下方 MySocket類
            mySocket.start();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

註釋中的兩個問題,很好理解,很少說,直接看看MySocket是怎麼寫的吧:測試

MySocket.java

public class MySocket extends Thread {
    Socket mSocket;
    BufferedWriter mWriter;
    BufferedReader mReader;

    public MySocket(Socket socket) {
        this.mSocket = socket;
        try {
            mWriter = new BufferedWriter(new OutputStreamWriter(
                        socket.getOutputStream(), "utf-8"));
            mReader = new BufferedReader(new InputStreamReader(
                        socket.getInputStream(), "utf-8"));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
    * 向客戶端發送消息
    * msg 發送消息內容
    **/
    public void send(String msg) {
        try {
            // 客戶端按行(readLine)讀取消息,因此每條消息最後必須加換行符 \n,不然讀取不到
            mWriter.write(msg + "\n");
            mWriter.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
    * 
    * 不斷讀取來自客戶端的消息,一旦收到消息,則自動回覆
    **/
    @Override
    public void run() {
        super.run();
        try {
            String line;
            //服務端按行讀取消息
            //不斷按行讀取,得到來自客戶端的消息
            while ((line = mReader.readLine()) != null) {
                System.out.println("客戶端消息:" + line);
                //收到客戶端消息後,自動回覆
                send("已經收到你發送的\"" + line + "\"");
            }
            mReader.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        System.out.println("end");
    }
}

看完MySocket以後豁然開朗,原來將讀取客戶端消息的操做是阻塞的,要放在子線程來作,因此繼承於Thread會方便一點

那麼至此,服務端的程序已經寫完了

什麼?你問怎麼這麼簡單?!緣由有兩個:

  1. 這只是一個基礎的Socket服務端程序,不用考慮那麼多其餘狀況,天然幾行代碼就搞定了

  2. 沒錯,Socket就是這麼簡單!

接下來你會發現,客戶端特麼更簡單!

客戶端(Android)

第一步新建一個安卓項目,也叫SocketDemo吧,畢竟,湊字數這個技能我比較熟練

簡單一點,佈局中就一個按鈕(id=btn_send),用來發送消息,初窺嘛,簡單就是王道,佈局代碼就不上了

安卓樣式.png

接下來看看MainActivity的代碼:

不行,在看MainActivity以前還有一些話要交代清楚:

  1. 若是你將安卓程序跑在電腦的虛擬機上,則你訪問的IP地址爲:10.0.2.2(虛擬機只能經過這個IP訪問電腦)

  2. 若是你將安卓程序跑在真機上,那麼你須要在CMD中輸入ipconfig獲取到IPv4地址,而且確保手機和電腦在同一個網絡下(鏈接了同一個WIFI)

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "MainActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        connectServer();
        findViewById(R.id.btn_send).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                sendMsg("2333");
            }
        });
    }

    private Socket mSocket;
    private BufferedWriter mWriter;
    private BufferedReader mReader;
    //這個IP上面解釋過了噢,要理解一下
    private static String IP = "10.0.2.2";
    //切記端口號必定要和服務端保持一致!
    private static int PORT = 2345;

    private void connectServer() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    mSocket = new Socket(IP, PORT);
                    mWriter = new BufferedWriter(new OutputStreamWriter(
                            mSocket.getOutputStream(), "utf-8"));
                    mReader = new BufferedReader(new InputStreamReader(
                            mSocket.getInputStream(), "utf-8"));
                    Log.i(TAG, "鏈接服務端成功");
                } catch (IOException e) {
                    Log.i(TAG, "鏈接服務端失敗");
                    e.printStackTrace();
                    return;
                }

                try {
                    String line;
                    while ((line = mReader.readLine()) != null) {
                        Log.i(TAG, "服務端消息: " + line);
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                    Log.i(TAG, "服務端:已中止服務");
                }
            }
        }).start();
    }

    private void sendMsg(String msg) {
        // 若是mSocket爲null有可能兩種狀況:
        // 1.還在嘗試鏈接服務端
        // 2.鏈接失敗
        if (mSocket == null){
            Toast.makeText(this,"鏈接未完成或鏈接失敗,沒法發送消息!",Toast.LENGTH_SHORT).show();
            return;
        }
        try {
            //服務端是按行讀取消息,因此每條消息最後必須加換行符 \n
            mWriter.write(msg + "\n");
            mWriter.flush();
        } catch (IOException e) {
            Toast.makeText(this,"發送失敗:服務端已關閉服務!",Toast.LENGTH_SHORT).show();
            e.printStackTrace();
        }
    }

}

這就寫完了客戶端??對,這就寫完了...那你別問爲啥Socket咋就這麼點內容,Socket原本也不是啥難點~

而且,這只是一個很是很是基礎的Demo

OK,到這裏就能夠來跑一下程序試一試了

跑起來

跑服務端程序

  • 若是你有Eclipse,那麼直接在Eclipse內跑起來就好了!

Eclipse跑服務端程序.png

  • 若是很不巧,你沒有Eclipse

    1. 新建本文章服務端部分的SocketDemo.javaMySocket.java兩個文件,而且放在同一個文件夾下,上面代碼沒有寫出import包,不能直接copy進文件內用,文末我會放出全部源代碼,到文末copy一下放在兩個文件內就好了(固然你得確保你有JDK環境!雖然做爲安卓狗,這是必要的,但仍是提醒一下!)

    2. 打開CMD,切換進入上述兩個文件所在的目錄
      java文件目錄.png

    3. 執行

      javac *.java
      java SocketDemo

      就將程序跑起來了(ctrl+c退出程序)

    CMD運行服務端程序.png

  • 注意事項:

    1. 在Eclipse內運行的程序,切記:若是修改內容後要從新啓動程序,請先將正在運行的程序關閉,不然將一直佔用端口!沒法再以此端口再次啓用一次程序!

    2. 若是用CMD運行的程序,提示編碼錯誤,請將全部中文替換成英文,或者將兩個.java文件內容轉換成GBK編碼(建議換成英文!英文好的哥們兒,上!)

跑客戶端程序

直接跑安卓程序就好了!

在Eclipse跑服務端的圖已經在文首放出,這裏放一個CMD下跑服務端的圖片:

cmd.gif

注:不知爲何發送消息的時候,命令行及LogCat不會即時顯示出內容,在我ctrl+c退出程序以後纔會一次全出來,如有知道的朋友,還望指教!萬分感謝!

改進一個不足

想一下,服務端程序只響應一個客戶端,若是又有客戶端發出鏈接請求,那豈不是沒法響應了!

再想一下以爲不對,也就是我本身測試,哪來的第二個客戶端發出鏈接請求

再再想一下,若是你改了一下安卓端的代碼,又一次點了運行,那誰來響應你?!這樣的話,由於修改安卓端代碼,又得去把服務端的程序停了,再啓動一下,多麻煩!

好吧,既然分析了確實有這個麻煩,那就把它解決掉:

public class SocketDemo {

    public static int PORT = 2745;

    public static void main(String[] args) {
        try {
            ServerSocket serverSocket = new ServerSocket(PORT);
            System.out.println("服務啓動...");
            //寫一個死循環,若是有一個客戶端鏈接成功,那麼繼續讓serverSocket.accept()阻塞住
            //等待下一個客戶端請求,這樣不論有多少個客戶端請求過來,均可以響應到,
            //結束調試的時候再關閉服務端程序
            while (true) {
                Socket socket = serverSocket.accept();
                System.out.println("客戶端鏈接成功...");
                MySocket mySocket = new MySocket(socket);
                mySocket.start();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

so easy~不解釋了~

至此整個SocketDemo就完成了,對Socket的第一步已經邁出了,那麼趕忙理解好,而後再深刻Socket吧!

源碼

  • SocketDemo.java:

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

public class SocketDemo {

    public static int PORT = 2745;

    public static void main(String[] args) {
        try {
            ServerSocket serverSocket = new ServerSocket(PORT);
            System.out.println("服務啓動...");
            while (true) {
                Socket socket = serverSocket.accept();
                System.out.println("客戶端鏈接成功...");
                MySocket mySocket = new MySocket(socket);
                mySocket.start();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
  • MySocket.java:

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.Socket;

public class MySocket extends Thread {
    Socket mSocket;
    BufferedWriter mWriter;
    BufferedReader mReader;

    public MySocket(Socket socket) {
        this.mSocket = socket;
        try {
            mWriter = new BufferedWriter(new OutputStreamWriter(
                        socket.getOutputStream(), "utf-8"));
            mReader = new BufferedReader(new InputStreamReader(
                        socket.getInputStream(), "utf-8"));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void send(String msg) {
        try {
            mWriter.write(msg + "\n");
            mWriter.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void run() {
        super.run();
        try {
            String line;
            while ((line = mReader.readLine()) != null) {
                System.out.println("客戶端消息:" + line);
                send("收到:\"" + line + "\"");
            }
            mReader.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        System.out.println("end");
    }
}
  • 客戶端(安卓端)的我就不放了!

結語

  • 更多內容歡迎訪問個人主頁個人博客

  • 若是個人文章確實有幫助到你,請不要忘了點一下文末的"♡"讓他變成"❤"

  • 做爲新手不免不少地方理解不到位,文中如有錯誤請直(bu)接(yao)指(ma)出(wo)

  • 寫做不易!

  • 致於題目叫"與本身聊次天",我想解釋一

相關文章
相關標籤/搜索