引自:http://ilewen.com/questions/514
在本教程中,我会向你展示如何用C#建立一个线程中的TCP服务端。如果你用过windows的sockets编写程序,你就知道有多麻烦。感谢.net框架,使得网络编程变得更容易了。
我们将建立一个非常简单的的服务器接受客户端连接,并可以发送和接收数据。服务器为每一个连接客户端产生一个线程,从理论上讲,可以接受多个连接(虽在实践中,Windows对此是有限制)。
下面看代码:
using System;
using System.Text;
using System.Net.Sockets;
using System.Threading;
using System.Net;
namespace TCPServerTutorial
{
class Server
{
private TcpListener tcpListener;
private Thread listenThread;
public Server()
{
this.tcpListener = new TcpListener(IPAddress.Any, 3000);
this.listenThread = new Thread(new ThreadStart(ListenForClients));
this.listenThread.Start();
}
}
}
上面建立了一个基本的服务器类。我们定义了一个TcpListener变量(TcpListener封装了底层套接字通信工作),同时定义了一个线程用于监听客户端的连接。接下来我们定义了ThreadStart的委托函数:ListenForClients。
代码如下:
private void ListenForClients()
{
this.tcpListener.Start();
while (true)
{
//blocks until a client has connected to the server
TcpClient client = this.tcpListener.AcceptTcpClient();
//create a thread to handle communication
//with connected client
Thread clientThread = new Thread(new ParameterizedThreadStart(HandleClientComm));
clientThread.Start(client);
}
}
这个函数非常简单。首先,它启动TcpListener,然后循环接受连接。 AcceptTcpClient的调用将阻塞线程的执行,直到一个客户端连接,在这里,我们触发一个线程来处理与我们的新客户端的通信。我用了ParameterizedThreadStart委托,所以我可以传递AcceptTcpClient调用返回的TcpClient对象到新线程。
ParameterizedThreadStart使用函数HandleClientComm。这个函数负责从客户端读取数据。让我们看看它。
private void HandleClientComm(object client)
{
TcpClient tcpClient = (TcpClient)client;
NetworkStream clientStream = tcpClient.GetStream();
byte[] message = new byte[4096];
int bytesRead;
while (true)
{
bytesRead = 0;
try
{
//blocks until a client sends a message
bytesRead = clientStream.Read(message, 0, 4096);
}
catch
{
//a socket error has occured
break;
}
if (bytesRead == 0)
{
//the client has disconnected from the server
break;
}
//message has successfully been received
ASCIIEncoding encoder = new ASCIIEncoding();
System.Diagnostics.Debug.WriteLine(encoder.GetString(message, 0, bytesRead));
}
tcpClient.Close();
}
第一步将client类型转换为TcpClient类型,因为ParameterizedThreadStart委托只能接受基本对象类型。下一步,从TcpClient得到NetworkStream用来读取数据。之后,通过一个while循环从客户端读取数据。 read调用会一直处于阻塞状态,直到从客户端接收到数据。如果从客户端读取到零字节,那么说明客户端已断开。在该例子里,我只是一个字符串转换成字节数组,并将它输出到控制台。当然,你会做一些更复杂的工作。如果socket出现错误或客户端断开连接,你应该调用TcpClient对象的close函数关闭连接,释放它使用的任何资源。
上面就是创建一个服务器线程,接受连接,并从客户端读取数据所需做的所有的事情。当然,如果服务端不能发送数据,那么就没什么用了。下面,让我们看看如何将数据发送到我们的连接的一个客户端。
NetworkStream clientStream = tcpClient.GetStream();
ASCIIEncoding encoder = new ASCIIEncoding();
byte[] buffer = encoder.GetBytes("Hello Client!");
clientStream.Write(buffer, 0 , buffer.Length);
clientStream.Flush();
从AcceptTcpClient返回的TcpClient对象用来发送数据给客户端。因此需要在服务端保存这些对象。通常可以创建一个TcpClient对象的集合。发送数据是非常简单的,只需要得到client的NetworkStream对象,然后调用它的write方法就可以了。
TCP服务端已经完成了。比较难的部分是定义一个协议用来在客户端和服务端之间发送信息。应用层的协议通常对不同的应用都是不一样的。所以我不打算讲解更多,你只需要实现你自己的。
如果没有一个客户端连接到服务端,那么这个服务端还有存在的意思吗?本教程主要是讲服务端编程,但这里有一个简短的代码,说明了如何设置一个基本的TCP连接,并发送一段数据。
TcpClient client = new TcpClient();
IPEndPoint serverEndPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 3000);
client.Connect(serverEndPoint);
NetworkStream clientStream = client.GetStream();
ASCIIEncoding encoder = new ASCIIEncoding();
byte[] buffer = encoder.GetBytes("Hello Server!");
clientStream.Write(buffer, 0 , buffer.Length);
clientStream.Flush();
第一步工作是获取客户端到服务端的连接。使用TcpClient.Connect方法。它需要知道服务端的IPEndPoint,在这里,我将连接到本地主机,端口号3000。然后发送字符串"hello Server!"
注意:从客户端或服务器写并不总是等于一个在接收端读。例如,客户端向服务器发送10个字节,但服务器可能无法在第一次读取的时候得到所有10个字节。使用TCP,几乎保证最终得到所有10个字节,但它可能需要不止读取一次。所以当设计交互协议的时候要注意这一点。