Android聊天室

??最新在研究網(wǎng)絡(luò)編程乡小,感覺好好玩佩微,然后玩得興起就想做個聊天室來玩玩九串。這個聊天室叫 ChatRoom 生棍,是一個基于 TCP 通信的聊天室,并且用到了一些 Material Design 的控件蛤虐,之前一直都在趕項目都沒時間學(xué)習(xí) Material Design(其實(shí)不是沒時間党饮,是有些事情很煩,所以耽擱了)驳庭,工作室的昭哥老說我刑顺,你基礎(chǔ)工不扎實(shí)氯窍,嘿嘿。

TCP介紹

TCP 是一種可靠蹲堂、必須連接才能通信的協(xié)議狼讨。

因為需要連接才能通信,所以 TCP 通信嚴(yán)格區(qū)分客戶端和服務(wù)器端柒竞,只有客戶端連接了服務(wù)器端才能實(shí)現(xiàn)通信政供,服務(wù)器端不能連接客戶端并且需要事先啟動,等待客戶端的請求能犯。

使用重發(fā)機(jī)制以保證數(shù)據(jù)傳輸?shù)目煽啃增昶#òl(fā)送后,需要收到確認(rèn)的信息踩晶,否則進(jìn)行重發(fā))

附圖
附圖

ChatRoom

好了执泰,扯了辣么多,我們來看看 ChatRoom 是怎么實(shí)現(xiàn)的吧渡蜻!先看看效果:

服務(wù)器端

/**
 * Created by kn on 2016/5/24.
 *
 * 聊天室主線程服務(wù)
 */
public class MyServerSocket {

    public static void main(String args[]){
        new ServerListener().start();
    }
}
/**
 * Created by kn on 2016/5/24.
 *
 * 聊天監(jiān)聽線程類
 */
public class ServerListener extends Thread {

    @Override
    public void run() {
        try {
            //端口號port:1~65535
            ServerSocket serverSocket = new ServerSocket(30000);
            while(true){
                //該方法會阻塞當(dāng)前線程
                Socket socket = serverSocket.accept();
                //建立連接
                System.out.println("有客戶端鏈接到本地的30000端口");
                //將socket傳遞給新的線程
                ChatSocket chatSocket = new ChatSocket(socket);
                chatSocket.start();
                ChatManager.getInstance().add(chatSocket);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
/**
 * Created by kn on 2016/5/24.
 *
 * 聊天線程類
 */
public class ChatSocket extends Thread {

    Socket socket;

    public ChatSocket(Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run() {
        try {
            BufferedReader br = new BufferedReader(
                    new InputStreamReader(socket.getInputStream(), "UTF-8"));
            String line = null;
            while ((line = br.readLine()) != null) {
                ChatManager.getInstance().publish(this, line);
            }
            br.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    /**
    * 輸出服務(wù)器返回的信息
    * @param message
    */
    public void showMessage(String message) {
        try {
            socket.getOutputStream().write((message + "\n").getBytes("UTF-8"));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    /**
    * 判斷socket是否關(guān)閉
    * @return
    */  
    public boolean isSocketClosed(){
        return socket.isClosed();
    }
}
/**
 * Created by kn on 2016/5/24.
 *
 * 聊天室線程管理類
 */
public class ChatManager {

    private static ChatManager instance = new ChatManager();
    //聊天線程列表
    Vector<ChatSocket> vector = new Vector<>();

    private ChatManager() {
    }

    /**
     * 單例模式
     * @return
     */
    public static ChatManager getInstance() {
        if (instance == null) {
            synchronized (ChatManager.class) {
                if (instance == null) {
                    instance = new ChatManager();
                }
            }
        }
        return instance;
    }

    /**
     * 添加聊天線程
     *
     * @param chatSocket
     */
    public void add(ChatSocket chatSocket) {
        vector.add(chatSocket);
    }

    /**
     * 向聊天室的其他線程發(fā)布消息
     * @param chatSocket
     * @param message
     */
    public void publish(ChatSocket chatSocket, String message) {
        //遍歷線程列表
        for (int i = 0; i < vector.size(); i++) {
            ChatSocket mChatSocket = vector.get(i);
            //判斷是否是己線程
            if (!chatSocket.equals(mChatSocket)) {
                //判斷該線程是否已經(jīng)斷開服務(wù)器的連接
                if (mChatSocket.isSocketClosed()) {
                    vector.remove(i);
                } else {
                    mChatSocket.showMessage(message);
                }
            }
        }
    }
}

寫完以上代碼术吝,服務(wù)器端基本上完成了,其實(shí)現(xiàn)在茸苇,我們就可以玩聊天室了排苍。打開多個 cmd 并輸入 telnet localhost 30000 就可以連接服務(wù)器,然后你就可以在黑框下聊天了学密。

客戶端

主界面淘衙,輸入服務(wù)器的 IP 和聊天中顯示的名字

進(jìn)入界面

public class MainActivity extends AppCompatActivity {

    MaterialEditText metName;//聊天的昵稱
    MaterialEditText metIP;//聊天室的IP
    ImageView ivAvatar;//聊天的頭像
    ButtonFlat btnEnter;//進(jìn)入聊天按鈕
    ProgressBar progressBar;//進(jìn)度條
    FrameLayout flProgressBar;

    public static Socket socket = null;

    final static int SUCCESS = 0x01;//進(jìn)入聊天室成功
    final static int FAILURE = 0x02;//進(jìn)入聊天室失敗
    final static int TIMEOUT = 0x03;//連接超時
    final static int UN_KNOWN_HOST = 0x04;//未知主機(jī)
    private BufferedWriter writer;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initView();
    }

    private void initView() {
        metName = (MaterialEditText) findViewById(R.id.met_name);
        metIP = (MaterialEditText) findViewById(R.id.met_ip);
        ivAvatar = (ImageView) findViewById(R.id.iv_avatar);
        btnEnter = (ButtonFlat) findViewById(R.id.btn_enter);
        progressBar = (ProgressBar) findViewById(R.id.progress);
        flProgressBar = (FrameLayout) findViewById(R.id.fl_progress);

        metName.setText("kntryer");
        metIP.setText("192.168.1.101");

        btnEnter.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                enterChatRoom();
            }
        });
    }

    private void enterChatRoom() {
        //獲取ip
        final String ipString = metIP.getText().toString().trim();

        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    socket = new Socket();
                    socket.connect(new InetSocketAddress(ipString, 30000), 5000);
                    writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
                    handler.sendEmptyMessage(SUCCESS);
                } catch (SocketTimeoutException e) {
                    //連接超時 在UI界面顯示消息
                    handler.sendEmptyMessage(TIMEOUT);
                    e.printStackTrace();
                } catch (UnknownHostException e) {
                    handler.sendEmptyMessage(UN_KNOWN_HOST);
                    e.printStackTrace();
                } catch (IOException e) {
                    handler.sendEmptyMessage(FAILURE);
                    e.printStackTrace();
                }
            }
        }).start();
    }

    Handler handler = new Handler(new Handler.Callback() {
        @Override
        public boolean handleMessage(Message msg) {
            switch (msg.what) {
                case SUCCESS:
                    String name = metName.getText().toString();
                    try {
                        if (writer != null) {
                            writer.write("system," + name + " enter ChatRoom\n");
                            writer.flush();
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    Intent intent = new Intent(MainActivity.this, ChatActivity.class);
                    intent.putExtra("name", name);
                    startActivity(intent);
                    break;
                case FAILURE:
                    showIsOpenWifi("Please check whether the network is open or not.");
                    break;
                case TIMEOUT:
                    showIsOpenWifi("SocketTimeoutException");
                    break;
                case UN_KNOWN_HOST:
                    showIsOpenWifi("UnknownHostException");
                    break;

            }
            return false;
        }
    });

    private void showIsOpenWifi(String message) {
        new SnackBar(this, message, "Yes", new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //跳到WiFi,這個還沒做
            }
        }).show();
    }
}

聊天界面

/**
 * Created by kn on 2016/5/24.
 */
public class ChatActivity extends AppCompatActivity {

    ButtonFlat btnSent;//發(fā)送消息
    MaterialEditText metMsg;//消息
    ListView lvMsg;//顯示消息

    ArrayList<ChatMessage> msgList = new ArrayList<>();//消息列表
    MessageAdapter adapter;
    Socket socket;
    String name;//聊天昵稱
    BufferedReader reader;//讀取服務(wù)器返回的數(shù)據(jù)
    BufferedWriter writer;//向服務(wù)器發(fā)送數(shù)據(jù)

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_chat);
        initView();
    }

    private void initView() {

        name = getIntent().getStringExtra("name");
        socket = MainActivity.socket;

        btnSent = (ButtonFlat) findViewById(R.id.btn_sent);
        metMsg = (MaterialEditText) findViewById(R.id.met_msg);
        lvMsg = (ListView) findViewById(R.id.lv_msg);

        adapter = new MessageAdapter(this,msgList);
        lvMsg.setAdapter(adapter);

        //開啟一個新的線程監(jiān)聽聊天室腻暮,并獲取聊天室的消息
        getMessage();

        btnSent.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                ChatMessage chatMessage = new ChatMessage();
                chatMessage.setType(2);
                chatMessage.setName(name);
                chatMessage.setMessage(metMsg.getText().toString());
                msgList.add(chatMessage);
                adapter.notifyDataSetChanged();

                String message = name + "," + metMsg.getText().toString();
                metMsg.setText("");
                setMessage(message);
            }
        });
    }
    /**
     * 發(fā)送消息
     * @param message
     */
    private void setMessage(String message) {
        try {
            if (writer != null) {
                writer.write(message + "\n");
                writer.flush();
            } else {
                Toast.makeText(ChatActivity.this, "你已經(jīng)離開 YY 聊天室彤守!", Toast.LENGTH_LONG).show();
            }

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    /**
     * 獲取聊天室的消息
     */
    private void getMessage() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
                    reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                    String line = null;
                    while ((line = reader.readLine()) != null) {

                        ChatMessage chatMessage = new ChatMessage();
                        String[] temp = line.split(",");
                        System.out.print(temp[0]);
                        if(temp[0].equals("system")){//如果temp[0]=system說明是系統(tǒng)消息
                            chatMessage.setType(0);
                            chatMessage.setMessage(temp[1]);
                        }else{
                            chatMessage.setType(1);
                            chatMessage.setName(temp[0]);
                            chatMessage.setMessage(temp[1]);
                        }
                        msgList.add(chatMessage);
                        handler.sendEmptyMessage(0x00);
                    }
                    reader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }

    Handler handler = new Handler(new Handler.Callback() {
        @Override
        public boolean handleMessage(Message msg) {
            adapter.notifyDataSetChanged();
            return false;
        }
    });

}

注意

查看服務(wù)器 IP

??打開 cmd ,輸入 ipconfig 哭靖,就可以看到本機(jī)的 IP 設(shè)置具垫,在無線局域網(wǎng)適配 wifi 這里的 IPv4地址 就是服務(wù)器的 IP 了。

手機(jī)連不上服務(wù)器

??模擬器可以連上服務(wù)器试幽,手機(jī)卻連不上怎么辦筝蚕?網(wǎng)上說是手機(jī)是外網(wǎng),訪問不了內(nèi)網(wǎng)的問題铺坞,手機(jī)和電腦連同一個 wifi 就行了起宽。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市济榨,隨后出現(xiàn)的幾起案子燎含,更是在濱河造成了極大的恐慌,老刑警劉巖腿短,帶你破解...
    沈念sama閱讀 222,252評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡橘忱,警方通過查閱死者的電腦和手機(jī)赴魁,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,886評論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來钝诚,“玉大人颖御,你說我怎么就攤上這事∧模” “怎么了潘拱?”我有些...
    開封第一講書人閱讀 168,814評論 0 361
  • 文/不壞的土叔 我叫張陵,是天一觀的道長拧略。 經(jīng)常有香客問我芦岂,道長,這世上最難降的妖魔是什么垫蛆? 我笑而不...
    開封第一講書人閱讀 59,869評論 1 299
  • 正文 為了忘掉前任禽最,我火速辦了婚禮,結(jié)果婚禮上袱饭,老公的妹妹穿的比我還像新娘川无。我一直安慰自己,他們只是感情好虑乖,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,888評論 6 398
  • 文/花漫 我一把揭開白布懦趋。 她就那樣靜靜地躺著,像睡著了一般疹味。 火紅的嫁衣襯著肌膚如雪仅叫。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,475評論 1 312
  • 那天佛猛,我揣著相機(jī)與錄音惑芭,去河邊找鬼。 笑死继找,一個胖子當(dāng)著我的面吹牛遂跟,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播婴渡,決...
    沈念sama閱讀 41,010評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼幻锁,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了边臼?” 一聲冷哼從身側(cè)響起哄尔,我...
    開封第一講書人閱讀 39,924評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎柠并,沒想到半個月后岭接,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體富拗,經(jīng)...
    沈念sama閱讀 46,469評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,552評論 3 342
  • 正文 我和宋清朗相戀三年鸣戴,在試婚紗的時候發(fā)現(xiàn)自己被綠了啃沪。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,680評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡窄锅,死狀恐怖创千,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情入偷,我是刑警寧澤追驴,帶...
    沈念sama閱讀 36,362評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站疏之,受9級特大地震影響殿雪,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜体捏,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,037評論 3 335
  • 文/蒙蒙 一冠摄、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧几缭,春花似錦河泳、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,519評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至某抓,卻和暖如春纸兔,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背否副。 一陣腳步聲響...
    開封第一講書人閱讀 33,621評論 1 274
  • 我被黑心中介騙來泰國打工汉矿, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人备禀。 一個月前我還...
    沈念sama閱讀 49,099評論 3 378
  • 正文 我出身青樓洲拇,卻偏偏與公主長得像,于是被迫代替她去往敵國和親曲尸。 傳聞我的和親對象是個殘疾皇子赋续,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,691評論 2 361

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