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

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

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,当然,其中还有其他消息,包括时间等,具体情况具体分析,服务器在接收到消息后,打印日志,将消息发送给接受者和发送者,为什么需要发送给发送者,因为这样发送者在接收到服务器的返回消息时可以确定服务器一定是接收到消息了。

那客户端什么样的呢?

<!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,这里使用了一个格式化的方法,也不难理解,注意这里监听的是发送给自己的消息,对其他消息则不处理不接收。

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

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

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

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

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语句,因此,在下一篇文章中介绍。

【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. MapReduce UnitTest

    通常情况下,我们需要用小数据集来单元测试我们写好的map函数和reduce函数.而一般我们可以使用Mockito框架来模拟OutputCollector对象(Hadoop版本号小于0.20.0)和Co ...

  2. PE文件结构整理

    一直想做一个PE结构的总结,只是学的时候有很多东西就没搞懂,加上时间一长,很多知识也早忘了,也就一直没完成.这几天从头看了下,好不容易理清楚了,整理一下,以免又忘了 pe文件框架结构,图片贴过来太模糊 ...

  3. Filter及FilterChain的使用详解

    原文地址:http://blog.csdn.net/zhaozheng7758/article/details/6105749 一.Filter的介绍及使用 什么是过滤器? 与Servlet相似,过滤 ...

  4. Django新手图文教程

    Django新手图文教程 本文面向:有python基础,刚接触web框架的初学者. 环境:windows7   python3.5.1  pycharm专业版  Django 1.10版 pip3 一 ...

  5. Windows10 删除已经保存的WIFI热点

    自己的笔记本很多时候都是连接WIFI上网,导致保存的WIFI越来越多,有些都过期不能用了,但还是在列表中存在着,致使列表很长很难看,如下: 删除无用热点的方法如下: win+r运行cmd,进入命令行界 ...

  6. 热门usb无线网卡

    拓实 N910 N95 N82 N81 N89 都是3070的 拓实 N87 G618 是8187的硬功夫 216 310 217 218 300 315 335 350 370 380 510 53 ...

  7. Entity Framework with MySQL 学习笔记一(查询)

    参考 : http://msdn.microsoft.com/en-us/data/jj574232.aspx EF 查询基本上有3中 默认是 Lazy Loading 特色是只有在需要数据的时候EF ...

  8. BZOJ3391: [Usaco2004 Dec]Tree Cutting网络破坏

    3391: [Usaco2004 Dec]Tree Cutting网络破坏 Time Limit: 1 Sec  Memory Limit: 128 MBSubmit: 47  Solved: 37[ ...

  9. 安卓,分享到facebook的若干种方法汇总

    近期做了facebook的分享功能,遇到了很多问题,这里总结一下,供大家参考,不足之处还请大家指正. facebook分享方式: 1.通过intent调用调用本地facebook应用方式 支持单独分享 ...

  10. 带’*’号字符串的匹配

    目标: 判断源字符串中是否含有指定子串,子串可能会有*号通配符. 初步测试没问题.记录下来.后面要是有问题再来纠正. #include <string> using namespace s ...