其实打造属于自己的聊天软件很简单,今天我们讲学习如何简单的写一个群组聊天app,通过sockets实现。这不是唯一的方法,但却是最快和最简单的。最好和最有效的方式应该是使用推送通知(push notifications )而不是sockets。
翻译自http://www.androidhive.info/2014/10/android-building-group-chat-app-using-sockets-part-1/
github 项目地址
译者注:原文只给了代码很少有说明,译者会根据自己的情况适当加一些说明,比较复杂的章节将会单独写博客来说明:比如说websockets。
*译者注:重要说明:为防止各种不确定的因素请使用以下开发环境:
- Tomcat7
- javaee-api7*
在这篇文章中我们会做三个东西。第一个也是最重要的就是sockett server。socket server扮演着重要的作用:处理socket客户端连接,传递数据。第二个东西就是web app,你可以通过浏览器加入聊天。最后就是android app。最大的有点就是你可以在web – web, web – android 或者 android – android 进行聊天。
这个系列的文章有点长,把它分为两部分。第一部分就是web篇,第二部分安卓篇。
最终我们的app是这个样子的:
究竟App是如何在sockets上工作的?
首先你可以看维基百科或者百度百科。
- 首先,我们有一个正在运行的socket server,当安卓app或者web app连接的时候,server打开了一个在服务端与客户端的TCP连接,党有多个用户的时候,serever有能力处理多个连接。
- 当socket连接的时候会出发一系列的回调函数,例如 onOpen, onMessage, onExit,在客户端和服务端都会触发。
- 服务端与客户端用json进行交互。每一个json都有一个叫flag的字段来确认json的目的。下面的例子是当一个客户加入到对甲中所发送的json
{
"message": " joined conversation!",
"flag": "new",
"sessionId": "4",
"name": "Ravi Tamada",
"onlineCount": 6
}
- 当收到json信息的时候客户端就会解析。
开始服务端
首先下载三个jar包,放到WEB-INF lib下
google-collection
创建一个叫JSONUtils的工具类,包含了众多创建json字符串的方法, 这额json在通信的时候被使用。
下面的代码,你看到每一个json都有一个flag节点,这个节点告诉客户端你的json字符串的目的,根据不同的目的采取不同的行动。
大体上flag有四种
- self:这个json里包含了session信息,指定了一个特定的客户。当建立起sockets连接的时候,这回事客户端收到的第一个json。
- new: 这个json在一个新客户加入socket server的时候向所有人广播。
- message:这个包含了客户端所发送的消息,也会广播给每个人。
- exit:当客户端退出连接的时候
package info.androidhive.webmobilegroupchat;
import org.json.JSONException;
import org.json.JSONObject;
public class JSONUtils {
// flags to identify the kind of json response on client side
private static final String FLAG_SELF = "self", FLAG_NEW = "new",
FLAG_MESSAGE = "message", FLAG_EXIT = "exit";
public JSONUtils() {
}
/**
* Json when client needs it's own session details
* */
public String getClientDetailsJson(String sessionId, String message) {
String json = null;
try {
JSONObject jObj = new JSONObject();
jObj.put("flag", FLAG_SELF);
jObj.put("sessionId", sessionId);
jObj.put("message", message);
json = jObj.toString();
} catch (JSONException e) {
e.printStackTrace();
}
return json;
}
/**
* Json to notify all the clients about new person joined
* */
public String getNewClientJson(String sessionId, String name,
String message, int onlineCount) {
String json = null;
try {
JSONObject jObj = new JSONObject();
jObj.put("flag", FLAG_NEW);
jObj.put("name", name);
jObj.put("sessionId", sessionId);
jObj.put("message", message);
jObj.put("onlineCount", onlineCount);
json = jObj.toString();
} catch (JSONException e) {
e.printStackTrace();
}
return json;
}
/**
* Json when the client exits the socket connection
* */
public String getClientExitJson(String sessionId, String name,
String message, int onlineCount) {
String json = null;
try {
JSONObject jObj = new JSONObject();
jObj.put("flag", FLAG_EXIT);
jObj.put("name", name);
jObj.put("sessionId", sessionId);
jObj.put("message", message);
jObj.put("onlineCount", onlineCount);
json = jObj.toString();
} catch (JSONException e) {
e.printStackTrace();
}
return json;
}
/**
* JSON when message needs to be sent to all the clients
* */
public String getSendAllMessageJson(String sessionId, String fromName,
String message) {
String json = null;
try {
JSONObject jObj = new JSONObject();
jObj.put("flag", FLAG_MESSAGE);
jObj.put("sessionId", sessionId);
jObj.put("name", fromName);
jObj.put("message", message);
json = jObj.toString();
} catch (JSONException e) {
e.printStackTrace();
}
return json;
}
}
创建SocketServer类
package info.androidhive.webmobilegroupchat;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import javax.websocket.OnClose;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;
import org.json.JSONException;
import org.json.JSONObject;
import com.google.common.collect.Maps;
@ServerEndpoint("/chat")
public class SocketServer {
// set to store all the live sessions
private static final Set<Session> sessions = Collections
.synchronizedSet(new HashSet<Session>());
// Mapping between session and person name
private static final HashMap<String, String> nameSessionPair = new HashMap<String, String>();
private JSONUtils jsonUtils = new JSONUtils();
// Getting query params
public static Map<String, String> getQueryMap(String query) {
Map<String, String> map = Maps.newHashMap();
if (query != null) {
String[] params = query.split("&");
for (String param : params) {
String[] nameval = param.split("=");
map.put(nameval[0], nameval[1]);
}
}
return map;
}
/**
* Called when a socket connection opened
* */
@OnOpen
public void onOpen(Session session) {
System.out.println(session.getId() + " has opened a connection");
Map<String, String> queryParams = getQueryMap(session.getQueryString());
String name = "";
if (queryParams.containsKey("name")) {
// Getting client name via query param
name = queryParams.get("name");
try {
name = URLDecoder.decode(name, "UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
// Mapping client name and session id
nameSessionPair.put(session.getId(), name);
}
// Adding session to session list
sessions.add(session);
try {
// Sending session id to the client that just connected
session.getBasicRemote().sendText(
jsonUtils.getClientDetailsJson(session.getId(),
"Your session details"));
} catch (IOException e) {
e.printStackTrace();
}
// Notifying all the clients about new person joined
sendMessageToAll(session.getId(), name, " joined conversation!", true,
false);
}
/**
* method called when new message received from any client
*
* @param message
* JSON message from client
* */
@OnMessage
public void onMessage(String message, Session session) {
System.out.println("Message from " + session.getId() + ": " + message);
String msg = null;
// Parsing the json and getting message
try {
JSONObject jObj = new JSONObject(message);
msg = jObj.getString("message");
} catch (JSONException e) {
e.printStackTrace();
}
// Sending the message to all clients
sendMessageToAll(session.getId(), nameSessionPair.get(session.getId()),
msg, false, false);
}
/**
* Method called when a connection is closed
* */
@OnClose
public void onClose(Session session) {
System.out.println("Session " + session.getId() + " has ended");
// Getting the client name that exited
String name = nameSessionPair.get(session.getId());
// removing the session from sessions list
sessions.remove(session);
// Notifying all the clients about person exit
sendMessageToAll(session.getId(), name, " left conversation!", false,
true);
}
/**
* Method to send message to all clients
*
* @param sessionId
* @param message
* message to be sent to clients
* @param isNewClient
* flag to identify that message is about new person joined
* @param isExit
* flag to identify that a person left the conversation
* */
private void sendMessageToAll(String sessionId, String name,
String message, boolean isNewClient, boolean isExit) {
// Looping through all the sessions and sending the message individually
for (Session s : sessions) {
String json = null;
// Checking if the message is about new client joined
if (isNewClient) {
json = jsonUtils.getNewClientJson(sessionId, name, message,
sessions.size());
} else if (isExit) {
// Checking if the person left the conversation
json = jsonUtils.getClientExitJson(sessionId, name, message,
sessions.size());
} else {
// Normal chat conversation message
json = jsonUtils
.getSendAllMessageJson(sessionId, name, message);
}
try {
System.out.println("Sending Message To: " + sessionId + ", "
+ json);
s.getBasicRemote().sendText(json);
} catch (IOException e) {
System.out.println("error in sending. " + s.getId() + ", "
+ e.getMessage());
e.printStackTrace();
}
}
}
创建web app(Html Css Js)
style.css
body {
padding: 0;
margin: 0;
}
.body_container {
width: 1000px;
margin: 0 auto;
padding: 0;
}
.clear {
clear: both;
}
.green {
color: #8aaf0d;
}
#header {
margin: 0 auto;
padding: 50px 0;
text-align: center;
}
#header h1,#header p.online_count {
font-family: 'Open Sans', sans-serif;
font-weight: 300;
}
#header p.online_count {
font-size: 18px;
display: none;
}
.box_shadow {
background: #f3f4f6;
border: 1px solid #e4e4e4;
-moz-box-shadow: 0px 0px 2px 1px #e5e5e5;
-webkit-box-shadow: 0px 0px 2px 1px #e5e5e5;
box-shadow: 0px 0px 2px 1px #e5e5e5;
}
#prompt_name_container {
margin: 0 auto;
width: 350px;
text-align: center;
}
#prompt_name_container p {
font-family: 'Open Sans', sans-serif;
font-weight: 300;
font-size: 24px;
color: #5e5e5e;
}
#prompt_name_container #input_name {
border: 1px solid #dddddd;
padding: 10px;
width: 250px;
display: block;
margin: 0 auto;
outline: none;
font-family: 'Open Sans', sans-serif;
}
#prompt_name_container #btn_join {
border: none;
width: 100px;
display: block;
outline: none;
font-family: 'Open Sans', sans-serif;
color: #fff;
background: #96be0e;
font-size: 18px;
padding: 5px 20px;
margin: 15px auto;
cursor: pointer;
}
#message_container {
display: none;
width: 500px;
margin: 0 auto;
background: #fff;
padding: 15px 0 0 0;
}
#messages {
margin: 0;
padding: 0;
height: 300px;
overflow: scroll;
overflow-x: hidden;
}
#messages li {
list-style: none;
font-family: 'Open Sans', sans-serif;
font-size: 16px;
padding: 10px 20px;
}
#messages li.new,#messages li.exit {
font-style: italic;
color: #bbbbbb;
}
#messages li span.name {
color: #8aaf0d;
}
#messages li span.red {
color: #e94e59;
}
#input_message_container {
margin: 40px 20px 0 20px;
}
#input_message {
background: #f0f0f0;
border: none;
font-size: 20px;
font-family: 'Open Sans', sans-serif;
outline: none;
padding: 10px;
float: left;
margin: 0;
width: 348px;
}
#btn_send {
float: left;
margin: 0;
border: none;
color: #fff;
font-family: 'Open Sans', sans-serif;
background: #96be0e;
outline: none;
padding: 10px 20px;
font-size: 20px;
cursor: pointer;
}
#btn_close {
margin: 0;
border: none;
color: #fff;
font-family: 'Open Sans', sans-serif;
background: #e94e59;
outline: none;
padding: 10px 20px;
font-size: 20px;
cursor: pointer;
width: 100%;
margin: 30px 0 0 0;
}
main.js
var sessionId = '';
// name of the client
var name = '';
// socket connection url and port
var socket_url = '192.168.0.102';
var port = '8080';
$(document).ready(function() {
$("#form_submit, #form_send_message").submit(function(e) {
e.preventDefault();
join();
});
});
var webSocket;
/**
* Connecting to socket
*/
function join() {
// Checking person name
if ($('#input_name').val().trim().length <= 0) {
alert('Enter your name');
} else {
name = $('#input_name').val().trim();
$('#prompt_name_container').fadeOut(1000, function() {
// opening socket connection
openSocket();
});
}
return false;
}
/**
* Will open the socket connection
*/
function openSocket() {
// Ensures only one connection is open at a time
if (webSocket !== undefined && webSocket.readyState !== WebSocket.CLOSED) {
return;
}
// Create a new instance of the websocket
webSocket = new WebSocket("ws://" + socket_url + ":" + port
+ "/WebMobileGroupChatServer/chat?name=" + name);
/**
* Binds functions to the listeners for the websocket.
*/
webSocket.onopen = function(event) {
$('#message_container').fadeIn();
if (event.data === undefined)
return;
};
webSocket.onmessage = function(event) {
// parsing the json data
parseMessage(event.data);
};
webSocket.onclose = function(event) {
alert('Error! Connection is closed. Try connecting again.');
};
}
/**
* Sending the chat message to server
*/
function send() {
var message = $('#input_message').val();
if (message.trim().length > 0) {
sendMessageToServer('message', message);
} else {
alert('Please enter message to send!');
}
}
/**
* Closing the socket connection
*/
function closeSocket() {
webSocket.close();
$('#message_container').fadeOut(600, function() {
$('#prompt_name_container').fadeIn();
// clearing the name and session id
sessionId = '';
name = '';
// clear the ul li messages
$('#messages').html('');
$('p.online_count').hide();
});
}
/**
* Parsing the json message. The type of message is identified by 'flag' node
* value flag can be self, new, message, exit
*/
function parseMessage(message) {
var jObj = $.parseJSON(message);
// if the flag is 'self' message contains the session id
if (jObj.flag == 'self') {
sessionId = jObj.sessionId;
} else if (jObj.flag == 'new') {
// if the flag is 'new', a client joined the chat room
var new_name = 'You';
// number of people online
var online_count = jObj.onlineCount;
$('p.online_count').html(
'Hello, <span class="green">' + name + '</span>. <b>'
+ online_count + '</b> people online right now')
.fadeIn();
if (jObj.sessionId != sessionId) {
new_name = jObj.name;
}
var li = '<li class="new"><span class="name">' + new_name + '</span> '
+ jObj.message + '</li>';
$('#messages').append(li);
$('#input_message').val('');
} else if (jObj.flag == 'message') {
// if the json flag is 'message', it means somebody sent the chat
// message
var from_name = 'You';
if (jObj.sessionId != sessionId) {
from_name = jObj.name;
}
var li = '<li><span class="name">' + from_name + '</span> '
+ jObj.message + '</li>';
// appending the chat message to list
appendChatMessage(li);
$('#input_message').val('');
} else if (jObj.flag == 'exit') {
// if the json flag is 'exit', it means somebody left the chat room
var li = '<li class="exit"><span class="name red">' + jObj.name
+ '</span> ' + jObj.message + '</li>';
var online_count = jObj.onlineCount;
$('p.online_count').html(
'Hello, <span class="green">' + name + '</span>. <b>'
+ online_count + '</b> people online right now');
appendChatMessage(li);
}
}
/**
* Appending the chat message to list
*/
function appendChatMessage(li) {
$('#messages').append(li);
// scrolling the list to bottom so that new message will be visible
$('#messages').scrollTop($('#messages').height());
}
/**
* Sending message to socket server message will be in json format
*/
function sendMessageToServer(flag, message) {
var json = '{""}';
// preparing json object
var myObject = new Object();
myObject.sessionId = sessionId;
myObject.message = message;
myObject.flag = flag;
// converting json object to json string
json = JSON.stringify(myObject);
// sending message to server
webSocket.send(json);
}
下载jquery-1.11.1.min
index.html
<!DOCTYPE html>
<html>
<head>
<title>Android, WebSockets Chat App | AndroidHive
(www.androidhive.info)</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width">
<script type="text/javascript" src="jquery-1.11.1.min.js"></script>
<link
href='http://fonts.googleapis.com/css?family=Open+Sans:400,600,300,700'
rel='stylesheet' type='text/css'>
<link href="style.css" type="text/css" rel='stylesheet' />
<script type="text/javascript" src="main.js"></script>
</head>
<body>
<div class="body_container">
<div id="header">
<h1>Android WebSockets Chat Application</h1>
<p class='online_count'>
<b>23</b> people online right now
</p>
</div>
<div id="prompt_name_container" class="box_shadow">
<p>Enter your name</p>
<form id="form_submit" method="post">
<input type="text" id="input_name" /> <input type="submit"
value="JOIN" id="btn_join">
</form>
</div>
<div id="message_container" class="box_shadow">
<ul id="messages">
</ul>
<div id="input_message_container">
<form id="form_send_message" method="post" action="#">
<input type="text" id="input_message"
placeholder="Type your message here..." /> <input type="submit"
id="btn_send" onclick="send();" value="Send" />
<div class="clear"></div>
</form>
</div>
<div>
<input type="button" onclick="closeSocket();"
value="Leave Chat Room" id="btn_close" />
</div>
</div>
</div>
</body>
</html>
请把main.js中的ip填自己的
var socket_url = '_YOUR_IP_ADDRESS_';
安卓版的请看下一篇