【socket.io研究】3.手机网页间聊天核心问题
前面我们已经说了服务器相关的一些内容,且又根据官网给出的一个例子写了一个可以聊天的小程序,但是这还远远不够呀,这只能算是应用前的准备工作。接下来,一起来考虑完善一个小的聊天程序吧。
首先,修改服务器的代码以前就是单纯的接收转发,现在我们考虑定向转发,及这个消息发送给需要接收的接受者,而不是全部客户端用户,并且考虑不使用默认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.手机网页间聊天核心问题的更多相关文章
- Node.js下基于Express + Socket.io 搭建一个基本的在线聊天室
一.聊天室简单介绍 采用nodeJS设计,基于express框架,使用WebSocket编程之 socket.io机制.聊天室增加了 注册登录模块 ,并将用户个人信息和聊天记录存入数据库. 数据库采用 ...
- 使用socket.io+redis来实现基本的聊天室应用场景
本文根据socket.io与Redis来实现基本的聊天室应用场景,主要表现于多个浏览器之间的信息同步和实时更新. 只是简单记录了一下, 更详细的内容可以参考后续的一篇补充文章: 使用node.js + ...
- 【socket.io研究】2.小试牛刀
1.建立个项目,也就是文件夹,这里使用testsocket 2.创建文件package.json,用于描述项目: { "name":"testsocket", ...
- 【socket.io研究】1.官网的一些相关说明,概述
socket.io是什么? 官网的解释是一个实时的,基于事件的通讯框架,可以再各个平台上运行,关注于效率和速度. 在javascript,ios,android,java中都实现了,可以很好的实现实时 ...
- 【socket.io研究】0.前提准备
WebSocket出现之前,web实时推送,一般采用轮询和Comet技术(可细分为长轮询机制和流技术两种),需要大量http请求,服务器受不了.HTML5定义了WebSocket协议,基于TCP协议, ...
- Socket.IO聊天室~简单实用
小编心语:大家过完圣诞准备迎元旦吧~小编在这里预祝大家元旦快乐!!这一次要分享的东西小编也不是很懂啊,总之小编把它拿出来是觉地比较稀奇,而且程序也没有那么难,是一个比较简单的程序,大家可以多多试试~ ...
- vue + socket.io实现一个简易聊天室
vue + vuex + elementUi + socket.io实现一个简易的在线聊天室,提高自己在对vue系列在项目中应用的深度.因为学会一个库或者框架容易,但要结合项目使用一个库或框架就不是那 ...
- 使用node.js + socket.io + redis实现基本的聊天室场景
在这篇文章Redis数据库及其基本操作中介绍了Redis及redis-cli的基本操作. 其中的publish-subscribe机制应用比较广泛, 那么接下来使用nodejs来实现该机制. 本文是对 ...
- node.js + socket.io实现聊天室一
前段时间,公司打算在社区做一个聊天室.决定让我来做.本小白第一次做聊天类功能,当时还想着通过ajax请求来实现.经过经理提示,说试试当前流行的node.js 和socket.io来做.于是就上网学习研 ...
随机推荐
- Debian/Ubuntu手动编译安装MongoDB C++11驱动及驱动测试
本文章仅限cnblogs网站内转载!请某网站自觉,遵纪守法,尊重原创! 系统环境情况: 最小化.无桌面环境 新安装的Debian 8 Server 版本操作系统虚拟机一台 手动编译安装MongoDB ...
- linux笔记2.20
用户相关: /etc/passwd 用户信息 /etc/shadow 密码信息 /etc/group 组信息 添加用户: useradd -u -g 修改用户: usermod - ...
- 学习笔记之AJAX无刷新分页
利用AJAX实现无刷新分页技术原理: 其主要是利用AJAX的异步处理机制,实现数据的异步传递,它隐藏了客户端向服务端请求数据的状态,在客户端表现为无刷新的显示状态. 实现分页的步骤: 1.客服端点击页 ...
- Dapper试用简例
1.选择3.5以上框架在新建项目中引用Dapper.dll. 2.在后台写代码,代码写出来后感觉以前学的都白学了. 3. using Dapper; using System; using Syste ...
- 数据分页SQL语句的比较
建立表 CREATE TABLE [TestTable] ( , ) NOT NULL , ) COLLATE Chinese_PRC_CI_AS NULL , ) COLLATE Chinese_P ...
- JavaScript常用
打印日志 console.log 类型判断 第一种方式var type = Object.prototype.toString.call(list);console.log(type);第二种方式ty ...
- Entity Framework with MySQL 学习笔记一(验证标签)
直接上代码 [Table("single_table")] public class SingleTable { [Key] public Int32 id { get; set; ...
- 技巧:Linux 动态库与静态库制作及使用详解
技巧:Linux 动态库与静态库制作及使用详解 标准库的三种连接方式及静态库制作与使用方法 Linux 应用开发通常要考虑三个问题,即:1)在 Linux 应用程序开发过程中遇到过标准库链接在不同 L ...
- 【转】C语言文件操作解析(三)
原文网址:http://www.cnblogs.com/dolphin0520/archive/2011/10/07/2200454.html C语言文件操作解析(三) 在前面已经讨论了文件打开操作, ...
- cocos2d-x 头文件中添加方法变量导致编译报错
代码如下: HelloWorldScene.h #ifndef __HELLOWORLD_SCENE_H__#define __HELLOWORLD_SCENE_H__ #include " ...