内容摘要
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
欢迎转载,但需保留版权!
转载于:https://www.cnblogs.com/liuliunumberone/archive/2012/01/11/2319886.html
相关资源:各显卡算力对照表!