初窺Socket:與自己聊次天

什么是Socket

網(wǎng)絡(luò)上的兩個程序通過一個雙向的通訊連接實現(xiàn)數(shù)據(jù)的交換吃嘿,這個雙向鏈路的一端稱為一個Socket祠乃。Socket通常用來實現(xiàn)客戶方和服務(wù)方的連接。Socket是TCP/IP協(xié)議的一個十分流行的編程界面兑燥,一個Socket由一個IP地址和一個端口號唯一確定

但是亮瓷,Socket所支持的協(xié)議種類不僅TCP/IP一種,因此兩者之間是沒有必然聯(lián)系的降瞳。在Java環(huán)境下嘱支,Socket編程主要是指基于TCP/IP協(xié)議的網(wǎng)絡(luò)編程

PS:雖然湊字數(shù)這種技能早就點滿了,但關(guān)于更多Socket及TCP/IP相關(guān)概念挣饥,還請各位看官自行/先行了解除师,這里不再多做贅述

本次Demo預(yù)覽

eclispe.gif

工具準備

  1. Eclipse(若你沒有Eclipse也沒事兒,后邊告訴你用命令行編譯運行H臃恪)
  2. AndroidStudio(若你本身就是用Eclipse開發(fā)安卓程序汛聚,那Eclipse就夠了)

服務(wù)端

OK,話不多說短荐,開干

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

接下來咱們要監(jiān)聽是否有客戶端發(fā)送連接請求叹哭,如果有,則連接并處理

SocketDemo.java:

public class SocketDemo {
    /**
     * 端口號 注意:0~1023為系統(tǒng)所保留端口號痕貌,選擇端口號時應(yīng)大于1023风罩,具體隨便你取
     */
    public static int PORT = 2345;

    public static void main(String[] args) {
        try {
            //serverSocket用于監(jiān)聽是否有客戶端發(fā)送連接請求
            ServerSocket serverSocket = new ServerSocket(PORT);
            System.out.println("服務(wù)啟動...");
            //serverSocket.accept():如果有客戶端發(fā)送連接請求,
            //則返回一個socket供處理與客戶端的連接舵稠,否則一直阻塞監(jiān)聽
            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();
        }
    }

    /**
    * 向客戶端發(fā)送消息
    * msg 發(fā)送消息內(nèi)容
    **/
    public void send(String msg) {
        try {
            // 客戶端按行(readLine)讀取消息,所以每條消息最后必須加換行符 \n,否則讀取不到
            mWriter.write(msg + "\n");
            mWriter.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
    * 
    * 不斷讀取來自客戶端的消息淋硝,一旦收到消息雹熬,則自動回復(fù)
    **/
    @Override
    public void run() {
        super.run();
        try {
            String line;
            //服務(wù)端按行讀取消息
            //不斷按行讀取,獲得來自客戶端的消息
            while ((line = mReader.readLine()) != null) {
                System.out.println("客戶端消息:" + line);
                //收到客戶端消息后谣膳,自動回復(fù)
                send("已經(jīng)收到你發(fā)送的\"" + line + "\"");
            }
            mReader.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        System.out.println("end");
    }
}

看完MySocket之后豁然開朗竿报,原來將讀取客戶端消息的操作是阻塞的,要放在子線程來做继谚,所以繼承于Thread會方便一點

那么至此烈菌,服務(wù)端的程序已經(jīng)寫完了

什么?你問怎么這么簡單花履?芽世!原因有兩個:

  1. 這只是一個基礎(chǔ)的Socket服務(wù)端程序,不用考慮那么多其他情況诡壁,自然幾行代碼就搞定了
  2. 沒錯济瓢,Socket就是這么簡單!

接下來你會發(fā)現(xiàn)妹卿,客戶端特么更簡單旺矾!

客戶端(Android)

第一步新建一個安卓項目,也叫SocketDemo吧夺克,畢竟箕宙,湊字數(shù)這個技能我比較熟練

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

安卓樣式.png

接下來看看MainActivity的代碼:

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

  1. 如果你將安卓程序跑在電腦的虛擬機上魁索,則你訪問的IP地址為:10.0.2.2(虛擬機只能通過這個IP訪問電腦)
  2. 如果你將安卓程序跑在真機上,那么你需要在CMD中輸入ipconfig獲取到IPv4地址盼铁,并且確保手機和電腦在同一個網(wǎng)絡(luò)下(連接了同一個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";
    //切記端口號一定要和服務(wù)端保持一致!
    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, "連接服務(wù)端成功");
                } catch (IOException e) {
                    Log.i(TAG, "連接服務(wù)端失敗");
                    e.printStackTrace();
                    return;
                }

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

    private void sendMsg(String msg) {
        // 如果mSocket為null有可能兩種情況:
        // 1.還在嘗試連接服務(wù)端
        // 2.連接失敗
        if (mSocket == null){
            Toast.makeText(this,"連接未完成或連接失敗饶火,無法發(fā)送消息鹏控!",Toast.LENGTH_SHORT).show();
            return;
        }
        try {
            //服務(wù)端是按行讀取消息,所以每條消息最后必須加換行符 \n
            mWriter.write(msg + "\n");
            mWriter.flush();
        } catch (IOException e) {
            Toast.makeText(this,"發(fā)送失敺羟蕖:服務(wù)端已關(guān)閉服務(wù)当辐!",Toast.LENGTH_SHORT).show();
            e.printStackTrace();
        }
    }

}

這就寫完了客戶端?鲤看?對缘揪,這就寫完了...那你別問為啥Socket咋就這么點內(nèi)容,Socket本來也不是啥難點~

并且义桂,這只是一個非常非痴殷荩基礎(chǔ)的Demo

OK,到這里就可以來跑一下程序試一試了

跑起來

跑服務(wù)端程序

  • 如果你有Eclipse慷吊,那么直接在Eclipse內(nèi)跑起來就行了袖裕!
Eclipse跑服務(wù)端程序.png
  • 如果很不巧,你沒有Eclipse

    1. 新建本文章服務(wù)端部分的SocketDemo.javaMySocket.java兩個文件溉瓶,并且放在同一個文件夾下急鳄,上面代碼沒有寫出import包,不能直接copy進文件內(nèi)用堰酿,文末我會放出所有源代碼疾宏,到文末copy一下放在兩個文件內(nèi)就行了(當然你得確保你有JDK環(huán)境!雖然作為安卓狗胞锰,這是必要的灾锯,但還是提醒一下>ふァ)
    2. 打開CMD嗅榕,切換進入上述兩個文件所在的目錄


      java文件目錄.png
    3. 執(zhí)行
    javac *.java
    java SocketDemo
    

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


    CMD運行服務(wù)端程序.png
  • 注意事項:
    1. 在Eclipse內(nèi)運行的程序,切記:如果修改內(nèi)容后要重新啟動程序吵聪,請先將正在運行的程序關(guān)閉脂男,否則將一直占用端口端考!無法再以此端口再次啟用一次程序!
    2. 如果用CMD運行的程序,提示編碼錯誤辰晕,請將所有中文替換成英文,或者將兩個.java文件內(nèi)容轉(zhuǎn)換成GBK編碼(建議換成英文!英文好的哥們兒,上佃乘!)

跑客戶端程序

直接跑安卓程序就行了!

在Eclipse跑服務(wù)端的圖已經(jīng)在文首放出驹尼,這里放一個CMD下跑服務(wù)端的圖片:

cmd.gif

注:不知為什么發(fā)送消息的時候趣避,命令行及LogCat不會即時顯示出內(nèi)容,在我ctrl+c退出程序之后才會一次全出來新翎,若有知道的朋友程帕,還望指教!萬分感謝地啰!

改進一個不足

想一下愁拭,服務(wù)端程序只響應(yīng)一個客戶端,如果又有客戶端發(fā)出連接請求亏吝,那豈不是無法響應(yīng)了岭埠!

再想一下覺得不對,也就是我自己測試蔚鸥,哪來的第二個客戶端發(fā)出連接請求

再再想一下枫攀,如果你改了一下安卓端的代碼,又一次點了運行株茶,那誰來響應(yīng)你来涨?!這樣的話启盛,因為修改安卓端代碼蹦掐,又得去把服務(wù)端的程序停了,再啟動一下僵闯,多麻煩卧抗!

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

public class SocketDemo {

    public static int PORT = 2745;

    public static void main(String[] args) {
        try {
            ServerSocket serverSocket = new ServerSocket(PORT);
            System.out.println("服務(wù)啟動...");
            //寫一個死循環(huán)社裆,如果有一個客戶端連接成功,那么繼續(xù)讓serverSocket.accept()阻塞住
            //等待下一個客戶端請求向图,這樣不論有多少個客戶端請求過來泳秀,都可以響應(yīng)到,
            //結(jié)束調(diào)試的時候再關(guān)閉服務(wù)端程序
            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的第一步已經(jīng)邁出了嗜傅,那么趕緊理解好,然后再深入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("服務(wù)啟動...");
            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");
    }
}
  • 客戶端(安卓端)的我就不放了吕嘀!

結(jié)語

  • 更多內(nèi)容歡迎訪問我的主頁我的博客
  • 如果我的文章確實有幫助到你,請不要忘了點一下文末的"?"讓他變成"?"
  • 作為新手難免很多地方理解不到位,文中若有錯誤請直(bu)接(yao)指(ma)出(wo)
  • 寫作不易偶房!
  • 致于題目叫"與自己聊次天"趁曼,我想解釋一
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市棕洋,隨后出現(xiàn)的幾起案子彰阴,更是在濱河造成了極大的恐慌,老刑警劉巖拍冠,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件尿这,死亡現(xiàn)場離奇詭異,居然都是意外死亡庆杜,警方通過查閱死者的電腦和手機射众,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來晃财,“玉大人叨橱,你說我怎么就攤上這事《鲜ⅲ” “怎么了罗洗?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長钢猛。 經(jīng)常有香客問我伙菜,道長,這世上最難降的妖魔是什么命迈? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任贩绕,我火速辦了婚禮,結(jié)果婚禮上壶愤,老公的妹妹穿的比我還像新娘淑倾。我一直安慰自己,他們只是感情好征椒,可當我...
    茶點故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布娇哆。 她就那樣靜靜地躺著,像睡著了一般勃救。 火紅的嫁衣襯著肌膚如雪碍讨。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天剪芥,我揣著相機與錄音垄开,去河邊找鬼琴许。 笑死税肪,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播益兄,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼锻梳,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了净捅?” 一聲冷哼從身側(cè)響起疑枯,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎蛔六,沒想到半個月后荆永,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡国章,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年具钥,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片液兽。...
    茶點故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡骂删,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出四啰,到底是詐尸還是另有隱情宁玫,我是刑警寧澤,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布柑晒,位于F島的核電站欧瘪,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏匙赞。R本人自食惡果不足惜恋追,卻給世界環(huán)境...
    茶點故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望罚屋。 院中可真熱鬧苦囱,春花似錦、人聲如沸脾猛。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽猛拴。三九已至羹铅,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間愉昆,已是汗流浹背职员。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留跛溉,地道東北人焊切。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓扮授,卻偏偏與公主長得像,于是被迫代替她去往敵國和親专肪。 傳聞我的和親對象是個殘疾皇子刹勃,可洞房花燭夜當晚...
    茶點故事閱讀 42,762評論 2 345

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,499評論 25 707
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)嚎尤,斷路器荔仁,智...
    卡卡羅2017閱讀 134,599評論 18 139
  • 我今晚下班比較晚。給女兒檢查完作業(yè)芽死,便陪她寫成長周記乏梁。快九點半時关贵,我說“時間不早了掌呜,要不然我們不寫了吧,剩下的讓你...
    相信就會看到閱讀 268評論 0 1
  • 名稱:甜白釉碗 Name: sweet white glazed bowl 規(guī)格:口徑15.5cm 坪哄、 高5.5c...
    御藏閣博物館金總閱讀 879評論 0 0
  • 姓名:陳權(quán) 公司:青檸養(yǎng)車 【知~學習】 《六項精進》大綱56遍 共89遍 《領(lǐng)導(dǎo)者的資質(zhì)》音頻打卡第4天 《輕課...
    水青檸閱讀 165評論 0 0