首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > .NET > C# >

多客户端Socket有关问题,感兴趣就来吧

2012-08-01 
多客户端Socket问题,感兴趣就来吧这一次我做多客户端对服务器相互对话,我的思路是每当有一个客户端连接到

多客户端Socket问题,感兴趣就来吧
这一次我做多客户端对服务器相互对话,我的思路是每当有一个客户端连接到服务器的时候,服务器增加一个CheckBox,可是我发现除了第一个客户端连接进来的时候服务器会增加一个CheckBox,后面的客户端连接服务器都增加了CheckBox但是界面上就是不显示(这是为什么,跟Control.Invoke方法有关么)。调试了好久找不到原因,麻烦各位朋友帮忙看看。源码如下:
  TcpListener tcpListener;
  //判断是否已经启动监听
  bool IsBeginListen = false;
  //当前连接数
  int linkCount = 0;
  //客户端连接列表
  List<TcpClient> clientArray;
  public MyServer()
  {
  InitializeComponent();
  }

  /// <summary>
  /// 启动tcp端口监听
  /// </summary>
  /// <param name="sender"></param>
  /// <param name="e"></param>
  private void btnStart_Click(object sender, EventArgs e)
  {
  //如果已经开始监听了,则返回
  if (IsBeginListen)
  {
  return;
  }
  IsBeginListen = true;

  //开始一个线程启动监听
  Thread thread = new Thread(new ThreadStart(StartListen));
  thread.Start();
  }

  /// <summary>
  ///异步启动监听
  /// </summary>
  void StartListen()
  {
  //开始Tcp监听
  IPEndPoint ip = new IPEndPoint(IPAddress.Parse(txtAddress.Text), int.Parse(txtPort.Text));
  tcpListener = new TcpListener(ip);
  tcpListener.Start(10000);
  //初始化clientArray;
  clientArray = new List<TcpClient>(1500);
  lblTip.Invoke(new Action(delegate() { lblTip.Text = "已启动监听"; }));
  // lblTip.Text = "已启动监听";
  while (true)
  {
  //如果监听数达到1200个,则不继续增加监听
  if (linkCount > 1200)
  {
  Thread.Sleep(10000);
  continue;
  }
  //开始异步接收客户端请求
  tcpListener.BeginAcceptTcpClient(new AsyncCallback(AcceptTcpAsync), tcpListener);
  //开始一个监听,则监听数加1
  linkCount++;
  }
  }

  /// <summary>
  /// 异步接受客户端请求后的回调
  /// </summary>
  /// <param name="res"></param>
  void AcceptTcpAsync(IAsyncResult res)
  {
  TcpListener listener = res.AsyncState as TcpListener;
  if (listener != null)
  {
  TcpClient client = listener.EndAcceptTcpClient(res);
  int index = 0;
  //这里一定要同步,以便正确获取当前Tcp连接在列表中的索引
  lock (this)
  {
  clientArray.Add(client);
  index = clientArray.Count - 1;
  //每接收一个连接,则在panelClient里增加一个复选框,问题就出来这里,我调试发现第二个客户端连接服务器的时候,panelClient明明增加了一个CheckBox,可是界面上就是不显示,但是我新增加的客户端发送消息服务器都能收到,这是为什么?
  panelClient.Invoke(new Action<int>((arg) => { CheckBox ck = new CheckBox(); ck.Name = "ck" + arg.ToString(); ck.Text = "客户端:" + arg.ToString(); panelClient.Controls.Add(ck); ck.Select(); }), index);  
  }

  //用指定的Socket缓冲区大小初始化接收客户端数据的字节数组,默认为8KB
  byte[] receiveArr = new byte[listener.Server.SendBufferSize];
  MyTcp myTcp = new MyTcp(clientArray[index], receiveArr);



  //开始异步接收客户端传来的数据
  clientArray[index].Client.BeginReceive(myTcp.ReceiveArray, 0, myTcp.ReceiveArray.Length, SocketFlags.None, new AsyncCallback(ReceiveAsync), myTcp);
  }
  }

  /// <summary>
  /// 异步接收客户端数据后的回调方法
  /// </summary>
  /// <param name="res"></param>
  void ReceiveAsync(IAsyncResult res)
  {
  MyTcp myTcp = (res.AsyncState as MyTcp);
  if (myTcp != null)
  {
  try
  {
  myTcp.Client.Client.EndReceive(res);
  }//客户端断开连接的时候,会触发这个回调  
  catch (SocketException ex)
  {
  //如果客户端断开连接
  if (ex.SocketErrorCode == SocketError.ConnectionReset)
  {
  MessageBox.Show("客户端断开连接!");
  btnStop_Click(null, null);
  btnStart_Click(null, null);
  return;
  }
  }
  }
  string receiveText = Encoding.UTF8.GetString(myTcp.ReceiveArray);
  //用lambda表达式初始化一个委托,在窗体线程上调用这个委托将服务器返回消息添加到获取消息框中
  rtbReceive.Invoke(new Action<string>(str => { rtbReceive.AppendText(str); rtbReceive.AppendText("\n"); }), receiveText);

  //在下一次接收数据之前,清空字节数组,以免下次接收读取数据时读取错误
  for (int i = 0; i < myTcp.ReceiveArray.Length; i++)
  {
  myTcp.ReceiveArray = 0;
  }

  //这一次接收完成以后,开始下一次接收,如果没有获取到服务器发过来的消息,线程会被堵塞,所以不用担心CPU占用100%
  myTcp.Client.Client.BeginReceive(myTcp.ReceiveArray, 0, myTcp.ReceiveArray.Length, SocketFlags.None, new AsyncCallback(ReceiveAsync), myTcp);
  }

  /// <summary>
  /// 发送消息给客户端
  /// </summary>
  /// <param name="sender"></param>
  /// <param name="e"></param>
  private void btnSend_Click(object sender, EventArgs e)
  {
  List<int> arr = new List<int>(10);
  //获取要发送出去的客户端Client编号
  foreach (CheckBox item in panelClient.Controls)
  {
  if (item.Checked)
  {
  int index = int.Parse(item.Text.Substring(item.Text.IndexOf(':') + 1));
  arr.Add(index);
  }
  }
  if (arr.Count == 0)
  {
  MessageBox.Show("您还没有选择客户端");
  }
  byte[] sendArr = Encoding.UTF8.GetBytes(rtbSend.Text);
  rtbSend.Clear();
  foreach (var item in arr)
  {
  clientArray[item].Client.BeginSend(sendArr, 0, sendArr.Length, SocketFlags.None, new AsyncCallback(SendAsync), clientArray[item]);
  }
  }

  /// <summary>
  /// 异步发送消息给服务器的回调方法
  /// </summary>
  /// <param name="res"></param>
  void SendAsync(IAsyncResult res)
  {
  TcpClient server = res.AsyncState as TcpClient;
  if (server != null)
  {


  server.Client.EndSend(res);
  }
  }  

  /// <summary>
  /// 停止服务器端口监听
  /// </summary>
  /// <param name="sender"></param>
  /// <param name="e"></param>
  private void btnStop_Click(object sender, EventArgs e)
  {
  tcpListener.Stop();
  lblTip.Text = "监听已停止";
  }

  /// <summary>
  /// 自定一个用于接收服务器返回数据的类,该类存储了TcpClient和服务器发送过来的字节数组
  /// </summary>
  class MyTcp
  {
  public MyTcp(TcpClient tcp, byte[] arr)
  {
  this.Client = tcp;
  this.ReceiveArray = arr;
  }
  public TcpClient Client;
  public byte[] ReceiveArray;
  }

[解决办法]
//写成了一个类 SocketsClass.cs
//主窗体Server 引用后
//再申明
//public SocketsClass _socketsClass;


//以下放在需要的地方运行就OK了
//_socketsClass = new SocketsClass(this);
//_socketsClass.StartSocket();

//关闭程序时记得运行 _socketsClass.close();



//以下是SocketsClass.cs的代码

using System;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Collections;
using System.Text;

namespace Server
{

public class SocketsClass
{
#region 申明



/// <summary>
/// 服务器程序使用的端口,默认为8888
/// </summary>
private int _port = 8888;
private IPAddress _ip;
private IPEndPoint _ipep;
/// <summary>
/// 服务器端的监听器
/// </summary>
private TcpListener _tcpl = null;

/// <summary>
/// 保存所有客户端会话的哈希表
/// </summary>
private Hashtable _transmit_tb = new Hashtable();

private Thread _Receiver;

private bool IsRun = false;


private DateTime GameTimeCou = DateTime.Now;


public SocketsClass(Form1 main)
{
FMain = main;
IsRun = false;
_ip = Dns.GetHostAddresses(Dns.GetHostName())[0];//可能有多个,此时的IP是本地IP

_ipep = new IPEndPoint(_ip, _port);
_tcpl = new TcpListener(_port);

IsRun = true;
}

public void StartSocket()
{
_tcpl.Start();//开始侦听传入的连接请求。

//启动Socke线程
_Receiver = new Thread(new ThreadStart(this.SocketReceiver));
_Receiver.Name = "Receiver";
_Receiver.IsBackground = true;
_Receiver.Start();
Thread.Sleep(100);
}


/// <summary>
/// socket分配,接收-------------------
/// </summary>
private void SocketReceiver()
{
while (IsRun)
{
#region 分配Socket
if (_tcpl.Pending()) //如果有挂起的请求则分析数据
{
try
{
//自定的串识别格式 客户端发送"lj 1 4load"这个格式的串过来才能被后台成功接受
//"aa"为最初识别码, " 1"为分机号, " 4"为后面字串的长度,"load"为登陆请求
//串中的空格是自己定的读取格式

byte[] buf = new byte[5];
byte[] tempBuf;
int count = 0;
string userName = "", msg = "";


Socket newSocket = _tcpl.AcceptSocket();

if (newSocket.Available > 0)
{
newSocket.Receive(buf, 0, 5, SocketFlags.None);
msg = Encoding.ASCII.GetString(buf, 0, 5).Trim('\0');
//自定的串识别格式 客户端发送lj 1 4load 这个串过来才能被后台成功接受
//"lj"为最初识别码, " 1"为分机号, " 4"为后面字串的长度,"load"为登陆请求
//串中的空格是自己定的读取格式
if (msg.IndexOf("lj") >= 0)
{
userName = msg.Substring(2, 3);//获取串中的机号
newSocket.Receive(buf, 0, 5, SocketFlags.None);
msg = Encoding.ASCII.GetString(buf, 0, 5).Trim('\0');
count = Convert.ToInt32(msg);//获取串的长度

tempBuf = new byte[count];
newSocket.Receive(tempBuf, 0, count, SocketFlags.None);
msg = Encoding.ASCII.GetString(tempBuf, 0, count).Trim('\0');//获取真正的请求串

if (msg == "load")
{
//验证是否为唯一用户,有了就直接踢掉原来的客户,客户再次发送登陆请求后才可成功边接后台
if (_transmit_tb.Count != 0 && _transmit_tb.ContainsKey(userName))
{
lock (_transmit_tb)
{
_transmit_tb.Remove(userName);
}
continue;
}
else
{
SendLoad(userName, newSocket, "test");//验证OK向客户发送test串
}

//将新连接加入转发表
lock (_transmit_tb)
{
_transmit_tb.Add(userName, newSocket);
}
}
//Log.AppearInfoLine("用户 " + userName + " 成功连接......");
}
}

}
catch (Exception ex)
{
//Log.WriteErr(ex.Message + ex.StackTrace, "ReceiveSocket");
}
}
#endregion

#region 接收数据
try
{
string msg = "", _clientName = "";
string[] s;

byte[] packetBuff = new byte[5];
byte[] tempbuf;
int count = 0;

lock (_transmit_tb)
{
foreach (DictionaryEntry de in _transmit_tb)
{
_clientName = de.Key as string;
Socket clientSkt = de.Value as Socket;


if (clientSkt.Connected)
{
if (clientSkt.Available > 0)
{
clientSkt.Receive(packetBuff, 0, 5, SocketFlags.None);
msg = Encoding.ASCII.GetString(packetBuff, 0, 5).Trim('\0');

if (msg.IndexOf("lj") >= 0)


{
if (_clientName != msg.Substring(2, 3)) break;

clientSkt.Receive(packetBuff, 0, 5, SocketFlags.None);
msg = Encoding.ASCII.GetString(packetBuff, 0, 5).Trim('\0');
count = Convert.ToInt32(msg);

tempbuf = new byte[count];
clientSkt.Receive(tempbuf, 0, count, SocketFlags.None);
msg = Encoding.ASCII.GetString(tempbuf, 0, count).Trim('\0'); ;

if (msg.IndexOf("History") >= 0)
{
SendHistory(_clientName);
}

}
}
}
}
}
}
catch (Exception ex)
{
//Log.WriteErr(ex.Message + ex.StackTrace, "ReceiveThread");
//Thread.CurrentThread.Abort();
break;
}



#endregion


if (ReceTimeCou.Second != DateTime.Now.Second)
{
ReceTimeCou = DateTime.Now;
SendAllTest();//定时向所有在线分机发送测试串
}

Thread.Sleep(300);
}
//Log.AppearInfoLine(DateTime.Now + " " + "finish...");
}



public void SendAllTest()
{
#region 向所有在线分机发送测试串

string _clientName = "", s = "", msg = "";

lock (_transmit_tb)
{
foreach (DictionaryEntry de in _transmit_tb)
{
_clientName = de.Key as string;
Socket clientSkt = de.Value as Socket;

msg = "test";

msg = "aa" + _clientName + Convert.ToString(msg.Length).PadLeft(5, ' ') + msg;

byte[] buf;
buf = new byte[msg.Length];
Buffer.BlockCopy(Encoding.ASCII.GetBytes(msg), 0, buf, 0, msg.Length);
try
{
clientSkt.Send(buf);
}
catch
{
//Log.AppearInfoLine(DateTime.Now + " " + "sendto " + _clientName + " test error");
s = _clientName;
}

}
}

if (s != "") _transmit_tb.Remove(s);//有发送错误的 直接移除客户表
s = "";

#endregion
}
[解决办法]
代码太长了。

这两个东西本没有什么关系,独立设计界面和服务器这两个东西,后者使用两个事件——一个客户端连接、一个客户端连接结束,来通知就可以了。你贴出的代码把它们纠结在一起,我就不太想看了。

另外,什么叫做客户端?一个电脑上可能有10个不同的别具一格的应用程序在访问同一个系统后台服务,并且每一应用都有可能启动多个线程每一个线程都单独与服务器会话,这才是真实的c/s系统架构需求。如果你从网上抄一两个点对点、仅进行一两个简单功能的“范例”(例如最容易胡乱滥用的“聊天”范例),难免写出很烂的服务器程序。
[解决办法]
出错原因:没有显示的原因是因为后添加的控件被它的前一个添加的控件遮挡了。因为后添加的控件的z序大于先添加的控件的z序
解决方案:只要调整一下每个控件的大小以及位置即可。
举例如下:

C# code
 static int i = 1; private void button1_Click(object sender, EventArgs e)        {            CheckBox cb = new CheckBox();            cb.Name = i.ToString();            panel1.Controls.Add(cb);            cb.Size = new Size(20, 20); // 注意这个大小,默认宽度比这个要长,所以遮挡后面控件了,如果去掉这句,将看不到效果,可以自己试一下            cb.Left = i * 40;            cb.Top = 10;            i++;        } 


[解决办法]
我觉得是你写控件的时候的问题。。你可以在那多设断点。。然后显示的时候用多一些办法。。代码太长没兴趣完全看。。不过既然你后台都有CB了。。就是你写到前台没写好
[解决办法]
参考这个例子,设计的灵活易扩展,可以学习!

源代码和实现原理:
http://www.codeproject.com/KB/IP/AsyncSocketServerandClien.aspx

效果图:

[解决办法]
你没设置 CheckBox的位置 ,所以添加的ChechBox都会在默认的地方显示,导致覆盖了。。。。

热点排行