1. 程式人生 > >基於Android ServerSocket的簡易聊天室功能

基於Android ServerSocket的簡易聊天室功能

上篇部落格介紹了Socket Socket的基本講解以及對於內部的方法使用做了一些簡單的整理,並且通過ServerSocket自己做了一個通過服務端(PC主機)與多臺手機進行通訊的Demo,實現了群發功能、指定手機發送訊息功能、顯示已連線的手機數量以及IP地址和埠號,通過這些功能的實現,就可以實現無線群控功能的一個雛形,自己也有想過能否通過ServerSocket來實現模擬聊天功能的一個Demo,所以就趁熱打鐵做了一個簡易聊天室的App,Server充當終端伺服器,來起到接收訊息並把訊息轉發給其它裝置,因為真正的聊天功能都是攜帶具體訊息和一個指定客戶的地址等資訊,來起到客戶將訊息傳送給特定的一個好友,而不是發給了不該收到的人,但是時間有限就只寫了一個類似於微信群或是qq討論組的這樣一個聊天功能,希望能夠加深自己對ServerSocket的認識瞭解,同時也能夠幫助他人。

1.話不多說進入正題,先建立服務端,在Android Studio中建立Java程式碼,如下圖所示:


選擇Java Library 需要改名字的自己隨意


2.建立Client Manager客戶端管理類來管理客戶端的訊息,因為省時間就直接從我上篇部落格的程式碼基礎上進行的修改~程式碼如下所示:(自己編寫程式碼塊提交後總有亂碼...所以只能把自己的程式碼複製貼上進來啦~格式有點奇怪,但是沒有亂碼~)

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket; import java.util.HashMap; import java.util.Map; /** * Created by sp01 on 2017/4/28. */ // 客戶端的管理類 public class ClientManager { private static Map<String,Socket> clientList = new HashMap<>(); private static ServerThread serverThread = null; private static class
ServerThread implements Runnable { private int port = 10010; private boolean isExit = false; private ServerSocket server; public ServerThread() { try { server = new ServerSocket(port); System.out.println("啟動服務成功" + "port:" + port); } catch (IOException e) { System.out.println("啟動server失敗,錯誤原因:" + e.getMessage()); } } @Override public void run() { try { while (!isExit) { // 進入等待環節 System.out.println("等待手機的連線... ... "); final Socket socket = server.accept(); // 獲取手機連線的地址及埠號 final String address = socket.getRemoteSocketAddress().toString(); System.out.println("連線成功,連線的手機為:" + address); new Thread(new Runnable() { @Override public void run() { try { // 單執行緒索鎖 synchronized (this){ // 放進到Map中儲存 clientList.put(address,socket); } // 定義輸入流 InputStream inputStream = socket.getInputStream(); byte[] buffer = new byte[1024]; int len; while ((len = inputStream.read(buffer)) != -1){ String text = new String(buffer,0,len); System.out.println("收到的資料為:" + text); // 在這裡群發訊息 sendMsgAll(text); } }catch (Exception e){ System.out.println("錯誤資訊為:" + e.getMessage()); }finally { synchronized (this){ System.out.println("關閉連結:" + address); clientList.remove(address); } } } }).start(); } } catch (IOException e) { e.printStackTrace(); } } public void Stop(){ isExit = true; if (server != null){ try { server.close(); System.out.println("已關閉server"); } catch (IOException e) { e.printStackTrace(); } } } } public static ServerThread startServer(){ System.out.println("開啟服務"); if (serverThread != null){ showDown(); } serverThread = new ServerThread(); new Thread(serverThread).start(); System.out.println("開啟服務成功"); return serverThread; } // 關閉所有server socket 和 清空Map public static void showDown(){ for (Socket socket : clientList.values()) { try { socket.close(); } catch (IOException e) { e.printStackTrace(); } } serverThread.Stop(); clientList.clear(); } // 群發的方法 public static boolean sendMsgAll(String msg){ try { for (Socket socket : clientList.values()) { OutputStream outputStream = socket.getOutputStream(); outputStream.write(msg.getBytes("utf-8")); } return true; }catch (Exception e){ e.printStackTrace(); } return false; } }

程式碼看起來比較簡單,用了儘可能方便理解的書寫,也寫好了一些註釋,應該不難理解所以就不具體解釋了,對Server Socket有不理解的地方,請參考我的上篇部落格~希望能有所幫助,但需要解釋的地方可能只有一點吧,群發的方法對收到的訊息全部進行廣播式的傳送,那麼不就傳送的人也會收到訊息了嘛?(可能有人感覺會有資料顯示重複的情況)我想說的是,真正歷史記錄都會在服務端進行資料儲存和處理這樣想就行了,我在Android端做了一個RecyclerView的載入不同行佈局實現模擬聊天介面,傳送和接收的歷史訊息都會顯示在列表上,本人傳送的內容在左側,其他人傳送的訊息被顯示在右側。

3.在MaClass.java(主入口類)中開啟服務:

public class MyClass {

    public static void main(String[]args){
        // 開啟伺服器
ClientManager.startServer();
}

}
4.到這裡為止,服務端的程式碼就完成了很簡單,有人執行程式碼時,會出現控制檯中文亂碼情況,解決辦法我轉發的部落格中有介紹,但是考慮到有人很懶,不想在麻煩去找,我就直接在下面介紹了,很簡單隻需要一句話:
tasks.withType(JavaCompile) {
    options.encoding = "UTF-8"
}


複製程式碼塊,放進藍色的gradle位置中(Java lib包內)dependencies{}下方位置,在Rebuild一下就好了。

5.新建並編寫Android客戶端工程,大致內容就是一個EditText輸入框,點選按鈕傳送資料,上方為一個載入不同行佈局的RecyclerView,實現歷史記錄閱覽,下面是activity_main.xml的內容:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="sq.test_socketchat.MainActivity">
    <android.support.v7.widget.RecyclerView
android:id="@+id/rv"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="9"/>
    <LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
        <EditText
android:id="@+id/et"
android:layout_weight="8"
android:layout_width="0dp"
android:hint="輸入內容"
android:layout_height="match_parent" />
        <Button
android:id="@+id/btn"
android:text="傳送"
android:layout_margin="3dp"
android:layout_weight="2"
android:layout_width="0dp"
android:layout_height="match_parent" />
    </LinearLayout>
</LinearLayout>


顯示效果如上圖所示。

6.接下來是準備工作,首先寫一個MyBean,用來儲存名字,訊息內容,訊息時間,以及載入哪種佈局:

/**
 * Created by sp01 on 2017/4/28.
 */
public class MyBean {
    private String data;
    private String time,name;
    private int number;
    public MyBean() {
    }

    public MyBean(String data, int number,String time,String name) {
        this.data = data;
        this.number = number;
        this.name = name;
        this.time = time;
}

    public String getTime() {
        return time;
}

    public void setTime(String time) {
        this.time = time;
}

    public String getName() {
        return name;
}

    public void setName(String name) {
        this.name = name;
}

    public String getData() {
        return data;
}

    public void setData(String data) {
        this.data = data;
}

    public int getNumber() {
        return number;
}

    public void setNumber(int number) {
        this.number = number;
}
}

7.同樣是準備工作,兩個不同佈局的item的書寫,第一種內容顯示在左側第二種則在右側,直接複製我的就好:

第一個item:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:background="#c8fffa"
android:layout_margin="5dp"
android:layout_width="match_parent"
android:layout_height="wrap_content">
    <TextView
android:id="@+id/tv"
android:layout_gravity="left"
android:textSize="20sp"
android:text="lalala"
android:layout_margin="5dp"
android:textColor="#000000"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
    <LinearLayout
android:layout_gravity="left"
android:orientation="horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
        <TextView
android:id="@+id/tv_name"
android:text="name_xx"
android:layout_margin="5dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
        <TextView
android:id="@+id/tv_time"
android:layout_margin="5dp"
android:text="1993-09-28"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
    </LinearLayout>
</LinearLayout>

第二個item:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:background="#fcfdd9"
android:layout_margin="5dp"
android:layout_width="match_parent"
android:layout_height="wrap_content">
    <TextView
android:id="@+id/tv2"
android:layout_gravity="right"
android:textSize="20sp"
android:text="lalala"
android:layout_margin="5dp"
android:textColor="#000000"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
    <LinearLayout
android:layout_gravity="right"
android:orientation="horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
        <TextView
android:id="@+id/tv_name2"
android:text="name_xx"
android:layout_margin="5dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
        <TextView
android:id="@+id/tv_time2"
android:layout_margin="5dp"
android:text="1993-09-28"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
    </LinearLayout>
</LinearLayout>

8.接下來是書寫MyAdapter內的程式碼(RecyclerView載入不同行佈局很簡單就不過多強調了):

import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import java.util.ArrayList;
/**
 * Created by sp01 on 2017/4/28.
 */
public class MyAdapter extends RecyclerView.Adapter {

    private Context context;
    private ArrayList<MyBean> data;
    private static final int TYPEONE = 1;
    private static final int TYPETWO = 2;
    public MyAdapter(Context context) {
        this.context = context;
}

    public void setData(ArrayList<MyBean> data) {
        this.data = data;
notifyDataSetChanged();
}

    @Override
public int getItemViewType(int position) {
        return data.get(position).getNumber();
}

    @Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        RecyclerView.ViewHolder holder = null;
        switch (viewType){
            case TYPEONE:
                View view = LayoutInflater.from(context).inflate(R.layout.item,parent,false);
holder = new OneViewHolder(view);
                break;
            case TYPETWO:
                View view1 = LayoutInflater.from(context).inflate(R.layout.item2,parent,false);
holder = new TwoViewHolder(view1);
                break;
}
        return holder;
}

    @Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        int itemViewType = getItemViewType(position);
        switch (itemViewType){
            case TYPEONE:
                OneViewHolder oneViewHolder = (OneViewHolder) holder;
oneViewHolder.tv1.setText(data.get(position).getData());
oneViewHolder.name1.setText(data.get(position).getName());
oneViewHolder.time1.setText(data.get(position).getTime());
                break;
            case TYPETWO:
                TwoViewHolder twoViewHolder = (TwoViewHolder) holder;
twoViewHolder.tv2.setText(data.get(position).getData());
twoViewHolder.name2.setText(data.get(position).getName());
twoViewHolder.time2.setText(data.get(position).getTime());
                break;
}
    }

    @Override
public int getItemCount() {
        return  data != null && data.size() > 0 ? data.size() : 0;
}
    
    class OneViewHolder extends RecyclerView.ViewHolder{
       private TextView tv1;
        private TextView name1,time1;
       public OneViewHolder(View itemView) {
           super(itemView);
tv1 = (TextView) itemView.findViewById(R.id.tv);
name1 = (TextView) itemView.findViewById(R.id.tv_name);
time1 = (TextView) itemView.findViewById(R.id.tv_time);
}
   }

    class TwoViewHolder extends RecyclerView.ViewHolder{
        private TextView tv2;
        private TextView name2,time2;
        public TwoViewHolder(View itemView) {
            super(itemView);
tv2 = (TextView) itemView.findViewById(R.id.tv2);
name2 = (TextView) itemView.findViewById(R.id.tv_name2);
time2 = (TextView) itemView.findViewById(R.id.tv_time2);
}
    }
    
}

9.下面終於進入到了正題~進入到MainActivity中,程式碼如下所示:

import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
public class MainActivity extends AppCompatActivity {

    private RecyclerView rv;
    private EditText et;
    private Button btn;
    private Socket socket;
    private ArrayList<MyBean> list;
    private MyAdapter adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
rv = (RecyclerView) findViewById(R.id.rv);
et = (EditText) findViewById(R.id.et);
btn = (Button) findViewById(R.id.btn);
list = new ArrayList<>();
adapter = new MyAdapter(this);
        final Handler handler = new MyHandler();
        new Thread(new Runnable() {
            @Override
public void run() {
                try {
                    socket = new Socket("192.168.1.111", 10010);
InputStream inputStream = socket.getInputStream();
                    byte[] buffer = new byte[1024];
                    int len;
                    while ((len = inputStream.read(buffer)) != -1) {
                        String data = new String(buffer, 0, len);
// 發到主執行緒中 收到的資料
Message message = Message.obtain();
message.what = 1;
message.obj = data;
handler.sendMessage(message);
}

                } catch (IOException e) {
                    e.printStackTrace();
}
            }
        }).start();
btn.setOnClickListener(new View.OnClickListener() {
            @Override
public void onClick(View v) {
                final String data = et.getText().toString();
                new Thread(new Runnable() {
                    @Override
public void run() {
                        try {
                            OutputStream outputStream = socket.getOutputStream();
SimpleDateFormat df = new SimpleDateFormat("HH:mm:ss");    //設定日期格式
outputStream.write((socket.getLocalPort() + "//" + data + "//" + df.format(new Date())).getBytes("utf-8"));
outputStream.flush();
} catch (IOException e) {
                            e.printStackTrace();
}
                    }
                }).start();
}
        });
}

    private class MyHandler extends Handler {
        @Override
public void handleMessage(Message msg) {
            super.handleMessage(msg);
            if (msg.what == 1) {
                //
int localPort = socket.getLocalPort();
String[] split = ((String) msg.obj).split("//");
                if (split[0].equals(localPort + "")) {
                    MyBean bean = new MyBean(split[1],1,split[2],"我:");
list.add(bean);
} else {
                    MyBean bean = new MyBean(split[1],2,split[2],("來自:" + split[0]));
list.add(bean);
}

                // 向介面卡set資料
adapter.setData(list);
rv.setAdapter(adapter);
LinearLayoutManager manager = new LinearLayoutManager(MainActivity.this, LinearLayoutManager.VERTICAL, false);
rv.setLayoutManager(manager);
}
        }
    }
}

程式碼很簡單,因為Socket傳送的資料只能是一個基本的資料型別,不能傳遞類似於HashMap、集合、陣列這樣的資料,所以只能通過拼接字串的形式通過加入一些特殊的符號,來起到分割資料的作用,因為傳遞的資料中帶有傳送者、接受者、時間、訊息等這樣的資料,所以通過split來區別這些資料,從而進行具體的分配來實現目的。

10.最後許可權不要忘記加入~

<uses-permission android:name="android.permission.INTERNET"/>

那麼執行實現的具體效果又是怎樣的呢?請看下面(話說CSDN加圖好麻煩啊):

1)這是開啟伺服器之後,兩臺手機開啟聊天Demo:


2)這是客戶端傳送資料的顯示內容:


3)這是服務端在客戶端聊天時顯示的Log:


總結:在這篇部落格中,主要是對ServerSocket和Android相結合的應用場景進行了自己為是的分析,覺得聊天的通訊方式可以通過Socket來實現,但是本文沒有對ServerSocket的基礎部分進行大量的講解,因為在我的上篇部落格中已經進行的很多的講解和分析了,所以就不做過多的累贅的去寫重複性的東西了,本文的一切東西都是自己親自手寫手敲的,如果這兩篇關於socket的講解和Demo為你帶來了哪怕僅僅一點點微不足道的幫助,都希望給我留個言點個贊來讓我知道,浪費不了你幾秒鐘的時間~~雖然自己也是在最近才接觸的Socket但是有了一點心得,就總結了這兩篇部落格,寫了倆個Demo來分享自己的一些淺薄的經驗,如果需要下載我的專案請 <點選這裡> 進行下載,裡面有個下載後先進行閱讀的文件要先檢視,兩個專案我都放在一個大包中了所以不需要下載兩次。下篇可能會介紹執行緒池,我日後也有打算自學.net,因為公司需要所以想自學一下,要是有什麼問題或是總結都會以部落格的形式來見證自己的成長,歡迎大家對我提建議和批評我會一一檢視並回復的。奮鬥

相關推薦

基於Android ServerSocket簡易聊天功能

上篇部落格介紹了Socket Socket的基本講解以及對於內部的方法使用做了一些簡單的整理,並且通過ServerSocket自己做了一個通過服務端(PC主機)與多臺手機進行通訊的Demo,實現了群發功能、指定手機發送訊息功能、顯示已連線的手機數量以及IP地址和埠號,通過這

基於websocket的簡易聊天的實現

用java實現基於websocket的簡易聊天室    WebSocket protocol 是HTML5一種新的協議。它實現了瀏覽器與伺服器全雙工通訊(full-duplex)。一開始的握手需要藉助HTTP請求完成。                          

php基於websocket搭建簡易聊天(socket)

前言 http連線分為短連線和長連線。短連線一般可以用ajax實現,長連線就是websocket。短連線實現起來比較簡單,但是太過於消耗資源。websocket高效不過相容存在點問題。websocket是html5的資源 前端 //連線socket

Unity 簡易聊天基於TCP)(1)

為了準備畢業設計,學習了伺服器與客戶端之間傳輸的一些簡單的知識,並跟著網路上的教程製作了一個簡易的Unity聊天室 伺服器:用C# .Net Framework寫的 結構分為:main(主函式)、Server類(用於伺服器的開啟和接收客戶端連線)、Client類(接收訊息和傳送訊息)、Message類(用

Netty+Android搭建一個簡易聊天(實現群聊和私聊)

零,前言 JRBM專案中無論是好友私聊,公開聊天室,還是比賽平臺都需要用到長連線,之前沒有接觸過網路通訊等知識,更別說框架了,因此直接上手netty確實有些困難,在前期主要是在b站上看(https://www.bilibili.com/video/av26415011)這個

java基於redis訂閱/釋出訊息實現聊天功能

一、引言 趁著國慶節把redis高階應用都寫完吧,其實都很簡單。 redis高階應用:安全性、事務處理、持久化操作,訂閱/釋出、虛擬記憶體 安全性其實就是在連線redis時,需要一個密碼認證,可以

基於flask框架,使用websocket實現多人聊天功能

後端程式碼:   # web_socket 的收發機制 # web_socket --> web + socket --> http協議 + socket # web_socket協議就是ws協議 # 基於flask框架為web_socket提供服務 from flas

基於flask框架,使用websocket實現一對一聊天功能

info app lan 提示 function _id 數據 sca 響應 後端代碼: from flask import Flask,request,render_template from geventwebsocket.handler import WebSock

SpringBoot整合WebSocket【基於STOMP協議】進行點對點[一對一]和廣播[一對多]實時推送,內附簡易聊天demo

最近專案來了新需求,需要做一個實時推送的功能,伺服器主動推送訊息給客戶端,在網上經過一輪搜查之後,確定使用WebSocket來進行開發。以前經常聽說WebSocket的神奇之處,如今終於可以嘗試使用它了。1.淺談WebSocketWebSocket是在HTML5基礎上單個TC

Android開發之基於MINA框架的聊天通訊功能實現

博主近端時間在做一個專案,裡面有個需求需要實現點對點可選擇聊天,就像一個QQ一樣。但是需求又沒那麼高大尚。似乎就只是一個簡單的聊天。網上找了很多資訊,最後決定使用MINA框架來實現。現在的IM通訊協議有一個叫XMPP的,博主一看,好像專案需求不需要那麼複雜,於是博主根據實際

基於Linux c 用socket和執行緒 實現的簡易聊天之伺服器

#include <stdio.h> #include <string.h> #include <stdlib.h> #include <sys/types.h> #include <sys/socket.h> #

Golang程式碼蒐集-基於websocket+vue.js的簡易聊天

前言 筆者學完vue.js後,總是不斷地找個機會練練手,於是,在假期花了點時間使用websocket和vue.js,寫了一個簡單的聊天室,功能並不強大,只是實現了簡單的群聊功能,但是詳細地演示了websocket、chan、vue.js的應用,寫在這裡算是做記

實現簡易聊天(一)

ima log body .com 麻煩 導入 定義 右鍵 正常 預備工作: (1)讀取文件的時候可能會遇到多個文件一起傳,可以用線程池。 (2)發送不同類型的請求時,如發送的是聊天信息,發送的是文件,發送的是好友上線請求等,但對於接受者來說都是字節流無法分別,這就需要我們

搭建Websocket簡易聊天

eui imp str %s 管理 關閉連接 string self stat 本文,我們通過Egret和Node.js實現一個在線聊天室的demo。主要包括:聊天,改用戶名,查看其他用戶在線狀態的功能。大致流程為,用戶訪問網頁,即進入聊天狀態,成為新遊客,通過底部的輸入框

ajax實現聊天功能

需求如下: 先死後活。  需求分析,分析思路如圖所示: 1.建立資料庫 create database chat; create table messages( id int unsigned primary key auto_increment, sender va

使用java nio 編寫簡易聊天

伺服器端:相當於是一個接收客戶端訊息的分發器,為了簡單,直接在接收到客戶端的訊息後,                  直接傳送給所有的客戶端 package chatroom.chatser

團隊專案簡易聊天開發NABCD分析

1) N (Need 需求) 面向具體共同興趣愛好的小眾群體和團隊的聊天分享資訊。 2) A (Approach 做法) 使用者可以配置服務端的埠 向已經連線到服務端的使用者傳送系統訊息 使用者可以配置要連線的伺服器的ip地址與埠號 使用者可以配置自己的使用者名稱 使用者可以

基於AIO的CS聊天

所謂AIO,即是非同步IO,它的IO操作交由作業系統完成。設定監聽器(類似於一個訊號處理函式),當系統IO操作完成時,會被監聽器監聽到,並執行相應的後續操作,然後返回。 監聽器一般使用CompletionHandler。 伺服器端程式碼:  package com.nanh

用 Netty 實現 WebSocket 聊天功能

WebSocket 是 H5 的一種技術,目前有很多語言都有相應的實現,之前有用 WebSocket 實現過 Java 和安卓,IOS 通訊的專案。想來也該分享一下,看過不少專案要實現頁面資料實時更新的操作,有用輪詢有用 Socket 連結的,當然也不排除有很多前端其他技術可以實現,WebSocke

NetCore MVC WebSocket 簡易聊天

前端程式碼:  function SocketHelper(params) { var options = $.extend({}, { uri: "ws://" + window.location.host + "//socket/Connect",