【socket.io研究】3.手機網頁間聊天核心問題

前面咱們已經說了服務器相關的一些內容,且又根據官網給出的一個例子寫了一個能夠聊天的小程序,可是這還遠遠不夠呀,這隻能算是應用前的準備工做。接下來,一塊兒來考慮完善一個小的聊天程序吧。html

首先,修改服務器的代碼之前就是單純的接收轉發,如今咱們考慮定向轉發,及這個消息發送給須要接收的接受者,而不是所有客戶端用戶,而且考慮不使用默認namespace,那樣太不安全了。java

var app = require('express')(); 
var http = require('http').Server(app); 
var io = require('socket.io')(http); 
var fs = require('fs'), 
    os = require('os'), 
    url = require('url');

var clients = []; 
var sockets = [];

app.get('/', function (req, res) { 
  res.sendFile(__dirname + '/chat_to_everyone.html'); 
});

io = io.of('/test'); 
io.on('connection', function (socket) { 
  // register 
  socket.on('online', function (msg) { 
    console.log('new user: ' + msg + '; socket id: ' + socket.id); 
    var client_info = new Object();

    client_info.socket = socket; 
    sockets[msg] = client_info;

    // return all registered list 
    var ret = ""; 
    for (var key in sockets) { 
      if (sockets.hasOwnProperty(key)) { 
        ret += key + ' '; 
      } 
    } 
    console.log('users: ' + ret); 
    io.emit('online', ret); 
  });

  // private 
  socket.on('private', function(data) { 
    console.log('private: ' + data['uesrname'] + ' --> ' + data['to'] + ' : ' + data['msg']); 
    //io.to(room).emit('private', data); 
    io.emit(data['to']+'', data); 
    io.emit(data['uesrname']+'', data); 
  });

  // leave 
  socket.on('disconnect', function(msg){ 
    // delete from sockets 
    for (var key in sockets) { 
      if (sockets.hasOwnProperty(key)) { 
        if (sockets[key].socket.id == socket.id) { 
          console.log('leave: ', msg); 
          delete(sockets[key]);

          // return all registered list 
          var ret = ""; 
          for (var key in sockets) { 
            if (sockets.hasOwnProperty(key)) { 
              ret += key + ' '; 
            } 
          } 
          io.emit('online', ret); 
          break; 
        } 
      } 
    } 
  }); 
});

http.listen(3000, function () { 
  console.log('listening on *:3000'); 
});

 

監聽3000端口,namespace爲test,監聽事件:用戶鏈接,上線online,發送消息private,下線disconnect,注意這裏的消息不是普通的字符串了,其中至少包含了發送者用戶名username,接受者to,消息msg,固然,其中還有其餘消息,包括時間等,具體狀況具體分析,服務器在接收到消息後,打印日誌,將消息發送給接受者和發送者,爲何須要發送給發送者,由於這樣發送者在接收到服務器的返回消息時能夠肯定服務器必定是接收到消息了。jquery

那客戶端什麼樣的呢?android

<!doctype html> 
<html> 
  <head> 
    <title>Socket.IO chat with room</title> 
    <style> 
      * { margin: 0; padding: 0; box-sizing: border-box; } 
      body { font: 13px Helvetica, Arial; } 
      form { background: #000; padding: 3px; position: fixed; bottom: 0; width: 100%; } 
      form input { border: 0; padding: 10px; width: 90%; margin-right: .5%; } 
      form button { width: 9%; background: rgb(130, 224, 255); border: none; padding: 10px; } 
      #messages { list-style-type: none; margin: 0; padding: 0; } 
      #messages li { padding: 5px 10px; } 
      #messages li:nth-child(odd) { background: #eee; } 
    </style> 
  </head> 
  <body> 
  <h1>Online users</h1> 
    <ul id="online-users">

    </ul>

  <h1 id="room">Messages</h1> 
    <ul id="messages"></ul> 
    <form action=""> 
      <input id="m" autocomplete="off" /><button>Send</button> 
    </form> 
    <script src="https://cdn.socket.io/socket.io-1.2.0.js"></script> 
    <script src="http://code.jquery.com/jquery-1.11.1.js"></script> 
    <script> 
      var myid = Math.floor(Math.random() * 100) + 1; 
      var talkto = 0; 
      var myroom = ''; 
      var socket = io('/test'); 
      var password = '123456';

      // register online 
      socket.emit('online', myid); 
      socket.on('online', function(msg) { 
        //$('#online-users').append($('<li>').text(msg)); 
        var users = msg.split(' '); 
        $('#online-users').empty(); 
        for (var i in users) { 
          if (users[i]) { 
            if (users[i] == myid) { 
              $('#online-users').append($('<li>').append($('<a>').attr('href', '#').text(users[i] + ' is me'))); 
            }else { 
              $('#online-users').append($('<li>').append($('<a>').attr('href', '#').text(users[i]))); 
            } 
          } 
        }

        $('#online-users li a').click(function(){ 
          var target = $(this).text(); 
          if (myid != parseInt(target)) { 
            var from = myid, to = target; 
            talkto = to; 
            myroom = from + '#' + to; 
          } 
        }); 
      });


      // create room 
      socket.on('talkwith', function(msg) { 
        $('#room').text(msg); 
        myroom = msg; 
      }); 
      
      socket.on('' + myid, function(data){ 
        $('#messages').append($('<li>').text(data['uesrname'] + ' --> ' + data['to'] + ' : ' + data['msg'])); 
      });

      // private message 
      $('form').submit(function(){ 
        //socket.emit('private', { 'room':myroom, 'msg': myid + ' says: ' + $('#m').val()}); 
        socket.emit('private', {'uesrname':myid, 'password':password, 'to':talkto, 'msg':$('#m').val(), 'date':new Date().Format("yyyy-MM-dd HH:mm:ss")}); 
        $('#m').val(''); 
        return false; 
      });

      socket.on('private', function(data){ 
        // switch to the new room 
        myroom = data['room']; 
        $('#room').text(myroom); 
        $('#messages').append($('<li>').text(data['msg'])); 
      }); 
      
    //格式化時間,來自網絡 
    Date.prototype.Format = function (fmt) { //author: meizz 
      var o = { 
        "M+": this.getMonth() + 1, //月份 
        "d+": this.getDate(), //
        "H+": this.getHours(), //小時 
        "m+": this.getMinutes(), //
        "s+": this.getSeconds(), //
        "q+": Math.floor((this.getMonth() + 3) / 3), //季度 
        "S": this.getMilliseconds() //毫秒 
      }; 
      if (/(y+)/.test(fmt)) fmt = fmt.replace(RegExp.$1, (this.getFullYear() + "").substr(4 - RegExp.$1.length)); 
      for (var k in o) 
      if (new RegExp("(" + k + ")").test(fmt)) fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length))); 
      return fmt; 
    } 
    </script>

  </body> 
</html>

其實也是挺簡單的,界面顯示在線用戶,點擊在線用戶名,則向該用戶發送消息,消息內容包括本身的用戶名username,接收者to,消息內容msg,時間date,這裏使用了一個格式化的方法,也不難理解,注意這裏監聽的是發送給本身的消息,對其餘消息則不處理不接收。git

能夠試驗,網頁客戶端確實能夠通行聊天了,那跟android相關的部分呢,主要有如下幾個點須要注意:github

1.app不在聊天窗口,關閉了程序,就徹底不監聽發過來的消息了嗎?這很差,須要在後臺監聽,通知欄通知,這樣的話,必然用到了service,在service中處理監聽等,並能夠把消息保存到本地數據庫中,也好往後查看顯示。sql

2.不一樣的人發送的消息應該有一個聯繫人的列表吧,就想qq同樣,那就是對於接收到的消息,按照發送者分組,數據庫查詢就是group by了,時間降序排序,這裏的在個人表中,id是根據時間遞增的,我能夠按照id降序就是時間的降序了,order by ** desc,好多聯繫人,ListView和Adapter是不可少的。數據庫

3.若是已經在聊天窗口中,要不要繼續在通知欄中通知了,不須要了吧,要有一個標誌。express

4.點擊一個列表的某一項,要顯示詳細聊天內容,這個在下一篇文章中討論。小程序

大體思路清楚了,看看代碼吧:

import android.content.Context; 
import android.database.sqlite.SQLiteDatabase; 
import android.database.sqlite.SQLiteOpenHelper; 
import android.util.Log;

/** 
* 聊天相關數據庫操做 
* Created by RENYUZHUO on 2015/12/14. 
*/ 
public class SQLOperation extends SQLiteOpenHelper {

    Context context; 
    String name; 
    SQLiteDatabase.CursorFactory factory; 
    int version;

    String sqlMessage = "create table message(" 
            + "id integer primary key autoincrement," + "fromwho text," 
            + "msg text," + "data text)";

    public SQLOperation(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) { 
        super(context, name, factory, version); 
        this.context = context; 
        this.name = name; 
        this.factory = factory; 
        this.version = version; 
    }

    @Override 
    public void onCreate(SQLiteDatabase db) { 
        db.execSQL(sqlMessage); 
        Log.i("create sqlMessage", "success"); 
    }

    @Override 
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { 
//        switch (oldVersion){ 
//            case 1:{ 
//                db.execSQL(sqlMessage); 
//            } 
//        } 
    } 
}
//數據庫初始化,開啓服務           

sqlOperation = new SQLOperation(this, "fanshop.db", null, 1); 
sqLiteDatabase = sqlOperation.getWritableDatabase();

Intent intent = new Intent(context, ChatService.class); 
startService(intent);
import android.app.Notification; 
import android.app.NotificationManager; 
import android.app.PendingIntent; 
import android.app.Service; 
import android.content.ContentValues; 
import android.content.Context; 
import android.content.Intent; 
import android.database.sqlite.SQLiteDatabase; 
import android.os.Build; 
import android.os.IBinder; 
import android.util.Log;

import com.bafst.fanshop.FanShopApplication; 
import com.bafst.fanshop.R; 
import com.bafst.fanshop.model.Chat.Message; 
import com.bafst.fanshop.net.JsonUtils; 
import com.bafst.fanshop.util.Global; 
import com.github.nkzawa.emitter.Emitter; 
import com.github.nkzawa.socketio.client.IO; 
import com.github.nkzawa.socketio.client.Socket;

import java.net.URISyntaxException; 
import java.text.DateFormat; 
import java.text.ParseException; 
import java.text.SimpleDateFormat;

/**

* 監聽消息,保存到數據庫中,neededNotice和notice方法設置是否須要在通知欄中通知

*/

public class ChatService extends Service {

    Socket mSocket; 
    Context context; 
    /** 
     * 身份信息 
     */ 
    private String myname; 
    private String str = ""; 
    private String textShow = "";

    public static int num = 0;

    public static boolean notice = true;

    { 
        try { 
            mSocket = IO.socket(Global.CHAT_URL); 
        } catch (URISyntaxException e) { 
        } 
    }

    public ChatService() { 
        Log.i("ChatService", "in ChatService"); 
        context = this; 
        myname = getUserName();

        mSocket.on("online", online); 
        mSocket.on(myname, myMessage);

        mSocket.connect(); 
        mSocket.emit("online", myname);

    }

    /** 
     * 發送登錄信息 
     */ 
    Emitter.Listener online = new Emitter.Listener() { 
        @Override 
        public void call(final Object... args) { 
            Log.i("online", "in online"); 
            new Runnable() { 
                @Override 
                public void run() { 
                    Log.i("online.run", "in online.run"); 
                    String msg = args[0].toString(); 
                    String[] users = msg.split(" "); 
                    str = ""; 
                    for (String user : users) { 
                        if (myname.equals(user)) { 
                            str += "my name:" + user + "\n"; 
                        } else { 
                            str += user + "\n"; 
                        } 
                    } 
                    textShow = str; 
                    Log.i("textShow", textShow); 
                } 
            }.run(); 
        } 
    };

    NotificationManager manager; 
    Notification myNotication;

    /** 
     * 獲取發給本用戶的信息 
     */ 
    Emitter.Listener myMessage = new Emitter.Listener() { 
        @Override 
        public void call(final Object... args) { 
            new Runnable() { 
                @Override 
                public void run() { 
                    Log.i("myMessage", "in myMessage"); 
                    str = "" + args[0]; 
                    Message message = JsonUtils.fromJson(str, Message.class); 
                    textShow = message.toString(); 
                    Log.i("message", textShow);

                    ContentValues values = new ContentValues(); 
                    values.put("fromwho", message.getUesrname()); 
                    values.put("msg", message.getMsg()); 
                    // values.put("data", message.getDate().replace("-", "T").replace(" ", "U").replace(":", "V")); 
                    values.put("data", message.getDate()); 
                    SQLiteDatabase sqliteDatabase = FanShopApplication.getSqLiteDatabase(); 
                    sqliteDatabase.insert("message", null, values);

                    if (notice) { 
                        manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); 
                        Intent intent = new Intent(context, ChatDetailActivity.class); 
                        PendingIntent pendingIntent = PendingIntent.getActivity(context, 1, intent, 0); 
                        Notification.Builder builder = new Notification.Builder(context); 
                        builder.setAutoCancel(true); 
                        builder.setTicker(message.getMsg()); 
                        builder.setContentTitle(getResources().getString(R.string.app_name)); 
                        builder.setContentText(message.getUesrname()); 
                        builder.setSmallIcon(R.mipmap.ic_launcher); 
                        builder.setContentIntent(pendingIntent); 
                        builder.setOngoing(false); 
                        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { 
                            builder.setSubText(message.getMsg()); 
                            builder.build(); 
                        } 
                        builder.setNumber(++num); 
                        myNotication = builder.getNotification(); 
                        manager.notify(1, myNotication); 
                    } 
                } 
            }.run(); 
        } 
    };

    /** 
     * 獲取用戶名或者是與用戶名能夠相互對應的惟一身份驗證標識 
     * 
     * @return username 
     */ 
    private String getUserName() { 
        return String.valueOf((int) (Math.random() * 100));

    }

    @Override 
    public IBinder onBind(Intent intent) { 
        throw new UnsupportedOperationException("Not yet implemented"); 
    }

    @Override 
    public void onDestroy() { 
        mSocket.off("online", online); 
        mSocket.off(myname, online);

        mSocket.close(); 
        super.onDestroy(); 
    }

    public static void notice() { 
        notice = true; 
    }

    public static void neededNotice() { 
        notice = false; 
    } 
}
//從數據庫中讀取數據並經過適配器顯示在界面上,沒有考慮頭像問題

private void dealTab1() { 
    ChatService.neededNotice();

    List<Message> messages = new ArrayList<Message>();

    SQLiteDatabase sqliteDatabase = FanShopApplication.getSqLiteDatabase(); 
    Cursor result = sqliteDatabase.query("message", null, null, null, "fromwho", null, "id desc"); 
    Message message; 
    while (result.moveToNext()) { 
        message = new Message(); 
        message.setUesrname(result.getString(result.getColumnIndex("fromwho")) + ""); 
        message.setId(result.getInt(result.getColumnIndex("id")) + ""); 
        message.setDate(result.getString(result.getColumnIndex("data")) + ""); 
        message.setMsg(result.getString(result.getColumnIndex("msg"))); 
        messages.add(message); 
    }

    mesList = (ListView) findViewById(R.id.mesList); 
    messageListAdapter = new MessageListAdapter(this, messages); 
    mesList.setAdapter(messageListAdapter); 
}
//列表中的每個的的佈局文件

<?xml version="1.0" encoding="utf-8"?> 
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" 
    android:layout_width="match_parent" 
    android:layout_height="wrap_content" 
    android:orientation="vertical">

    <RelativeLayout 
        android:layout_width="wrap_content" 
        android:layout_height="wrap_content">

        <ImageView 
            android:id="@+id/heap" 
            android:layout_width="60sp" 
            android:layout_height="60sp" 
            android:src="@drawable/a" />

        <ImageView 
            android:layout_width="60sp" 
            android:layout_height="60sp" 
            android:src="@drawable/message_item_pit_top" />

        <TextView 
            android:id="@+id/username" 
            android:layout_width="wrap_content" 
            android:layout_height="wrap_content" 
            android:layout_toRightOf="@id/heap" 
            android:paddingLeft="@dimen/activity_horizontal_margin" 
            android:paddingTop="@dimen/heap_top" 
            android:textSize="20sp" 
            android:text="ooo" />

        <TextView 
            android:id="@+id/msg" 
            android:layout_width="wrap_content" 
            android:layout_height="wrap_content" 
            android:layout_below="@id/username" 
            android:layout_toRightOf="@id/heap" 
            android:paddingLeft="@dimen/activity_horizontal_margin" 
            android:paddingTop="@dimen/msg_top" 
            android:text="222" />

        <TextView 
            android:id="@+id/time" 
            android:text="ddd" 
            android:layout_alignParentEnd="true" 
            android:layout_alignParentRight="true" 
            android:paddingRight="@dimen/activity_horizontal_margin" 
            android:paddingTop="@dimen/heap_top" 
            android:layout_width="wrap_content" 
            android:layout_height="wrap_content" /> 
    </RelativeLayout> 
</RelativeLayout>

 

//列表總體佈局

<?xml version="1.0" encoding="utf-8"?> 
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 
    android:orientation="vertical" android:layout_width="match_parent" 
    android:layout_height="match_parent">

    <ListView 
        android:id="@+id/mesList" 
        android:layout_width="match_parent" 
        android:layout_height="wrap_content"> 
    </ListView>

</LinearLayout>

 

這些內容合理組織就能夠了,能夠實現手機與手機,手機與網頁之間的通訊,能夠接收消息,保存到數據庫中,列表顯示不一樣發來消息的用戶,就像QQ中的列表同樣,這裏沒有考慮如何顯示詳細的聊天內容,這是由於要經過點擊進入到詳情中查看,在其餘的activity中,其餘的sql語句,所以,在下一篇文章中介紹。

相關文章
相關標籤/搜索