綁定帳號登入

Android 台灣中文網

打印 上一主題 下一主題

[資料] 以C#編寫的Socket服務器的Android手機聊天室Demo

[複製連結] 查看: 4857|回覆: 10|好評: 1
跳轉到指定樓層
樓主
暗桌之光 | 收聽TA | 只看該作者 回帖獎勵 |倒序瀏覽 |閱讀模式
發表於 2011-7-8 15:26

馬上加入Android 台灣中文網,立即免費下載應用遊戲。

您需要 登錄 才可以下載或查看,沒有帳號?註冊

x
  內容摘要
   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。
  1.     public interface IResponseManager
  2.     {
  3.         void Write(Socket sender, IList<Socket> cliens, IDictionary<string, object> param);
  4.     }
複製代碼
其次,我們知道,換了是WCF編程的話,就需要在服務契約中寫兩個方法:「登陸」和「發送消息」。由於這裡是Socket編程,我們實現之前寫的IResponseManager接口,一個實現作為「登陸」的方法,另一個實現作為「發送消息」的方法。
  1. public class LogonResponseManager : IResponseManager
  2.     {
  3.         public void Write(System.Net.Sockets.Socket sender, IList<System.Net.Sockets.Socket> cliens, IDictionary<string, object> param)
  4.         {
  5.             Console.WriteLine("客戶端({0})登陸", sender.Handle);
  6.             var response = new SocketResponse
  7.             {
  8.                 Method = "Logon",
  9.                 DateTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"),
  10.                 Result = new { UserName = param["UserName"].ToString() }
  11.             };

  12.             JavaScriptSerializer jss = new JavaScriptSerializer();
  13.             string context = jss.Serialize(response);
  14.             Console.WriteLine("登陸發送的數據為:{0}", context);
  15.             sender.Send(Encoding.UTF8.GetBytes(context + "\n"));
  16.         }
  17.     }
複製代碼
  1. public class SendResponseManager : IResponseManager
  2.     {
  3.         public void Write(System.Net.Sockets.Socket sender, IList<System.Net.Sockets.Socket> cliens, IDictionary<string, object> param)
  4.         {
  5.             Console.WriteLine("客戶端({0})發送消息", sender.Handle);
  6.             var msgList = param["Message"] as IEnumerable<object>;
  7.             if (msgList == null)
  8.             {
  9.                 return;
  10.             }

  11.             var response = new SocketResponse
  12.             {
  13.                 Method = "Send",
  14.                 DateTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"),
  15.                 Result = new
  16.                 {
  17.                     UserName = param["UserName"].ToString(),
  18.                     Message = msgList.Select(s => s.ToString()).ToArray()
  19.                 }
  20.             };

  21.             JavaScriptSerializer jss = new JavaScriptSerializer();
  22.             string context = jss.Serialize(response);
  23.             Console.WriteLine("消息發送的數據為:{0}", context);

  24.             Parallel.ForEach(cliens, (item) =>
  25.             {
  26.                 try
  27.                 {
  28.                     item.Send(Encoding.UTF8.GetBytes(context + "\n"));
  29.                 }
  30.                 catch { };
  31.             });
  32.         }
  33.     }
複製代碼
最後在Socket程序中使用反射加「策略模式」調用這兩個接口實現類。
  1.             var typeName = "SocketServer." + request.Method + "ResponseManager, SocketServer";
  2.             Console.WriteLine("反射類名為:" + typeName);

  3.             Type type = Type.GetType(typeName);
  4.             if (type == null)
  5.             {
  6.                 return;
  7.             }

  8.             var manager = Activator.CreateInstance(type) as IResponseManager;
  9.             manager.Write(sender, this.socketClientSesson.Select(s => s.Key).ToList(),
  10.                 request.Param as IDictionary<string, object>);
複製代碼
完整的Socket服務器代碼如下:
  1. public class SocketHost
  2.     {
  3.         private IDictionary<Socket, byte[]> socketClientSesson = new Dictionary<Socket, byte[]>();

  4.         public int Port { get; set; }

  5.         public void Start()
  6.         {
  7.             var socketThread = new Thread(() =>
  8.             {
  9.                 Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
  10.                 IPEndPoint iep = new IPEndPoint(IPAddress.Any, this.Port);

  11.                 //綁定到通道上
  12.                 socket.Bind(iep);

  13.                 //偵聽
  14.                 socket.Listen(6);

  15.                 //通過異步來處理
  16.                 socket.BeginAccept(new AsyncCallback(Accept), socket);

  17.             });

  18.             socketThread.Start();

  19.             Console.WriteLine("服務器已啟動");
  20.         }

  21.         private void Accept(IAsyncResult ia)
  22.         {
  23.             Socket socket = ia.AsyncState as Socket;
  24.             var client = socket.EndAccept(ia);

  25.             socket.BeginAccept(new AsyncCallback(Accept), socket);

  26.             byte[] buf = new byte[1024];
  27.             this.socketClientSesson.Add(client, buf);

  28.             try
  29.             {
  30.                 client.BeginReceive(buf, 0, buf.Length, SocketFlags.None, new AsyncCallback(Receive), client);
  31.                 string sessionId = client.Handle.ToString();
  32.                 Console.WriteLine("客戶端({0})已連接", sessionId);
  33.             }
  34.             catch (Exception ex)
  35.             {
  36.                 Console.WriteLine("監聽請求時出錯:\r\n" + ex.ToString());
  37.             }
  38.         }

  39.         private void Receive(IAsyncResult ia)
  40.         {
  41.             var client = ia.AsyncState as Socket;

  42.             if (client == null || !this.socketClientSesson.ContainsKey(client))
  43.             {
  44.                 return;
  45.             }

  46.             int count = client.EndReceive(ia);

  47.             byte[] buf = this.socketClientSesson[client];

  48.             if (count > 0)
  49.             {
  50.                 try
  51.                 {
  52.                     client.BeginReceive(buf, 0, buf.Length, SocketFlags.None, new AsyncCallback(Receive), client);
  53.                     string context = Encoding.UTF8.GetString(buf, 0, count);
  54.                     Console.WriteLine("接收的數據為:", context);

  55.                     this.Response(client, context);
  56.                 }
  57.                 catch (Exception ex)
  58.                 {
  59.                     Console.WriteLine("接收的數據出錯:\r\n{0}", ex.ToString());
  60.                 }
  61.             }
  62.             else
  63.             {
  64.                 try
  65.                 {
  66.                     string sessionId = client.Handle.ToString();
  67.                     client.Disconnect(true);
  68.                     this.socketClientSesson.Remove(client);
  69.                     Console.WriteLine("客戶端({0})已斷開", sessionId);
  70.                 }
  71.                 catch (Exception ex)
  72.                 {
  73.                     Console.WriteLine("客戶端已斷開出錯" + ex.ToString());
  74.                 }
  75.             }
  76.         }

  77.         private void Response(Socket sender, string context)
  78.         {
  79.             SocketRequest request = null;
  80.             JavaScriptSerializer jss = new JavaScriptSerializer();
  81.             request = jss.Deserialize(context, typeof(SocketRequest)) as SocketRequest;

  82.             if (request == null)
  83.             {
  84.                 return;
  85.             }

  86.             var typeName = "SocketServer." + request.Method + "ResponseManager, SocketServer";
  87.             Console.WriteLine("反射類名為:" + typeName);

  88.             Type type = Type.GetType(typeName);
  89.             if (type == null)
  90.             {
  91.                 return;
  92.             }

  93.             var manager = Activator.CreateInstance(type) as IResponseManager;
  94.             manager.Write(sender, this.socketClientSesson.Select(s => s.Key).ToList(),
  95.                 request.Param as IDictionary<string, object>);
  96.         }
  97.     }
複製代碼
最後,json數據傳輸的實體對像為:
  1.    [Serializable]
  2.     public class SocketRequest
  3.     {
  4.         public string Method { get; set; }

  5.         public string DateTime { get; set; }

  6.         public object Param { get; set; }
  7.     }
複製代碼
  1.     [Serializable]
  2.     public class SocketResponse
  3.     {
  4.         public string Method { get; set; }

  5.         public string DateTime { get; set; }

  6.         public object Result { get; set; }
  7.     }
複製代碼
運行效果
「用Android 就來APK.TW」,快來加入粉絲吧!
Android 台灣中文網(APK.TW)

評分

參與人數 2幫助 +2 收起 理由
yoyovivi + 1 很給力!
idvtw + 1

查看全部評分

收藏收藏4 分享分享 分享專題
用Android 就來Android 台灣中文網(https://apk.tw)
沙發
sonar | 收聽TA | 只看該作者
發表於 2012-1-2 18:11
請問...
如果想要透過socket來對傳資料?(如mp3,4mp等)
也是透過server 跟client的方式嗎?
可否提供給小弟一些方向。
用Android 就來Android 台灣中文網(https://apk.tw)
回覆 支持 反對

使用道具 舉報

板凳
sonar | 收聽TA | 只看該作者
發表於 2012-1-2 18:17
此外,在請教一下。
程式中 server "C#"的部份,在Eclipse中,Debug Configurations要選擇那個application來Debug?
請指教。
用Android 就來Android 台灣中文網(https://apk.tw)
回覆 支持 反對

使用道具 舉報

地板
tn708516 | 收聽TA | 只看該作者
發表於 2012-8-8 13:52
感謝大大分享
剛好最近在研究這部分的程式碼
用Android 就來Android 台灣中文網(https://apk.tw)
回覆 支持 反對

使用道具 舉報

5
yaxin | 收聽TA | 只看該作者
發表於 2012-9-12 13:39
怎么下载看看呢。
用Android 就來Android 台灣中文網(https://apk.tw)
回覆 支持 反對

使用道具 舉報

6
wwxiaofen | 收聽TA | 只看該作者
發表於 2012-9-14 08:46
谢楼主分享!下载来看看先~~
用Android 就來Android 台灣中文網(https://apk.tw)
回覆 支持 反對

使用道具 舉報

7
cmdunlop | 收聽TA | 只看該作者
發表於 2012-9-17 10:59
sonar 發表於 2012-1-2 18:11
請問...
如果想要透過socket來對傳資料?(如mp3,4mp等)
也是透過server 跟client的方式嗎?

沒錯,使用Byte()來傳送,通常會轉成Stream:這是C#
用Android 就來Android 台灣中文網(https://apk.tw)
回覆 支持 反對

使用道具 舉報

8
cmdunlop | 收聽TA | 只看該作者
發表於 2012-9-17 11:00
sonar 發表於 2012-1-2 18:17
此外,在請教一下。
程式中 server "C#"的部份,在Eclipse中,Debug Configurations要選擇那個application ...

C#是使用visual studio編譯的
用Android 就來Android 台灣中文網(https://apk.tw)
回覆 支持 反對

使用道具 舉報

9
qw0323wq | 收聽TA | 只看該作者
發表於 2012-9-29 22:39
剛好想了解有關於這方面的資訊
用Android 就來Android 台灣中文網(https://apk.tw)
回覆 支持 反對

使用道具 舉報

10
shamslud | 收聽TA | 只看該作者
發表於 2012-10-31 11:06
要怎麼下載呀~~沒有連結說。
用Android 就來Android 台灣中文網(https://apk.tw)
回覆 支持 反對

使用道具 舉報

您需要登錄後才可以回帖 登錄 | 註冊

本版積分規則