首页 > 代码库 > 以C#编写的Socket服务器的Android手机聊天室Demo
以C#编写的Socket服务器的Android手机聊天室Demo
内容摘要
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 ;
}
}
五、运行效果
代码下载