您的位置:首页 > 移动开发 > Android开发

以C#编写的Socket服务器的Android手机聊天室Demo

2014-10-16 10:19 549 查看
内容摘要

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。



public interface IResponseManager

{

void Write(Socket sender, IList<Socket> cliens, IDictionary<string, object> param);

}





其次,我们知道,换了是WCF编程的话,就需要在服务契约中写两个方法:“登陆”和“发送消息”。由于这里是Socket编程,我们实现之前写的IResponseManager接口,一个实现作为“登陆”的方法,另一个实现作为“发送消息”的方法。





public class LogonResponseManager : 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 class SendResponseManager : 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 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程序中使用反射加“策略模式”调用这两个接口实现类。





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>);





完整的Socket服务器代码如下:





public class SocketHost

{

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数据传输的实体对象为:





[Serializable]

public class SocketRequest

{

public string Method { get; set; }

public string DateTime { get; set; }

public object Param { get; set; }

}









[Serializable]

public class SocketResponse

{

public string Method { get; set; }

public string DateTime { get; set; }

public object Result { get; set; }

}





四、客户端源代码

1.布局文件

logon.xml:





<?xml version="1.0" encoding="utf-8"?>

<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:



<?xml version="1.0" encoding="utf-8"?>

<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:





<?xml version="1.0" encoding="utf-8"?>

<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:





package ld.socket;

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:





package ld.socket;

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:





package ld.socket;

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

欢迎转载,但需保留版权!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐