• 设为首页
  • 点击收藏
  • 手机版
    手机扫一扫访问
    迪恩网络手机版
  • 关注官方公众号
    微信扫一扫关注
    公众号

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

原作者: [db:作者] 来自: [db:来源] 收藏 邀请

 

   内容摘要

   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<stringobject> param);
    }

 

 

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

 

public class LogonResponseManager : IResponseManager
    {
        
public void Write(System.Net.Sockets.Socket sender, IList<System.Net.Sockets.Socket> cliens, IDictionary<stringobject> 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<stringobject> 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<stringobject>);

 

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

 

public class SocketHost
    {
        
private IDictionary<Socket, byte[]> socketClientSesson = new Dictionary<Socket, byte[]>();

        
public int Port { getset; }

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

 

 

最后,json数据传输的实体对象为:

 

   [Serializable]
    
public class SocketRequest
    {
        
public string Method { getset; }

        
public string DateTime { getset; }

        
public object Param { getset; }
    }

 

 

    [Serializable]
    
public class SocketResponse
    {
        
public string Method { getset; }

        
public string DateTime { getset; }

        
public object Result { getset; }
    }

 

 四、客户端源代码

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.HashMa

鲜花

握手

雷人

路过

鸡蛋
该文章已有0人参与评论

请发表评论

全部评论

专题导读
热门推荐
热门话题
阅读排行榜

扫描微信二维码

查看手机版网站

随时了解更新最新资讯

139-2527-9053

在线客服(服务时间 9:00~18:00)

在线QQ客服
地址:深圳市南山区西丽大学城创智工业园
电邮:jeky_zhao#qq.com
移动电话:139-2527-9053

Powered by 互联科技 X3.4© 2001-2213 极客世界.|Sitemap