前面我们已经说了服务器相关的一些内容,且又根据官网给出的一个例子写了一个可以聊天的小程序,但是这还远远不够呀,这只能算是应用前的准备工作。接下来,一起来考虑完善一个小的聊天程序吧。

首先,修改服务器的代码以前就是单纯的接收转发,现在我们考虑定向转发,及这个消息发送给需要接收的接受者,而不是全部客户端用户,并且考虑不使用默认namespace,那样太不安全了。

  1. var app = require('express')();
  2. var http = require('http').Server(app);
  3. var io = require('socket.io')(http);
  4. var fs = require('fs'),
  5. os = require('os'),
  6. url = require('url');
  7.  
  8. var clients = [];
  9. var sockets = [];
  10.  
  11. app.get('/', function (req, res) {
  12. res.sendFile(__dirname + '/chat_to_everyone.html');
  13. });
  14.  
  15. io = io.of('/test');
  16. io.on('connection', function (socket) {
  17. // register
  18. socket.on('online', function (msg) {
  19. console.log('new user: ' + msg + '; socket id: ' + socket.id);
  20. var client_info = new Object();
  21.  
  22. client_info.socket = socket;
  23. sockets[msg] = client_info;
  24.  
  25. // return all registered list
  26. var ret = "";
  27. for (var key in sockets) {
  28. if (sockets.hasOwnProperty(key)) {
  29. ret += key + ' ';
  30. }
  31. }
  32. console.log('users: ' + ret);
  33. io.emit('online', ret);
  34. });
  35.  
  36. // private
  37. socket.on('private', function(data) {
  38. console.log('private: ' + data['uesrname'] + ' --> ' + data['to'] + ' : ' + data['msg']);
  39. //io.to(room).emit('private', data);
  40. io.emit(data['to']+'', data);
  41. io.emit(data['uesrname']+'', data);
  42. });
  43.  
  44. // leave
  45. socket.on('disconnect', function(msg){
  46. // delete from sockets
  47. for (var key in sockets) {
  48. if (sockets.hasOwnProperty(key)) {
  49. if (sockets[key].socket.id == socket.id) {
  50. console.log('leave: ', msg);
  51. delete(sockets[key]);
  52.  
  53. // return all registered list
  54. var ret = "";
  55. for (var key in sockets) {
  56. if (sockets.hasOwnProperty(key)) {
  57. ret += key + ' ';
  58. }
  59. }
  60. io.emit('online', ret);
  61. break;
  62. }
  63. }
  64. }
  65. });
  66. });
  67.  
  68. http.listen(3000, function () {
  69. console.log('listening on *:3000');
  70. });

监听3000端口,namespace为test,监听事件:用户连接,上线online,发送消息private,下线disconnect,注意这里的消息不是普通的字符串了,其中至少包含了发送者用户名username,接受者to,消息msg,当然,其中还有其他消息,包括时间等,具体情况具体分析,服务器在接收到消息后,打印日志,将消息发送给接受者和发送者,为什么需要发送给发送者,因为这样发送者在接收到服务器的返回消息时可以确定服务器一定是接收到消息了。

那客户端什么样的呢?

  1. <!doctype html>
  2. <html>
  3. <head>
  4. <title>Socket.IO chat with room</title>
  5. <style>
  6. * { margin: 0; padding: 0; box-sizing: border-box; }
  7. body { font: 13px Helvetica, Arial; }
  8. form { background: #000; padding: 3px; position: fixed; bottom: 0; width: 100%; }
  9. form input { border: 0; padding: 10px; width: 90%; margin-right: .5%; }
  10. form button { width: 9%; background: rgb(130, 224, 255); border: none; padding: 10px; }
  11. #messages { list-style-type: none; margin: 0; padding: 0; }
  12. #messages li { padding: 5px 10px; }
  13. #messages li:nth-child(odd) { background: #eee; }
  14. </style>
  15. </head>
  16. <body>
  17. <h1>Online users</h1>
  18. <ul id="online-users">
  19.  
  20. </ul>
  21.  
  22. <h1 id="room">Messages</h1>
  23. <ul id="messages"></ul>
  24. <form action="">
  25. <input id="m" autocomplete="off" /><button>Send</button>
  26. </form>
  27. <script src="https://cdn.socket.io/socket.io-1.2.0.js"></script>
  28. <script src="http://code.jquery.com/jquery-1.11.1.js"></script>
  29. <script>
  30. var myid = Math.floor(Math.random() * 100) + 1;
  31. var talkto = 0;
  32. var myroom = '';
  33. var socket = io('/test');
  34. var password = '123456';
  35.  
  36. // register online
  37. socket.emit('online', myid);
  38. socket.on('online', function(msg) {
  39. //$('#online-users').append($('<li>').text(msg));
  40. var users = msg.split(' ');
  41. $('#online-users').empty();
  42. for (var i in users) {
  43. if (users[i]) {
  44. if (users[i] == myid) {
  45. $('#online-users').append($('<li>').append($('<a>').attr('href', '#').text(users[i] + ' is me')));
  46. }else {
  47. $('#online-users').append($('<li>').append($('<a>').attr('href', '#').text(users[i])));
  48. }
  49. }
  50. }
  51.  
  52. $('#online-users li a').click(function(){
  53. var target = $(this).text();
  54. if (myid != parseInt(target)) {
  55. var from = myid, to = target;
  56. talkto = to;
  57. myroom = from + '#' + to;
  58. }
  59. });
  60. });
  61.  
  62. // create room
  63. socket.on('talkwith', function(msg) {
  64. $('#room').text(msg);
  65. myroom = msg;
  66. });
  67.  
  68. socket.on('' + myid, function(data){
  69. $('#messages').append($('<li>').text(data['uesrname'] + ' --> ' + data['to'] + ' : ' + data['msg']));
  70. });
  71.  
  72. // private message
  73. $('form').submit(function(){
  74. //socket.emit('private', { 'room':myroom, 'msg': myid + ' says: ' + $('#m').val()});
  75. socket.emit('private', {'uesrname':myid, 'password':password, 'to':talkto, 'msg':$('#m').val(), 'date':new Date().Format("yyyy-MM-dd HH:mm:ss")});
  76. $('#m').val('');
  77. return false;
  78. });
  79.  
  80. socket.on('private', function(data){
  81. // switch to the new room
  82. myroom = data['room'];
  83. $('#room').text(myroom);
  84. $('#messages').append($('<li>').text(data['msg']));
  85. });
  86.  
  87. //格式化时间,来自网络
  88. Date.prototype.Format = function (fmt) { //author: meizz
  89. var o = {
  90. "M+": this.getMonth() + 1, //月份
  91. "d+": this.getDate(), //日
  92. "H+": this.getHours(), //小时
  93. "m+": this.getMinutes(), //分
  94. "s+": this.getSeconds(), //秒
  95. "q+": Math.floor((this.getMonth() + 3) / 3), //季度
  96. "S": this.getMilliseconds() //毫秒
  97. };
  98. if (/(y+)/.test(fmt)) fmt = fmt.replace(RegExp.$1, (this.getFullYear() + "").substr(4 - RegExp.$1.length));
  99. for (var k in o)
  100. if (new RegExp("(" + k + ")").test(fmt)) fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length)));
  101. return fmt;
  102. }
  103. </script>
  104.  
  105. </body>
  106. </html>

其实也是挺简单的,界面显示在线用户,点击在线用户名,则向该用户发送消息,消息内容包括自己的用户名username,接收者to,消息内容msg,时间date,这里使用了一个格式化的方法,也不难理解,注意这里监听的是发送给自己的消息,对其他消息则不处理不接收。

可以试验,网页客户端确实可以通行聊天了,那跟android相关的部分呢,主要有以下几个点需要注意:

1.app不在聊天窗口,关闭了程序,就完全不监听发过来的消息了吗?这不好,需要在后台监听,通知栏通知,这样的话,必然用到了service,在service中处理监听等,并可以把消息保存到本地数据库中,也好日后查看显示。

2.不同的人发送的消息应该有一个联系人的列表吧,就想qq一样,那就是对于接收到的消息,按照发送者分组,数据库查询就是group by了,时间降序排序,这里的在我的表中,id是根据时间递增的,我可以按照id降序就是时间的降序了,order by ** desc,好多联系人,ListView和Adapter是不可少的。

3.如果已经在聊天窗口中,要不要继续在通知栏中通知了,不需要了吧,要有一个标志。

4.点击一个列表的某一项,要显示详细聊天内容,这个在下一篇文章中讨论。

大致思路清楚了,看看代码吧:

  1. import android.content.Context;
  2. import android.database.sqlite.SQLiteDatabase;
  3. import android.database.sqlite.SQLiteOpenHelper;
  4. import android.util.Log;
  5.  
  6. /**
  7. * 聊天相关数据库操作
  8. * Created by RENYUZHUO on 2015/12/14.
  9. */
  10. public class SQLOperation extends SQLiteOpenHelper {
  11.  
  12. Context context;
  13. String name;
  14. SQLiteDatabase.CursorFactory factory;
  15. int version;
  16.  
  17. String sqlMessage = "create table message("
  18. + "id integer primary key autoincrement," + "fromwho text,"
  19. + "msg text," + "data text)";
  20.  
  21. public SQLOperation(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
  22. super(context, name, factory, version);
  23. this.context = context;
  24. this.name = name;
  25. this.factory = factory;
  26. this.version = version;
  27. }
  28.  
  29. @Override
  30. public void onCreate(SQLiteDatabase db) {
  31. db.execSQL(sqlMessage);
  32. Log.i("create sqlMessage", "success");
  33. }
  34.  
  35. @Override
  36. public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
  37. // switch (oldVersion){
  38. // case 1:{
  39. // db.execSQL(sqlMessage);
  40. // }
  41. // }
  42. }
  43. }
  1. //数据库初始化,开启服务
  2.  
  3. sqlOperation = new SQLOperation(this, "fanshop.db", null, 1);
  4. sqLiteDatabase = sqlOperation.getWritableDatabase();
  5.  
  6. Intent intent = new Intent(context, ChatService.class);
  7. startService(intent);
  1. import android.app.Notification;
  2. import android.app.NotificationManager;
  3. import android.app.PendingIntent;
  4. import android.app.Service;
  5. import android.content.ContentValues;
  6. import android.content.Context;
  7. import android.content.Intent;
  8. import android.database.sqlite.SQLiteDatabase;
  9. import android.os.Build;
  10. import android.os.IBinder;
  11. import android.util.Log;
  12.  
  13. import com.bafst.fanshop.FanShopApplication;
  14. import com.bafst.fanshop.R;
  15. import com.bafst.fanshop.model.Chat.Message;
  16. import com.bafst.fanshop.net.JsonUtils;
  17. import com.bafst.fanshop.util.Global;
  18. import com.github.nkzawa.emitter.Emitter;
  19. import com.github.nkzawa.socketio.client.IO;
  20. import com.github.nkzawa.socketio.client.Socket;
  21.  
  22. import java.net.URISyntaxException;
  23. import java.text.DateFormat;
  24. import java.text.ParseException;
  25. import java.text.SimpleDateFormat;
  26.  
  27. /**
  28.  
  29. * 监听消息,保存到数据库中,neededNotice和notice方法设置是否需要在通知栏中通知
  30.  
  31. */
  32.  
  33. public class ChatService extends Service {
  34.  
  35. Socket mSocket;
  36. Context context;
  37. /**
  38. * 身份信息
  39. */
  40. private String myname;
  41. private String str = "";
  42. private String textShow = "";
  43.  
  44. public static int num = 0;
  45.  
  46. public static boolean notice = true;
  47.  
  48. {
  49. try {
  50. mSocket = IO.socket(Global.CHAT_URL);
  51. } catch (URISyntaxException e) {
  52. }
  53. }
  54.  
  55. public ChatService() {
  56. Log.i("ChatService", "in ChatService");
  57. context = this;
  58. myname = getUserName();
  59.  
  60. mSocket.on("online", online);
  61. mSocket.on(myname, myMessage);
  62.  
  63. mSocket.connect();
  64. mSocket.emit("online", myname);
  65.  
  66. }
  67.  
  68. /**
  69. * 发送登陆信息
  70. */
  71. Emitter.Listener online = new Emitter.Listener() {
  72. @Override
  73. public void call(final Object... args) {
  74. Log.i("online", "in online");
  75. new Runnable() {
  76. @Override
  77. public void run() {
  78. Log.i("online.run", "in online.run");
  79. String msg = args[0].toString();
  80. String[] users = msg.split(" ");
  81. str = "";
  82. for (String user : users) {
  83. if (myname.equals(user)) {
  84. str += "my name:" + user + "\n";
  85. } else {
  86. str += user + "\n";
  87. }
  88. }
  89. textShow = str;
  90. Log.i("textShow", textShow);
  91. }
  92. }.run();
  93. }
  94. };
  95.  
  96. NotificationManager manager;
  97. Notification myNotication;
  98.  
  99. /**
  100. * 获取发给本用户的信息
  101. */
  102. Emitter.Listener myMessage = new Emitter.Listener() {
  103. @Override
  104. public void call(final Object... args) {
  105. new Runnable() {
  106. @Override
  107. public void run() {
  108. Log.i("myMessage", "in myMessage");
  109. str = "" + args[0];
  110. Message message = JsonUtils.fromJson(str, Message.class);
  111. textShow = message.toString();
  112. Log.i("message", textShow);
  113.  
  114. ContentValues values = new ContentValues();
  115. values.put("fromwho", message.getUesrname());
  116. values.put("msg", message.getMsg());
  117. // values.put("data", message.getDate().replace("-", "T").replace(" ", "U").replace(":", "V"));
  118. values.put("data", message.getDate());
  119. SQLiteDatabase sqliteDatabase = FanShopApplication.getSqLiteDatabase();
  120. sqliteDatabase.insert("message", null, values);
  121.  
  122. if (notice) {
  123. manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
  124. Intent intent = new Intent(context, ChatDetailActivity.class);
  125. PendingIntent pendingIntent = PendingIntent.getActivity(context, 1, intent, 0);
  126. Notification.Builder builder = new Notification.Builder(context);
  127. builder.setAutoCancel(true);
  128. builder.setTicker(message.getMsg());
  129. builder.setContentTitle(getResources().getString(R.string.app_name));
  130. builder.setContentText(message.getUesrname());
  131. builder.setSmallIcon(R.mipmap.ic_launcher);
  132. builder.setContentIntent(pendingIntent);
  133. builder.setOngoing(false);
  134. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
  135. builder.setSubText(message.getMsg());
  136. builder.build();
  137. }
  138. builder.setNumber(++num);
  139. myNotication = builder.getNotification();
  140. manager.notify(1, myNotication);
  141. }
  142. }
  143. }.run();
  144. }
  145. };
  146.  
  147. /**
  148. * 获取用户名或者是与用户名可以相互对应的唯一身份验证标识
  149. *
  150. * @return username
  151. */
  152. private String getUserName() {
  153. return String.valueOf((int) (Math.random() * 100));
  154.  
  155. }
  156.  
  157. @Override
  158. public IBinder onBind(Intent intent) {
  159. throw new UnsupportedOperationException("Not yet implemented");
  160. }
  161.  
  162. @Override
  163. public void onDestroy() {
  164. mSocket.off("online", online);
  165. mSocket.off(myname, online);
  166.  
  167. mSocket.close();
  168. super.onDestroy();
  169. }
  170.  
  171. public static void notice() {
  172. notice = true;
  173. }
  174.  
  175. public static void neededNotice() {
  176. notice = false;
  177. }
  178. }
  1. //从数据库中读取数据并通过适配器显示在界面上,没有考虑头像问题
  2.  
  3. private void dealTab1() {
  4. ChatService.neededNotice();
  5.  
  6. List<Message> messages = new ArrayList<Message>();
  7.  
  8. SQLiteDatabase sqliteDatabase = FanShopApplication.getSqLiteDatabase();
  9. Cursor result = sqliteDatabase.query("message", null, null, null, "fromwho", null, "id desc");
  10. Message message;
  11. while (result.moveToNext()) {
  12. message = new Message();
  13. message.setUesrname(result.getString(result.getColumnIndex("fromwho")) + "");
  14. message.setId(result.getInt(result.getColumnIndex("id")) + "");
  15. message.setDate(result.getString(result.getColumnIndex("data")) + "");
  16. message.setMsg(result.getString(result.getColumnIndex("msg")));
  17. messages.add(message);
  18. }
  19.  
  20. mesList = (ListView) findViewById(R.id.mesList);
  21. messageListAdapter = new MessageListAdapter(this, messages);
  22. mesList.setAdapter(messageListAdapter);
  23. }
  1. //列表中的每一个的的布局文件
  2.  
  3. <?xml version="1.0" encoding="utf-8"?>
  4. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  5. android:layout_width="match_parent"
  6. android:layout_height="wrap_content"
  7. android:orientation="vertical">
  8.  
  9. <RelativeLayout
  10. android:layout_width="wrap_content"
  11. android:layout_height="wrap_content">
  12.  
  13. <ImageView
  14. android:id="@+id/heap"
  15. android:layout_width="60sp"
  16. android:layout_height="60sp"
  17. android:src="@drawable/a" />
  18.  
  19. <ImageView
  20. android:layout_width="60sp"
  21. android:layout_height="60sp"
  22. android:src="@drawable/message_item_pit_top" />
  23.  
  24. <TextView
  25. android:id="@+id/username"
  26. android:layout_width="wrap_content"
  27. android:layout_height="wrap_content"
  28. android:layout_toRightOf="@id/heap"
  29. android:paddingLeft="@dimen/activity_horizontal_margin"
  30. android:paddingTop="@dimen/heap_top"
  31. android:textSize="20sp"
  32. android:text="ooo" />
  33.  
  34. <TextView
  35. android:id="@+id/msg"
  36. android:layout_width="wrap_content"
  37. android:layout_height="wrap_content"
  38. android:layout_below="@id/username"
  39. android:layout_toRightOf="@id/heap"
  40. android:paddingLeft="@dimen/activity_horizontal_margin"
  41. android:paddingTop="@dimen/msg_top"
  42. android:text="222" />
  43.  
  44. <TextView
  45. android:id="@+id/time"
  46. android:text="ddd"
  47. android:layout_alignParentEnd="true"
  48. android:layout_alignParentRight="true"
  49. android:paddingRight="@dimen/activity_horizontal_margin"
  50. android:paddingTop="@dimen/heap_top"
  51. android:layout_width="wrap_content"
  52. android:layout_height="wrap_content" />
  53. </RelativeLayout>
  54. </RelativeLayout>
  1. //列表整体布局
  2.  
  3. <?xml version="1.0" encoding="utf-8"?>
  4. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  5. android:orientation="vertical" android:layout_width="match_parent"
  6. android:layout_height="match_parent">
  7.  
  8. <ListView
  9. android:id="@+id/mesList"
  10. android:layout_width="match_parent"
  11. android:layout_height="wrap_content">
  12. </ListView>
  13.  
  14. </LinearLayout>

这些内容合理组织就可以了,可以实现手机与手机,手机与网页之间的通信,可以接收消息,保存到数据库中,列表显示不同发来消息的用户,就像QQ中的列表一样,这里没有考虑如何显示详细的聊天内容,这是因为要通过点击进入到详情中查看,在其他的activity中,其他的sql语句,因此,在下一篇文章中介绍。

【socket.io研究】3.手机网页间聊天核心问题的更多相关文章

  1. Node.js下基于Express + Socket.io 搭建一个基本的在线聊天室

    一.聊天室简单介绍 采用nodeJS设计,基于express框架,使用WebSocket编程之 socket.io机制.聊天室增加了 注册登录模块 ,并将用户个人信息和聊天记录存入数据库. 数据库采用 ...

  2. 使用socket.io+redis来实现基本的聊天室应用场景

    本文根据socket.io与Redis来实现基本的聊天室应用场景,主要表现于多个浏览器之间的信息同步和实时更新. 只是简单记录了一下, 更详细的内容可以参考后续的一篇补充文章: 使用node.js + ...

  3. 【socket.io研究】2.小试牛刀

    1.建立个项目,也就是文件夹,这里使用testsocket 2.创建文件package.json,用于描述项目: { "name":"testsocket", ...

  4. 【socket.io研究】1.官网的一些相关说明,概述

    socket.io是什么? 官网的解释是一个实时的,基于事件的通讯框架,可以再各个平台上运行,关注于效率和速度. 在javascript,ios,android,java中都实现了,可以很好的实现实时 ...

  5. 【socket.io研究】0.前提准备

    WebSocket出现之前,web实时推送,一般采用轮询和Comet技术(可细分为长轮询机制和流技术两种),需要大量http请求,服务器受不了.HTML5定义了WebSocket协议,基于TCP协议, ...

  6. Socket.IO聊天室~简单实用

    小编心语:大家过完圣诞准备迎元旦吧~小编在这里预祝大家元旦快乐!!这一次要分享的东西小编也不是很懂啊,总之小编把它拿出来是觉地比较稀奇,而且程序也没有那么难,是一个比较简单的程序,大家可以多多试试~ ...

  7. vue + socket.io实现一个简易聊天室

    vue + vuex + elementUi + socket.io实现一个简易的在线聊天室,提高自己在对vue系列在项目中应用的深度.因为学会一个库或者框架容易,但要结合项目使用一个库或框架就不是那 ...

  8. 使用node.js + socket.io + redis实现基本的聊天室场景

    在这篇文章Redis数据库及其基本操作中介绍了Redis及redis-cli的基本操作. 其中的publish-subscribe机制应用比较广泛, 那么接下来使用nodejs来实现该机制. 本文是对 ...

  9. node.js + socket.io实现聊天室一

    前段时间,公司打算在社区做一个聊天室.决定让我来做.本小白第一次做聊天类功能,当时还想着通过ajax请求来实现.经过经理提示,说试试当前流行的node.js 和socket.io来做.于是就上网学习研 ...

随机推荐

  1. 如果设置http.get超时控制

    var timeout_wrapper = function (req) { return function () { // do some logging, cleaning, etc. depen ...

  2. 自己寫的 Loading JS插件

    本文為原創文章,轉載請注明出處,謝謝./** * @author samkin.yang * @version 1.0 */var $_yxj = new SamkinLoading(); (func ...

  3. 新写PHP HTTP断点续传类文件代码

    一个支持断点续传的PHP文件下载类文件,调用方法简单,类代码简洁,可记忆上次的下载的节点,实现累积下载,类名称download,类代码如下: function download($path,$file ...

  4. ARC - strong和weak指针

    ARC指南1 - strong和weak指针   提示:本文中所说的"实例变量"即是"成员变量","局部变量"即是"本地变量&qu ...

  5. 在网页中获取 facebook page 的内容

    参考 : http://www.ibm.com/developerworks/cn/opensource/os-cn-facebookapi/ 1.首先你要有 facebook page, 内容要公开 ...

  6. 设置tableWidget->verticalScrollBar()的属性

    tableWidget->verticalScrollBar()->setStyleSheet("QScrollBar{background:rgb(255,255,0); wi ...

  7. HDOJ 1164 Eddy's research I(拆分成素数因子)

    Problem Description Eddy's interest is very extensive, recently he is interested in prime number. Ed ...

  8. Response.Expires 属性 (转载于疯狂客的BLOG)

    Expires 属性 Expires 属性指定了在浏览器上缓冲存储的页距过期还有多少时间.如果用户在某个页过期之前又回到此页,就会显示缓冲区中的版本 语法 Response.Expires [= nu ...

  9. HDU_2016——数据的交换输出

    Problem Description 输入n(n<100)个数,找出其中最小的数,将它与最前面的数交换后输出这些数.   Input 输入数据有多组,每组占一行,每行的开始是一个整数n,表示这 ...

  10. win10 pro eclipse maven: Cannot read lifecycle mapping metadata for artifact org.apache.maven.plugins:mav invalid END header (bad central directory offset)

    Error:Cannot read lifecycle mapping metadata for artifact org.apache.maven.plugins:mav ... invalid E ...