内容摘要
1.程序架构
2.通信协议
3.服务器源代码
4.客户端源代码
5.运行效果
一、程序架构
在开发一个聊天室程序时,我们可以使用Socket、Remoting、WCF这些具有双向通信的协议或框架。而现在,我正要实现一个C#语言作为服务器端、Android作为客户端的聊天室。由于服务器端和客户端不是同一语言(C#和java),所有我选择了Socket作为通信协议。
图1.1所示,我们可以看出:android手机客户端A向服务器端发送消息,服务器端收到消息后,又把消息推送到android手机客户端B。
图1.1
二、通信协议
我们知道,在C#语言中使用Socket技术需要“四部曲”,即“Bind”,“Listen”,“Accept”,“Receive”。然而Socket编程不像WCF那样面向对象。而且对应每个请求都用同一种方式处理。作为习惯面向对象编程的我来说,编写一个传统的Socket程序很不爽。绞尽脑汁,我们将数据传输的格式改为json(JavaScript Object Notation 是一种轻量级的数据交换格式),面对对象的问题就解决了。
假设程序的服务契约有两个方法:“登陆”和“发送消息”。调用登陆的方法,就传送方法名(Method Name)为“Logon”的json数据;调用发送消息的方法,就传送方法名为“Send”的json数据。返回的数据中也使用json格式,这样在android客户端中也能知道是哪个方法的返回值了。
三、服务器源代码
首先需要编写一个处理客户端消息的接口:IResponseManager。
{
void Write(Socket sender, IList<Socket> cliens, IDictionary<string, object> param);
}
其次,我们知道,换了是WCF编程的话,就需要在服务契约中写两个方法:“登陆”和“发送消息”。由于这里是Socket编程,我们实现之前写的IResponseManager接口,一个实现作为“登陆”的方法,另一个实现作为“发送消息”的方法。
{
public void Write(System.Net.Sockets.Socket sender, IList<System.Net.Sockets.Socket> cliens, IDictionary<string, object> param)
{
Console.WriteLine("客户端({0})登陆", sender.Handle);
var response = new SocketResponse
{
Method = "Logon",
DateTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"),
Result = new { UserName = param["UserName"].ToString() }
};
JavaScriptSerializer jss = new JavaScriptSerializer();
string context = jss.Serialize(response);
Console.WriteLine("登陆发送的数据为:{0}", context);
sender.Send(Encoding.UTF8.GetBytes(context + "\n"));
}
}
{
public void Write(System.Net.Sockets.Socket sender, IList<System.Net.Sockets.Socket> cliens, IDictionary<string, object> param)
{
Console.WriteLine("客户端({0})发送消息", sender.Handle);
var msgList = param["Message"] as IEnumerable<object>;
if (msgList == null)
{
return;
}
var response = new SocketResponse
{
Method = "Send",
DateTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"),
Result = new
{
UserName = param["UserName"].ToString(),
Message = msgList.Select(s => s.ToString()).ToArray()
}
};
JavaScriptSerializer jss = new JavaScriptSerializer();
string context = jss.Serialize(response);
Console.WriteLine("消息发送的数据为:{0}", context);
Parallel.ForEach(cliens, (item) =>
{
try
{
item.Send(Encoding.UTF8.GetBytes(context + "\n"));
}
catch { };
});
}
}
最后在Socket程序中使用反射加“策略模式”调用这两个接口实现类。
Console.WriteLine("反射类名为:" + typeName);
Type type = Type.GetType(typeName);
if (type == null)
{
return;
}
var manager = Activator.CreateInstance(type) as IResponseManager;
manager.Write(sender, this.socketClientSesson.Select(s => s.Key).ToList(),
request.Param as IDictionary<string, object>);
完整的Socket服务器代码如下:
{
private IDictionary<Socket, byte[]> socketClientSesson = new Dictionary<Socket, byte[]>();
public int Port { get; set; }
public void Start()
{
var socketThread = new Thread(() =>
{
Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
IPEndPoint iep = new IPEndPoint(IPAddress.Any, this.Port);
//绑定到通道上
socket.Bind(iep);
//侦听
socket.Listen(6);
//通过异步来处理
socket.BeginAccept(new AsyncCallback(Accept), socket);
});
socketThread.Start();
Console.WriteLine("服务器已启动");
}
private void Accept(IAsyncResult ia)
{
Socket socket = ia.AsyncState as Socket;
var client = socket.EndAccept(ia);
socket.BeginAccept(new AsyncCallback(Accept), socket);
byte[] buf = new byte[1024];
this.socketClientSesson.Add(client, buf);
try
{
client.BeginReceive(buf, 0, buf.Length, SocketFlags.None, new AsyncCallback(Receive), client);
string sessionId = client.Handle.ToString();
Console.WriteLine("客户端({0})已连接", sessionId);
}
catch (Exception ex)
{
Console.WriteLine("监听请求时出错:\r\n" + ex.ToString());
}
}
private void Receive(IAsyncResult ia)
{
var client = ia.AsyncState as Socket;
if (client == null || !this.socketClientSesson.ContainsKey(client))
{
return;
}
int count = client.EndReceive(ia);
byte[] buf = this.socketClientSesson[client];
if (count > 0)
{
try
{
client.BeginReceive(buf, 0, buf.Length, SocketFlags.None, new AsyncCallback(Receive), client);
string context = Encoding.UTF8.GetString(buf, 0, count);
Console.WriteLine("接收的数据为:", context);
this.Response(client, context);
}
catch (Exception ex)
{
Console.WriteLine("接收的数据出错:\r\n{0}", ex.ToString());
}
}
else
{
try
{
string sessionId = client.Handle.ToString();
client.Disconnect(true);
this.socketClientSesson.Remove(client);
Console.WriteLine("客户端({0})已断开", sessionId);
}
catch (Exception ex)
{
Console.WriteLine("客户端已断开出错" + ex.ToString());
}
}
}
private void Response(Socket sender, string context)
{
SocketRequest request = null;
JavaScriptSerializer jss = new JavaScriptSerializer();
request = jss.Deserialize(context, typeof(SocketRequest)) as SocketRequest;
if (request == null)
{
return;
}
var typeName = "SocketServer." + request.Method + "ResponseManager, SocketServer";
Console.WriteLine("反射类名为:" + typeName);
Type type = Type.GetType(typeName);
if (type == null)
{
return;
}
var manager = Activator.CreateInstance(type) as IResponseManager;
manager.Write(sender, this.socketClientSesson.Select(s => s.Key).ToList(),
request.Param as IDictionary<string, object>);
}
}
最后,json数据传输的实体对象为:
public class SocketRequest
{
public string Method { get; set; }
public string DateTime { get; set; }
public object Param { get; set; }
}
public class SocketResponse
{
public string Method { get; set; }
public string DateTime { get; set; }
public object Result { get; set; }
}
四、客户端源代码
1.布局文件
logon.xml:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="fill_parent"
android:layout_height="fill_parent" android:background="@drawable/background">
<LinearLayout android:orientation="vertical"
android:layout_width="fill_parent" android:layout_height="60dip"
android:background="@drawable/logon" />
<LinearLayout android:orientation="vertical"
android:layout_width="fill_parent" android:layout_height="fill_parent"
android:paddingLeft="10dp" android:paddingRight="10dp">
<View android:layout_width="fill_parent" android:layout_height="20dip" />
<TextView android:id="@+id/feedback_title" android:textColor="#FFFFFF"
android:layout_width="fill_parent" android:layout_height="wrap_content"
android:text="用户名:" />
<EditText android:id="@+id/edtUserName" android:layout_width="fill_parent"
android:layout_height="wrap_content" />
<View android:layout_width="fill_parent" android:layout_height="2dip"
android:background="#FF909090" />
<TextView android:layout_width="fill_parent"
android:textColor="#FFFFFF" android:layout_height="wrap_content"
android:text="IP地址:" />
<EditText android:id="@+id/edtIp" android:layout_width="fill_parent"
android:layout_height="wrap_content" android:digits="1234567890."
android:text="192.168.1.101"/>
<View android:layout_width="fill_parent" android:layout_height="2dip"
android:background="#FF909090" />
<TextView android:layout_width="fill_parent"
android:textColor="#FFFFFF" android:layout_height="wrap_content"
android:text="端口号:" />
<EditText android:id="@+id/edtPort" android:layout_width="fill_parent"
android:layout_height="wrap_content" android:inputType="number"
android:numeric="integer" android:text="1234"/>
<LinearLayout android:orientation="horizontal"
android:layout_width="fill_parent" android:layout_height="wrap_content"
android:layout_marginTop="10dp">
</LinearLayout>
<RelativeLayout android:layout_width="fill_parent"
android:layout_height="fill_parent">
<View android:id="@+id/feedback_content" android:layout_width="fill_parent"
android:layout_height="fill_parent" android:maxEms="10"
android:minEms="10" android:gravity="top"
android:layout_marginBottom="50dip" />
<Button android:id="@+id/btnLogon" android:layout_width="fill_parent"
android:layout_height="50dp" android:text="登陆" android:textSize="19dp"
android:layout_gravity="center_horizontal"
android:layout_alignParentBottom="true" />
</RelativeLayout>
</LinearLayout>
</LinearLayout>
main.xml:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="fill_parent"
android:layout_height="fill_parent" android:background="@drawable/background">
<ListView android:layout_width="fill_parent"
android:layout_height="wrap_content" android:id="@+id/ltvMessage">
</ListView>
<RelativeLayout android:layout_width="fill_parent"
android:layout_height="wrap_content">
<EditText android:layout_width="fill_parent"
android:layout_height="wrap_content" android:id="@+id/edtMessage"
android:hint="请输入消息" android:layout_alignTop="@+id/btnSend"
android:layout_toLeftOf="@+id/btnSend" />
<Button android:text="SEND" android:id="@+id/btnSend"
android:layout_height="wrap_content" android:layout_width="wrap_content"
android:layout_alignParentRight="true" />
</RelativeLayout>
</LinearLayout>
listview_item.xml:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent" android:layout_height="wrap_content"
android:orientation="vertical" android:paddingBottom="3dip"
android:paddingLeft="10dip">
<TextView android:layout_height="wrap_content"
android:layout_width="fill_parent" android:id="@+id/itmMessage"
android:textSize="20dip">
</TextView>
<LinearLayout android:layout_width="fill_parent"
android:orientation="horizontal" android:layout_height="20dip">
<TextView android:layout_height="fill_parent"
android:layout_width="100dip" android:id="@+id/itmUserName" >
</TextView>
<TextView android:layout_height="fill_parent"
android:layout_width="200dip" android:id="@+id/itmTime" >
</TextView>
</LinearLayout>
</LinearLayout>
SocketClient:
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.Socket;
import java.net.UnknownHostException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
public class SocketClient {
private static Socket client;
private static SocketClient instance = null;
public static SocketClient getInstance() {
if (instance == null) {
synchronized (ChartInfo.class) {
if (instance == null) {
try {
ChartInfo chartInfo = ChartInfo.getInstance();
client = new Socket(chartInfo.getIp(), chartInfo
.getPort());
instance = new SocketClient();
} catch (UnknownHostException e) {
// TODO Auto-generated catch block
} catch (IOException e) {
// TODO Auto-generated catch block
}
}
}
}
return instance;
}
private SocketClient() {
this.initMap();
this.startThread();
}
private void initMap() {
this.handlerMap = new HashMap<String, Handler>();
}
public void close() {
try {
client.close();
} catch (IOException e) {
// TODO Auto-generated catch block
//e.printStackTrace();
}
instance = null;
}
private void startThread() {
Thread thread = new Thread() {
@Override
public void run() {
while (true) {
if (client == null || !client.isConnected()) {
continue;
}
BufferedReader reader;
try {
reader = new BufferedReader(new InputStreamReader(
client.getInputStream()));
String line = reader.readLine();
Log.d("initSocket", "line:" + line);
if (line.equals("")) {
continue;
}
JSONObject json = new JSONObject(line);
String method = json.getString("Method");
Log.d("initSocket", "method:" + method);
if (method.equals("")
|| !handlerMap.containsKey(method)) {
Log.d("initSocket", "handlerMap not method");
continue;
}
Handler handler = handlerMap.get(method);
if (handler == null) {
Log.d("initSocket", "handler is null");
continue;
}
Log.d("initSocket", "handler:" + method);
Object obj = json.getJSONObject("Result");
Log.d("initSocket", "Result:" + obj);
Message msg = new Message();
msg.obj = obj;
handler.sendMessage(msg);
} catch (IOException e) {
} catch (JSONException e) {
}
}
}
};
thread.start();
}
private Map<String, Handler> handlerMap;
public void putHandler(String methodnName, Handler handler) {
this.removeHandler(methodnName);
this.handlerMap.put(methodnName, handler);
}
public void removeHandler(String methodnName) {
if (this.handlerMap.containsKey(methodnName)) {
this.handlerMap.remove(methodnName);
}
}
public void logon(String userName) {
Log.d("initSocket", "logon");
try {
OutputStreamWriter osw = new OutputStreamWriter(client
.getOutputStream());
BufferedWriter writer = new BufferedWriter(osw);
JSONObject param = new JSONObject();
param.put("UserName", userName.replace("\n", " "));
JSONObject json = this.getJSONData("Logon", param);
writer.write(json.toString());
writer.flush();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (JSONException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public void sendMessage(String message) {
Log.d("initSocket", "Send");
try {
OutputStreamWriter osw = new OutputStreamWriter(client
.getOutputStream());
BufferedWriter writer = new BufferedWriter(osw);
JSONArray array = new JSONArray();
for (String item : message.split("\n")) {
array.put(item);
}
JSONObject param = new JSONObject();
param.put("Message", array);
param.put("UserName", ChartInfo.getInstance().getUserName());
JSONObject json = this.getJSONData("Send", param);
writer.write(json.toString());
writer.flush();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (JSONException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
private JSONObject getJSONData(String methodName, JSONObject param) {
JSONObject json = new JSONObject();
try {
json.put("Method", methodName);
SimpleDateFormat format = new SimpleDateFormat(
"yyyy-MM-dd HH:mm:ss");
json.put("DateTime", format.format(new Date()));
json.put("Param", param);
return json;
} catch (JSONException e) {
return null;
}
}
}
LogonActivity:
import org.json.JSONException;
import org.json.JSONObject;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.ComponentName;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
public class LogonActivity extends Activity {
private EditText edtUserName;
private EditText edtIp;
private EditText edtPort;
private Button btnLogon;
@Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
setContentView(R.layout.logon);
this.initViews();
}
private void initViews() {
this.edtUserName = (EditText) this.findViewById(R.id.edtUserName);
this.edtIp = (EditText) this.findViewById(R.id.edtIp);
this.edtPort = (EditText) this.findViewById(R.id.edtPort);
this.btnLogon = (Button) this.findViewById(R.id.btnLogon);
this.btnLogon.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
// showAlert(edtUserName.getText().toString());
if (edtUserName.getText().toString().equals("")) {
showDialog("请输入用户名");
return;
}
if (edtIp.getText().toString().equals("")) {
showDialog("请输入IP地址");
return;
}
if (edtPort.getText().toString().equals("")) {
showDialog("请输入端口号");
return;
}
int port = Integer.parseInt(edtPort.getText().toString());
ChartInfo chartInfo = ChartInfo.getInstance();
chartInfo.setIp(edtIp.getText().toString());
chartInfo.setPort(port);
SocketClient proxy = SocketClient.getInstance();
if (proxy == null) {
showDialog("未接入互联网");
setWireless();
return;
}
proxy.putHandler("Logon", new Handler() {
@Override
public void handleMessage(Message msg) {
SocketClient proxy = SocketClient.getInstance();
proxy.removeHandler("Logon");
Log.d("initSocket", "handleMessage");
if (msg == null || msg.obj == null) {
return;
}
JSONObject json = (JSONObject) msg.obj;
try {
String userName = json.getString("UserName");
Log.d("initSocket", "userName:" + userName);
ChartInfo.getInstance().setUserName(userName);
Intent itt = new Intent();
itt
.setClass(LogonActivity.this,
MainActivity.class);
LogonActivity.this.startActivity(itt);
} catch (JSONException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
});
proxy.logon(edtUserName.getText().toString());
}
});
}
private void setWireless() {
Intent mIntent = new Intent("/");
ComponentName comp = new ComponentName("com.android.settings",
"com.android.settings.WirelessSettings");
mIntent.setComponent(comp);
mIntent.setAction("android.intent.action.VIEW");
startActivityForResult(mIntent, 0);
}
private void showDialog(String mess) {
new AlertDialog.Builder(this).setTitle("信息").setMessage(mess)
.setNegativeButton("确定", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
}
}).show();
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK && event.getRepeatCount() == 0) {
AlertDialog alertDialog = new AlertDialog.Builder(
LogonActivity.this).setTitle("退出程序").setMessage("是否退出程序")
.setPositiveButton("确定",
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog,
int which) {
LogonActivity.this.finish();
}
}).setNegativeButton("取消",
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
return;
}
}).create(); // 创建对话框
alertDialog.show(); // 显示对话框
return false;
}
return false;
}
@Override
protected void onDestroy() {
// TODO Auto-generated method stub
super.onDestroy();
SocketClient proxy = SocketClient.getInstance();
if (proxy != null) {
proxy.close();
}
}
}
MainActivity:
import org.json.JSONException;
import org.json.JSONObject;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.view.WindowManager;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ListView;
public class MainActivity extends Activity {
private EditText edtMessage;
private Button btnSend;
private ListView ltvMessage;
private MessageAdapter adapter;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// 隐藏键盘
this.getWindow().setSoftInputMode(
WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
Log.d("initSocket", "MessageAdapter");
this.adapter = new MessageAdapter(this);
Log.d("initSocket", "adapter is ok");
this.findThisViews();
this.initHandler();
this.serOnClick();
Log.d("initSocket", "onCreate");
}
private void findThisViews() {
this.edtMessage = (EditText) this.findViewById(R.id.edtMessage);
this.btnSend = (Button) this.findViewById(R.id.btnSend);
this.ltvMessage = (ListView) this.findViewById(R.id.ltvMessage);
// this.ltvMessage.setEnabled(false);
this.ltvMessage.setAdapter(this.adapter);
}
private void initHandler() {
Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
if (msg.obj == null) {
Log.d("initSocket", "handleMessage is null");
return;
}
Log.d("initSocket", "handleMessage");
try {
JSONObject json = (JSONObject) msg.obj;
String userName = json.getString("UserName");
StringBuilder sb = new StringBuilder();
int length = json.getJSONArray("Message").length();
for (int i = 0; i < length; i++) {
String item = json.getJSONArray("Message").getString(i);
if (item.equals("")) {
continue;
}
if (length > i + 1) {
Log.d("initSocket", "length:" + length);
Log.d("initSocket", "i:" + i);
Log.d("initSocket", "item:" + item);
item += "\n";
}
sb.append(item);
}
MessageRecord record = new MessageRecord();
record.setUserName(userName);
record.setMessage(sb.toString());
MainActivity.this.adapter.add(record);
adapter.notifyDataSetChanged();
} catch (JSONException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
};
SocketClient proxy = SocketClient.getInstance();
proxy.putHandler("Send", handler);
}
private void serOnClick() {
this.btnSend.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
btnSend.setEnabled(false);
String txt = edtMessage.getText().toString();
if (txt.equals("")) {
btnSend.setEnabled(true);
return;
}
SocketClient proxy = SocketClient.getInstance();
proxy.sendMessage(txt);
edtMessage.setText("");
btnSend.setEnabled(true);
}
});
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK && event.getRepeatCount() == 0) {
AlertDialog alertDialog = new AlertDialog.Builder(
MainActivity.this).setTitle("询问").setMessage("是否注销登录?")
.setPositiveButton("确定",
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog,
int which) {
MainActivity.this.finish();
}
}).setNegativeButton("取消",
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
return;
}
}).create(); // 创建对话框
alertDialog.show(); // 显示对话框
return false;
}
return false;
}
}
五、运行效果
代码下载
出处:http://www.cnblogs.com/GoodHelper/archive/2011/07/08/android_socket_chart.html
欢迎转载,但需保留版权!