【Android】Socket實現(xiàn)簡易聊天室功能

作者:鄒峰立莲镣,微博:zrunker福稳,郵箱:zrunker@yahoo.com,微信公眾號:書客創(chuàng)作瑞侮,個人平臺:www.ibooker.cc的圆。

本文選自書客創(chuàng)作平臺第31篇文章。閱讀原文 区岗。

書客創(chuàng)作

簡易聊天室,什么是聊天室呢毁枯,簡單一點說就是一些人可以共同聊天慈缔,別人能夠看見你發(fā)布的消息,你也可以看到別人的消息种玛,大家的消息是公開的藐鹤。

功能分析:

1、聊天功能赂韵,聊天是一個長時間的相互交互的過程娱节,要實現(xiàn)長時間連接Socket是一個比較不錯的選擇。

2祭示、一些人相互聊天功能肄满,要想實現(xiàn)相互聊天,就要將消息轉(zhuǎn)發(fā)給所有建立連接的人,這里就要進(jìn)行消息轉(zhuǎn)發(fā)稠歉。

實現(xiàn)思路:

利用Socket實現(xiàn)客戶端和服務(wù)端長連接掰担,每次將連接進(jìn)來的Socket保存到一個集合當(dāng)中,當(dāng)其中一個Socket有接收到數(shù)據(jù)的時候怒炸,將接收到的數(shù)據(jù)轉(zhuǎn)發(fā)給其他的Socket带饱,這樣既能實現(xiàn)聊天功能又能實現(xiàn)轉(zhuǎn)發(fā)。

對Socket的操作應(yīng)放在子線程當(dāng)中阅羹,每一次有新的Socket連接進(jìn)來之后就新開一個子線程勺疼。

如果對Socket不是很了解,推薦【JavaEE】Socket簡單體驗, TCP和UDP這篇文章進(jìn)行了解捏鱼。

服務(wù)端搭建

首先要實現(xiàn)監(jiān)聽客戶端連接执庐,需要利用ServerSocket的accept方法進(jìn)行監(jiān)聽,但是該方法會將程序阻塞穷躁,所以該監(jiān)聽過程要放在子線程中完成耕肩。這里定義的線程為ServerListener。

其次每監(jiān)聽到一個Socket问潭,Socket無論是獲取還是傳輸過程都是對字節(jié)流進(jìn)行操作猿诸,這是一個比較耗時的操作,所以也要放在子線程中操作狡忙。這里定義的線程為ChatSocket梳虽。

最后還要定義一個Socket的子線程管理類,方便對所有連接進(jìn)來的Socket的子線程進(jìn)行管理灾茁,例如對數(shù)據(jù)的轉(zhuǎn)發(fā)窜觉。這里定義的管理類為ChatManager。

在程序最開始運行的時候北专,要開啟對Socket連接的監(jiān)聽禀挫,所以在main方法中開始監(jiān)聽線程。

public static void main(String[] args) {
   // 開啟服務(wù)端監(jiān)聽
   new ServerListener().start();
}

開啟監(jiān)聽線程之后拓颓,當(dāng)監(jiān)聽到有Socket進(jìn)行連接的時候语婴,不僅要新建一個子線程實現(xiàn)該Socket的操作,而且要把該Socket添加到Socket線程管理類ChatManager當(dāng)中驶睦,方便后期管理砰左。

public class ServerListener extends Thread {
   @Override
   public void run() {
      try {
         // 1、創(chuàng)建ServerScoket场航,設(shè)置端口
         ServerSocket serverSocket = new ServerSocket(12345);
         while (true) {
            // 2缠导、accept方法將導(dǎo)致程序阻塞
            Socket socket = serverSocket.accept();
            JOptionPane.showMessageDialog(null, "有客戶端連接到本機(jī)的12345端口");
            // 3、將socket傳遞給新線程
            ChatSocket cs = new ChatSocket(socket);
            cs.start();
            // 4溉痢、使用Chatmanager進(jìn)行管理
            ChatManager.getInstance().add(cs);
         }
      } catch (IOException e) {e.printStackTrace();}
   }
}

在ChatSocket新線程中僻造,要實現(xiàn)對接收到的Socket進(jìn)行處理憋他,例如獲取Socket客戶端發(fā)送過來的數(shù)據(jù)進(jìn)行轉(zhuǎn)發(fā)等。而轉(zhuǎn)發(fā)過程嫡意,是轉(zhuǎn)發(fā)給所有連接進(jìn)來的Socket举瑰,所以轉(zhuǎn)發(fā)過程的實現(xiàn)將由ChatManager完成。

ChatSocket在一開啟線程的時候蔬螟,就要進(jìn)行客戶端數(shù)據(jù)獲取此迅,并轉(zhuǎn)發(fā)。

ChatSocket要能夠完成服務(wù)端想客戶端發(fā)送數(shù)據(jù)的過程旧巾。

public class ChatSocket extends Thread {
   private Socket socket;
   public ChatSocket(Socket socket) {
      this.socket = socket;
   }
   // 服務(wù)端傳值給客戶端
   public void out(String out) {
      try {
         if (socket.isConnected() && !socket.isClosed()) {
            // 獲取當(dāng)前Socket輸出流耸序,輸出數(shù)據(jù)
            socket.getOutputStream().write(out.getBytes("gbk"));
            System.out.println("轉(zhuǎn)發(fā)數(shù)據(jù)**********" + out);
         } else {
            // 鏈接已關(guān)閉
            ChatManager.getInstance().remove(this);
         }
      } catch (IOException e) {
         e.printStackTrace();
      }
   }
   @Override
   public void run() {
      try {
         if (socket.isConnected() && !socket.isClosed()) {
            // 接受客戶端數(shù)據(jù)
            BufferedReader br = new BufferedReader(new InputStreamReader(
socket.getInputStream(), "gbk"));
            // 讀取數(shù)據(jù)
            String line = null;
            while ((line = br.readLine()) != null) {
               // 轉(zhuǎn)發(fā)數(shù)據(jù)
               ChatManager.getInstance().publish(this, line);
               System.out.println("接受數(shù)據(jù)******" + line);
            }
            br.close();
         } else {
            // 鏈接已關(guān)閉
            ChatManager.getInstance().remove(this);
         }
      } catch (UnsupportedEncodingException e) {
         e.printStackTrace();
      } catch (IOException e) {
         e.printStackTrace();
      }
   }
}

最后來說說Socket線程管理類ChatManager,一個聊天室只能有一個Socket線程管理類鲁猩,所以該類要使用單例模式坎怪。

private static ChatManager cm;
// 單例
public static ChatManager getInstance() {
   if (cm == null) {
      cm = new ChatManager();
   }
   return cm;
}

提供Socket子線程存放集合,能夠?qū)崿F(xiàn)Socket子線程添加和移除功能廓握。

// ChatSocket集合
Vector vector = new Vector<>();
// 添加ChatSocket
public void add(ChatSocket chatSocket) {
   vector.add(chatSocket);
}
// 移除ChatSocket
public void remove(ChatSocket chatSocket) {
   vector.remove(chatSocket);
}

最后是借助Socket子線程存放集合搅窿,實現(xiàn)數(shù)據(jù)轉(zhuǎn)發(fā)功能。

// 發(fā)送消息
public void publish(ChatSocket cs, String out) {
   for (int i = 0; i < vector.size(); i++) {
      ChatSocket chatSocket = vector.get(i);
      if (!cs.equals(chatSocket)) {
         chatSocket.out(out);
      }
   }
}

Android端搭建

對于Android客戶端而言就顯得稍微簡單一些隙券,只需要實現(xiàn)數(shù)據(jù)的接受和傳遞即可男应。

首先是界面的構(gòu)建:

界面的構(gòu)建

客戶端界面
當(dāng)點擊連接的時候,客戶端會向服務(wù)端發(fā)送連接請求娱仔,請求成功之后便可發(fā)送消息沐飘,聊天內(nèi)容將會在界面中間部分顯示。

1牲迫、初始化信息耐朴,在Activity中完成對控件的初始化和點擊事件監(jiān)聽。

// 定義控件全局變量
private TextView ipTv, contentTv;
private Button linkBtn, sendBtn;
private EditText sendEd;
// 初始化控件
private void initView() {
   ipTv = (TextView) findViewById(R.id.tv_ip);
   contentTv = (TextView) findViewById(R.id.tv_content);
   linkBtn = (Button) findViewById(R.id.btn_link);
   linkBtn.setOnClickListener(this);
   sendBtn = (Button) findViewById(R.id.btn_send);
   sendBtn.setOnClickListener(this);
   sendEd = (EditText) findViewById(R.id.ed_send);
}
// 點擊事件監(jiān)聽
@Override
public void onClick(View v) {
   switch (v.getId()) {
      case R.id.btn_link:// 鏈接
         connect();
         break;
      case R.id.btn_send:// 發(fā)送
         send();
         break;
      }
}

2盹憎、連接服務(wù)端筛峭,并獲取服務(wù)端傳遞數(shù)據(jù)。

// 定義三個全局變量
private Socket socket;
private BufferedReader reader;
private BufferedWriter writer;
// 連接Socket服務(wù)端
private void connect() {
   final String ip = ipTv.getText().toString().trim();
   final int port = 12345;
   // 異步執(zhí)行
   AsyncTask asyncTask = new AsyncTask() {
      @Override
      protected Void doInBackground(Void... params) {
         try {
            // 實例化Socket
            socket = new Socket(ip, port);
            reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
            publishProgress("success");
            // 讀取傳遞過來的數(shù)據(jù)
            String line;
            while ((line = reader.readLine()) != null) {
               publishProgress(line);
            }
         } catch (IOException e) {
            publishProgress("failed");
            e.printStackTrace();
         }
         return null;
      }
      @Override
      protected void onProgressUpdate(String... values) {
         if ("success".equals(values[0]))
            Toast.makeText(MainActivity.this, "連接成功", Toast.LENGTH_LONG).show();
         else if ("failed".equals(values[0]))
            Toast.makeText(MainActivity.this, "無法建立連接", Toast.LENGTH_LONG).show();
         else
            contentTv.append("他說:" + values[0] + "\n");
         super.onProgressUpdate(values);
      }
   };
   asyncTask.execute();
}

注意連接服務(wù)端和都去服務(wù)端數(shù)據(jù)是一個耗時的操作陪每,所以應(yīng)放在子線程中完成影晓,這里是采用異步AsyncTask的方式來進(jìn)行處理。

3奶稠、發(fā)送數(shù)據(jù)

// 向Socket服務(wù)端發(fā)送
private void send() {
   String out = sendEd.getText().toString().trim();
   // 發(fā)送數(shù)據(jù)
   try {
      writer.write(out + "\n");
      writer.flush();
      sendEd.setText("");
      contentTv.append("我說:" + out + "\n");
   } catch (IOException e) {
      e.printStackTrace();
   }
}

到這里就已經(jīng)全部完成了俯艰,包括服務(wù)端和Android客戶端的實現(xiàn)捡遍,在實際開發(fā)當(dāng)中往往是通過構(gòu)建一些比較成熟的框架來實現(xiàn)這一過程锌订,實現(xiàn)原理大致相同,實現(xiàn)過程有所不同画株。

Github后臺代碼地址
Github安卓客戶端代碼地址
閱讀原文


微信公眾號:書客創(chuàng)作
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末辆飘,一起剝皮案震驚了整個濱河市啦辐,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌蜈项,老刑警劉巖芹关,帶你破解...
    沈念sama閱讀 212,718評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異紧卒,居然都是意外死亡侥衬,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,683評論 3 385
  • 文/潘曉璐 我一進(jìn)店門跑芳,熙熙樓的掌柜王于貴愁眉苦臉地迎上來轴总,“玉大人,你說我怎么就攤上這事博个』痴粒” “怎么了?”我有些...
    開封第一講書人閱讀 158,207評論 0 348
  • 文/不壞的土叔 我叫張陵盆佣,是天一觀的道長往堡。 經(jīng)常有香客問我,道長共耍,這世上最難降的妖魔是什么虑灰? 我笑而不...
    開封第一講書人閱讀 56,755評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮征堪,結(jié)果婚禮上瘩缆,老公的妹妹穿的比我還像新娘。我一直安慰自己佃蚜,他們只是感情好庸娱,可當(dāng)我...
    茶點故事閱讀 65,862評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著谐算,像睡著了一般熟尉。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上洲脂,一...
    開封第一講書人閱讀 50,050評論 1 291
  • 那天斤儿,我揣著相機(jī)與錄音,去河邊找鬼恐锦。 笑死往果,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的一铅。 我是一名探鬼主播陕贮,決...
    沈念sama閱讀 39,136評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼潘飘!你這毒婦竟也來了肮之?” 一聲冷哼從身側(cè)響起掉缺,我...
    開封第一講書人閱讀 37,882評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎戈擒,沒想到半個月后眶明,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,330評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡筐高,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,651評論 2 327
  • 正文 我和宋清朗相戀三年搜囱,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片柑土。...
    茶點故事閱讀 38,789評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡犬辰,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出冰单,到底是詐尸還是另有隱情幌缝,我是刑警寧澤,帶...
    沈念sama閱讀 34,477評論 4 333
  • 正文 年R本政府宣布诫欠,位于F島的核電站涵卵,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏荒叼。R本人自食惡果不足惜轿偎,卻給世界環(huán)境...
    茶點故事閱讀 40,135評論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望被廓。 院中可真熱鬧坏晦,春花似錦、人聲如沸嫁乘。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,864評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蜓斧。三九已至仓蛆,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間挎春,已是汗流浹背看疙。 一陣腳步聲響...
    開封第一講書人閱讀 32,099評論 1 267
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留直奋,地道東北人能庆。 一個月前我還...
    沈念sama閱讀 46,598評論 2 362
  • 正文 我出身青樓,卻偏偏與公主長得像脚线,于是被迫代替她去往敵國和親搁胆。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,697評論 2 351

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

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,858評論 25 707
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)丰涉,斷路器,智...
    卡卡羅2017閱讀 134,638評論 18 139
  • 之前就覺得docker 是個很吊的東西斯碌,也在電腦上裝了docker,但是一直都比較忙一死,所以也忘了,最近突然想起就重...
    samYau閱讀 616評論 0 3
  • 今天第一次發(fā)現(xiàn)有簡書這個軟件傻唾,下載打開之后有個叫手賬的詞語引起了我的注意投慈,通過網(wǎng)絡(luò)我了解到什么是手帳,所以試著寫屬...
    baby琴閱讀 212評論 0 1
  • to my regret , the years passed my english is still poor ...
    Singaforever閱讀 149評論 0 0