服务器端,对应于一个客户端都有一个对象,这些对象被放到list中,控制了这个list,就控制了整个服务器的通讯。
服务器端的这个对象叫tcpuser, 里面放一些变量,有一个变量比较特别,tcpclient,这个变量以后就是它同客户端建立起通道然后通讯。
public class tcpuser
{
public int id { get; set; }
public TcpClient tcp { get; set; }
public string username { get; set; }
public string password { get; set; }
public string txtin { get; set; }
public string txtout { get; set; }
public string heart { get; set; }
}
tcpuser对象放到一个list对象集中。
public List<tcpuser> tcpuserlist = new List<tcpuser>();服务器网络模块运行时,需要用一个IP和端口做监听,就是netstat -an中看到的listening, 客户段就用这个端口接入。
既然使用C#,为了最简单,就不去使用socket,直接用tcplistener,很方便。
下面的函数,我放到了一个线程中,这个线程执行起来,它会监听等待用户连接。
private void server_listen()
{
listener = new TcpListener(IPAddress.Any, 3000);
listener.Start();
while (true)
{
Thread.Sleep(1000);
while (listener.Pending()) //跳开阻塞,用这个在关闭服务器程序时不会出现异常
{
tcpuser one = new tcpuser();
one.tcp = listener.AcceptTcpClient();
NetworkStream stream = one.tcp.GetStream();
try {
if (stream.CanRead) { stream.BeginRead(buffer, 0, buffer.Length, new AsyncCallback(ReadCallBack), one); }
}
catch
{
}
one.id = tcpuserid;
tcpuserlist.Add(one);
tcpuserid++;
}
}
}
上面的代码,当一个客户端接入,服务器会新建一个tcpuser对象,把通过客户端通讯的通道连接到tcpuser对象的一个变量TcpClient 上。
通道建立好后,在这个stream中,使用beginread异步来读取客户端传来的数据。同当前线程中对立的是,这个beginread启动了系统回调功能,系统去处理beginread中的代码,再无需我们关心。只是在beginread代码的最后,不能让它就这么结束了。应该要让它一直接跑作,才能一直接收用户的数据,所有最后,我们让beginread再调用自己,这样就可以一直运行下去。
下面代码就是这个一直自己调用自己的回调函数:
public void ReadCallBack(IAsyncResult ar)
{
try
{
tcpuser one = (tcpuser)ar.AsyncState;
//心跳改变
one.heart = DateTime.Now.ToString();
NetworkStream stream = (NetworkStream)one.tcp.GetStream();
int numberOfBytesRead = stream.EndRead(ar);
one.txtin = Encoding.UTF8.GetString(buffer, 0, numberOfBytesRead);
//处理客户端数据
string[] s = one.txtin.Split('|');
//如果是1,1 就是认证用的用户密码数据
if ((int.Parse(s[0]) == 1) && (int.Parse(s[1]) == 1)) { one.username = s[2]; one.password = s[3]; }
stream.BeginRead(buffer, 0, buffer.Length, new AsyncCallback(ReadCallBack), one);
//卡了一个写回客户端
if (one.txtout != null)
{
wbuffer = Encoding.UTF8.GetBytes(one.txtout);
if (stream.CanWrite) { stream.BeginWrite(wbuffer, 0, wbuffer.Length, new AsyncCallback(WriteCall), one); }
}
}
catch
{
}
}
这个回调函数,一旦跑起来,就可以一直由系统来处理传入的用户数据,这是一个很好的功能。
客户端一次传入的数据,函数中就近就放在txtin变量中,等服务器其它模块来处理,反正网络模块就只能处理到这里了。
其它模块只需要遍历tcpuser 这个list,取出来看看txtin变量,就知道客户端想做什么。
目前我干脆把写回客户端的功能,也放到了里面。 还加入了心跳功能,解析一种客户端数据的功能。
心跳功能,其实就是在回调函数中不停的刷新heart这个变量,
回调函数是基于通道stream的,如果客户端退出,通道就关闭,stream就会无效,回调函数就会停工。
心跳变量就会停止刷新,检查心跳变量,没有刷新的,就是客户端已经退出的对应于服务器的tcpuser对象。
基于上面的设计,先搞了一个原型出来,测试了一下。
模拟了500个客户端连接服务器,每秒发送一串字符。
服务器使用beginread函数,没有为每个连接建立单独的线程,服务器程序才几个线程就可以应付了。
本文介绍了一种基于C#的TCP服务器设计思路,利用TcpListener监听客户端连接,并通过异步IO处理客户端请求,实现了高效的消息收发及心跳检测。

被折叠的 条评论
为什么被折叠?



