C# 高效的语言,既然选择了它来写网络服务器,就应该使用最简单的方式。
MS提供了SOCKET和TCPCLIENT两种方式给我们使用,TCPCLIENT封装了socket,似乎应该用tcpclient来搞。。。
关于阻塞:
该概念出现的场景是服务器端,在接受客户端连接的场景。我们在服务器端用TcpListener打开一个监听,等待客户端的连接,需要使用AcceptTcpClient()方法。
这个AcceptTcpClient()方法一旦使用,整个线程就停住(阻塞)了。 直到有客户端连接上来后,AcceptTcpClient()方法才返回客户端的连接。
关于异步:
该概念出现在网络连接已经建立好后的场景,服务器程序或客户端程序,从连接的数据流里面读取或写入数据,如果数据流中有数据才读取,程序代码跟着执行就是同步,
或者不管有无数据,都去试做读数据流中的数据,而使用回调函数来处理这摊子事情,就是异步。
基于TCPCLIENT,下面使用简单的代码来实现一个阻塞,异步的网络服务器。
服务器开始,使用一个线程来处理监听操作:
Thread lsThread = new Thread(new ThreadStart(server_listen));
lsThread.IsBackground = true;
lsThread.Start();
//////////////////
/////网络监听////
/////////////////
private void server_listen()
{
listener = new TcpListener(IPAddress.Parse("127.0.0.1"), 3000);
listener.Start();
while (true)
{
Thread.Sleep(1000);
while (listener.Pending()) //跳开阻塞,用这个在关闭服务器程序时不会出现异常
{
tcpuser one = new tcpuser();
one.tcp = listener.AcceptTcpClient();
Thread subThread = new Thread(new ParameterizedThreadStart(f1)); //线程
subThread.Start(one);
gv.tcpuserlist.Add(one);
}
}
}
关于listener.Pending,这个方法可以测试listener中有没有客户端连接上来,如果有,再去接收客户端的连接。
这样的写法,就可以避免程序被直接使用AcceptTcpClient()方法阻塞住。
//多客户端的处理线程
private void f1(object xxx)
{
tcpuser tp = (tcpuser)xxx;
NetworkStream stream = tp.tcp.GetStream();
while (true)
{
var begint = DateTime.Now;
rwl.EnterReadLock(); //无论花费多长时间,线程都会一直等到获得锁为止
try
{
//对话逻辑处理
client_business(tp);
//网络写
if (!String.IsNullOrWhiteSpace(tp.txtout))
{
tp.writebuffer = Encoding.UTF8.GetBytes(tp.txtout + "."); //加一次消息结束符号.
if (tp.tcp != null && tp.tcp.Connected)
{
if (stream.CanWrite) { stream.BeginWrite(tp.writebuffer, 0, tp.writebuffer.Length, new AsyncCallback(WriteCallBack), tp); }
}
else { tp.islogon = 0; rwl.ExitReadLock(); break; }
}
//网络读
if (tp.tcp != null && tp.tcp.Connected) { stream.BeginRead(tp.readbuffer, 0, tp.readbuffer.Length, new AsyncCallback(ReadCallBack), tp); }
else { tp.islogon = 0; rwl.ExitReadLock(); break; }
}
catch { listBox2.Items.Insert(0, "对话模块 ERROR"); tp.islogon = 0; rwl.ExitReadLock(); break; }
Thread.Sleep(100);
rwl.ExitReadLock();
listBox1.Items.Insert(0, tp.playername + " client:" + Convert.ToInt32(DateTime.Now.Subtract(begint).TotalMilliseconds).ToString());
}
}
上面的代码使用BeginRead来读取流中的数据,BeginRead方法是一个回调函数,WIN系统会处理函数中的代码,并且它自己结束时候,又调用了自己,就形成了一个循环,可以不停的处理数据。
下面是BeginRead函数的代码内容:
//网络读子函数
public void ReadCallBack(IAsyncResult ar)
{
try
{
tcpuser one = (tcpuser)ar.AsyncState;
if (one.tcp != null && one.tcp.Connected)
{
NetworkStream stream = (NetworkStream)one.tcp.GetStream();
int numberOfBytesRead = stream.EndRead(ar);
one.txtin = Encoding.UTF8.GetString(one.readbuffer, 0, numberOfBytesRead);
if (!String.IsNullOrWhiteSpace(one.txtin))
{ one.txtlonglong = one.txtlonglong + one.txtin; one.txtin = ""; } //每次收到的,都放longlong
}
}
catch { listBox2.Items.Insert(0, "ReadCallBack ERROR"); }
}
//网络写子函数
public void WriteCallBack(IAsyncResult ar)
{
try
{
tcpuser one = (tcpuser)ar.AsyncState;
if (one.tcp != null && one.tcp.Connected)
{
NetworkStream stream = (NetworkStream)one.tcp.GetStream();
stream.EndWrite(ar);
one.txtout = "";
}
}
catch { listBox2.Items.Insert(0, "WriteCallBack ERROR"); }
}