TCP套接字服务器 - 一种检测传入数据的方法

时间:2014-06-16 17:21:16

标签: c# sockets tcp

我正在尝试编写一个服务器,它将侦听端口,接受传入的客户端连接,然后将该客户端连接放入堆栈以等待将来的消息到达。理想情况下,当消息到达时,我想发起一个事件,然后允许服务器执行操作。

我已经写了几乎全部内容......我正在使用AcceptTCPClient()来实际获取新的客户端连接,这很好。然后我创建一个线程,将套接字传递给一个包含客户端状态和其他一些数据的类。但是,我能想到阻止并等待该线程上未来传入连接的唯一方法是调用类似于NetworkStream.Read()的东西,然后阻塞直到字节到达。

所以这是根本问题 - 我使用的是Protobuff-net,它允许我反序列化网络流而不是单个字节数组。因此,通过读取前几个字节,我必须重置读取,将网络流传递给protobuff反序列化方法,然后继续。

我真正想要的是一种阻塞方法,直到检测到一些字节,但并不要求我实际读取字节,直到我准备好。

任何人都有任何想法如何实现这一目标?

更新

如下面的评论所示,这不是.Net似乎支持的东西,因此最简单的解决方案似乎是使用下面使用异步读/写的 Tsukasa 示例。

我写了它以消耗线上的所有字节,然后将这些字节传递给protobuff Deserialize方法。

不是我想要的,但它运作正常。谢谢大家的帮助。

    private byte[] buffer = new byte[256];
    private Socket socket;
    private NetworkStream networkStream;
    private AsyncCallback callbackRead;
    private AsyncCallback callbackWrite;

    public Socket Socket
    {
        get { return socket; }            
    }

    public ClientProxy(Socket clientSocket)
    {
        socket = clientSocket;
        networkStream = new NetworkStream(clientSocket);
        callbackRead = new AsyncCallback(OnReadComplete);
        callbackWrite = new AsyncCallback(OnWriteComplete);
    }

    public void ReadAsync()
    {
        networkStream.BeginRead(buffer, 0, buffer.Length, callbackRead, null);
    }

    private void OnReadComplete(IAsyncResult ar)
    {
        int bytesRead = networkStream.EndRead(ar);

        if (bytesRead > 0)
        {                
            MemoryStream stream = new MemoryStream(buffer);
            Message data;
            data = Serializer.DeserializeWithLengthPrefix<Message>(stream, PrefixStyle.Fixed32);

            if (data.Type == Chat.Type.User && data.Action == Chat.Action.Add)
            {
                Communication.RegisterClient(data.From, socket.Handle.ToString());
            }                
            Communication.readMessage(data);

            ReadAsync();
        }
        else
        {
            networkStream.Close();
            socket.Close();
            networkStream = null;
            socket = null;
        }
    }

2 个答案:

答案 0 :(得分:2)

您可以轮询DataAvailable属性但它不可靠且效率低下。

更好的方法是使用长度前缀为protobuf流添加前缀。它还允许在不读取protobuf消息的情况下进行读取。通过使用前缀,您可以调用异步读取,该异步读取将在有可用内容时立即返回(如果您使用长度标头,则将Read配置为仅读取4个字节)。

如果您不想自己处理网络操作,可以使用我的apache许可库。以下是使用protobuf-net的示例:http://blog.gauffin.org/2014/06/easy-and-perfomant-clientserver-communication-with-protobuf-net-griffin-framework/

答案 1 :(得分:1)

BeginRead()允许您在数据准备就绪时调用事件,这样您的进程就可以处于阻塞状态,操作系统只会在资源准备就绪时将其唤醒

class Client
{
    private byte[] buffer = new byte[256];
    private Socket socket;
    private NetworkStream networkStream;
    private AsyncCallback callbackRead;
    private AsyncCallback callbackWrite;

    public Client(Socket clientSocket)
    {
        socket = clientSocket;
        networkStream = new NetworkStream(clientSocket);
        callbackRead = new AsyncCallback(OnReadComplete);
        callbackWrite = new AsyncCallback(OnWriteComplete);
    }

    public void StartRead()
    {
        networkStream.BeginRead(buffer, 0, buffer.Length, callbackRead, null);
    }

    private void OnReadComplete(IAsyncResult ar)
    {
        int bytesRead = networkStream.EndRead(ar);

        if (bytesRead > 0)
        {
            string s = System.Text.Encoding.ASCII.GetString(buffer, 0, bytesRead);
            //do something with complete data here
            networkStream.BeginWrite(buffer, 0, bytesRead, callbackWrite, null);
        }
        else
        {
            networkStream.Close();
            socket.Close();
            networkStream = null;
            socket = null;
        }
    }

    private void OnWriteComplete(IAsyncResult ar)
    {
        networkStream.EndWrite(ar);
        networkStream.BeginRead(buffer, 0, buffer.Length, callbackRead, null);
    }
}

用法

bool running = true;
IPAddress localAddr = IPAddress.Parse("127.0.0.1");
TcpListener tcpListener = new TcpListener(localAddr, 3000);
tcpListener.Start();

while (running)
{
    while (!tcpListener.Pending())
    {
        Thread.Sleep(10);
    }

    Socket socket = tcpListener.AcceptSocket();
    Client client = new Client(socket);
    client.StartRead();
}