【问题标题】:When I send a command to server from client , the client receives the response only if the request is sent twice当我从客户端向服务器发送命令时,客户端仅在请求发送两次时才会收到响应
【发布时间】:2017-10-03 00:24:21
【问题描述】:

我正在尝试向服务器发送命令,例如请求服务器发回其目录中的文件列表。问题是当我向服务器发送“list”命令时,我必须发送两次,以便服务器将文件列表发送回客户端。我确信服务器两次都收到命令,因为在服务器端我打印了应该在控制台上发送到客户端的结果,并且两次都出现。

我正在使用 C# 和 TCPListeners 来侦听传入的响应或命令,并使用 TCPClient 在服务器和客户端之间发送响应或命令。

客户端代码

    private TcpListener tcpListener = new TcpListener(9090);
    private void button3_Click(object sender, EventArgs e)
    {
        Byte[] bytesToSend = ASCIIEncoding.ASCII.GetBytes("list");
        try
        {
            TcpClient clientSocket = new TcpClient(serverIPFinal, 8080);
            if (clientSocket.Connected)
            {
                NetworkStream networkStream = clientSocket.GetStream();
                networkStream.Write(bytesToSend, 0, bytesToSend.Length);
               // networkStream.Close();
               // clientSocket.Close();
                thdListener = new Thread(new ThreadStart(listenerThreadList));
                thdListener.Start();
            }
        }
        catch
        {
            isConnectedLbl.Text = "Server not running";
        }
    }
    //Listener Thread to receive list of files.
    public void listenerThreadList()
    {

        tcpListener.Start();

        while (true)
        {
            handlerSocket = tcpListener.AcceptSocket();
            if (handlerSocket.Connected)
            {
                Control.CheckForIllegalCrossThreadCalls = false;
                lock (this)
                {
                    if (handlerSocket != null)
                    {
                        nSockets.Add(handlerSocket);
                    }
                }
                ThreadStart thdstHandler = new
                ThreadStart(handlerThreadList);
                Thread thdHandler = new Thread(thdstHandler);
                thdHandler.Start();
            }
        }
    }
    //Handler Thread to receive list of files.
    public void handlerThreadList()
    {

        Socket handlerSocketList = (Socket)nSockets[nSockets.Count - 1];
        NetworkStream networkStreams = new NetworkStream(handlerSocketList);

        int requestRead = 0;
        string dataReceived;
        byte[] buffer = new byte[1024];
        //int iRx = soc.Receive(buffer);
        requestRead = networkStreams.Read(buffer, 0, 1024);
        char[] chars = new char[requestRead];

        System.Text.Decoder d = System.Text.Encoding.UTF8.GetDecoder();
        int charLen = d.GetChars(buffer, 0, requestRead, chars, 0);
        dataReceived = new System.String(chars);

        Console.WriteLine(dataReceived);
        MessageBox.Show(dataReceived);

        //tcpListener.Stop();
        thdListener.Abort();


    }

服务器代码:

    TcpListener tcpListener = new TcpListener(8080);        
    public void listenerThreadCommands()
    {

        tcpListener.Start();
        while (true)
        {
            handlerSocket = tcpListener.AcceptSocket();

            if (handlerSocket.Connected)
            {
                Control.CheckForIllegalCrossThreadCalls = false;
                connections.Items.Add(
                handlerSocket.RemoteEndPoint.ToString() + " connected.");
                // clientIP = handlerSocket.RemoteEndPoint.ToString();
                lock (this)
                {
                    nSockets.Add(handlerSocket);
                }
                ThreadStart thdstHandler = new
                ThreadStart(handlerThreadCommands);
                Thread thdHandler = new Thread(thdstHandler);
                thdHandler.Start();
                //tcpListener.Stop();


                //handlerSocket.Close();
            }
        }

    }
    //Handler Thread to receive commands
    public void handlerThreadCommands()
    {
        Socket handlerSocketCommands = (Socket)nSockets[nSockets.Count - 1];

        NetworkStream networkStream = new NetworkStream(handlerSocketCommands);

        int requestRead = 0;
        string dataReceived;
        byte[] buffer = new byte[1024];
        requestRead = networkStream.Read(buffer, 0, 1024);
        char[] chars = new char[requestRead];

        System.Text.Decoder d = System.Text.Encoding.UTF8.GetDecoder();
        int charLen = d.GetChars(buffer, 0, requestRead, chars, 0);
        dataReceived = new System.String(chars);

        //connections.Items.Add(dataReceived);
        if (dataReceived.Equals("list"))
        {
            localDate = DateTime.Now;

            Files = Directory.GetFiles(System.IO.Directory.GetCurrentDirectory())
                                 .Select(Path.GetFileName)
                                 .ToArray();
            String FilesString = "";
            for (int i = 0; i < Files.Length; i++)
            {
                FilesString += Files[i] + "\n";
            }
            String clientIP = handlerSocketCommands.RemoteEndPoint.ToString();
            int index = clientIP.IndexOf(":");
            clientIP = clientIP.Substring(0, index);
            WriteLogFile(logFilePath, clientIP, localDate.ToString(), " ", "list");
            Console.WriteLine(clientIP);
            Console.WriteLine(FilesString);

            Byte[] bytesToSend = ASCIIEncoding.ASCII.GetBytes(FilesString);

            try
            {
                WriteLogFile(logFilePath, clientIP, localDate.ToString(), " ", "list-response");

                TcpClient clientSocket = new TcpClient(clientIP, 9090);
                if (clientSocket.Connected)
                {

                    NetworkStream networkStreamS = clientSocket.GetStream();
                    networkStreamS.Write(bytesToSend, 0, bytesToSend.Length);
                    networkStreamS.Close();
                    clientSocket.Close();
                    networkStream.Close();
                    //tcpListener.Stop();

                    // handlerSocketAuthenticate.Close();

                }
            }
            catch
            {
                Console.WriteLine("Cant send");
            }
        }

        else if (dataReceived.Equals("downloadfile"))
        {
            // handlerSocketAuthenticate.Close();
            // tcpListener.Stop();
            networkStream.Close();
            thdListenerDownload = new Thread(new ThreadStart(listenerThreadDownloading));
            thdListenerDownload.Start();
        }

        else
        {
            String clientIP1 = handlerSocketCommands.RemoteEndPoint.ToString();
            int index = clientIP1.IndexOf(":");
            clientIP1 = clientIP1.Substring(0, index);
            // handlerSocketAuthenticate.Close();
            CommandExecutor(dataReceived, clientIP1);
        }
    }

【问题讨论】:

  • 如果你不能给我们足够的代码来找出问题所在,我们能给你的只是同情。我不是说我们。但这至少是一种可能。
  • 除非您为客户端和服务器添加代码,否则没有人可以帮助您。
  • @Gusman 在这里我添加了对不起我没有注意到我没有粘贴代码
  • 您正在客户端上连接一个套接字,然后您又在客户端上执行tcpListener.AcceptSocket();。这是一团糟。把它全部扔掉,从头开始。 interwebz上有很多很好的例子,没有必要解开这个无望的烂摊子。

标签: c# sockets tcp server client


【解决方案1】:

您发布的代码有很多不同的问题,很难知道从哪里开始,而且不可能有信心在堆栈溢出的上下文中充分解决所有缺陷。也就是说,为了提供帮助,似乎值得一试:

  1. 套接字是双向的。客户端根本不需要使用TcpListener。 (按照惯例,“服务器”是“侦听”新连接的端点,而“客户端”是通过连接到侦听服务器来启动新连接的端点。)

    您应该只做从客户端到服务器的单个连接,然后使用该套接字向服务器发送和接收。
  2. 您将CheckForIllegalCrossThreadCalls 属性设置为false。这是邪恶的。发生的异常是为了帮助你。将该属性设置为 false 会禁用异常,但什么也不来防止异常旨在警告您的问题。

    您应该使用某种机制来确保当您访问 UI 对象时,您只能在拥有这些对象的线程中这样做。最原始的方法是使用Control.Invoke()。在现代 C# 中,最好使用 async/await。使用TcpClient,这很容易:您已经在使用GetStream() 来获取代表套接字的NetworkStream 对象,因此只需对该对象使用异步方法,例如ReadAsync(),或者如果您包装流在StreamWriterStreamReader 中,对that 对象使用异步方法,例如ReadLineAsync()
  3. 您正在检查TcpClient 对象的Connected 属性。这是没有意义的。当Connect() 方法返回时,您已连接。如果不是,则会引发异常。
  4. 您没有充分同步对nSockets 对象的访问。特别是,您在handlerThreadList() 方法中使用它的索引器。仅当您保证没有其他线程正在修改该对象时,并发使用该对象时这是安全的,而您的代码中并非如此。
  5. 您正在使用 ASCII 编码写入流,但使用 UTF8 编码读取。实际上,这并不是一个真正的问题,因为 ASCII 仅包含代码点 0-127,而这些代码点恰好映射到 UTF8 中的相同字符代码点。但这是真的糟糕的形式。选择一种编码,坚持下去。
  6. 您接受使用AcceptSocket(),但无论如何都将其包装在NetworkStream 中。为什么不直接使用AcceptTcpClient() 并调用GetStream() 呢? SocketTcpClient 都是很好的 API,但是在同一个程序中混合和匹配有点奇怪,并且可能会在以后导致一些混乱,试图保持直接你在哪里以及为什么使用。
  7. 您的代码假定handlerThreadCommands() 方法将始终以与接受连接的顺序完全相同的顺序被调用。也就是说,您使用nSockets[nSockets.Count - 1] 检索当前套接字。但是,由于 Windows 线程调度的工作方式,完全有可能在任何一个用于处理连接的线程启动之前接受两个或多个连接,结果只处理最近的连接,并且它由多个线程处理。
  8. 您假设命令字符串将作为完整的单元接收。但这不是 TCP 的工作方式。 TCP 仅保证如果您收到一个字节,它将相对于在它之前发送的所有字节按顺序排列。但是您可以接收任意数量的字节。特别是,你可以只接收一个字节,或者你可以接收多个相互连接的命令,或者你可以接收半个命令字符串,然后再接收另一半,或者一个命令的后半部分和命令的前半部分在实践中,这些问题不会出现在早期测试中,因为服务器没有在负载下运行,但后来它们很可能会出现。并且代码需要从一开始就设计为在这些条件下正常工作;稍后尝试修补错误代码要困难得多。

我不能说上面是代码中唯一的错误,但它们是最明显的,无论如何我认为上面的内容足以让你思考。

底线:您确实应该花更多时间查看优秀的网络示例,并真正了解它们的工作原理以及它们的编写方式。您需要为自己建立一个关于 TCP 协议如何工作的良好思维模型,并确保您非常小心地遵守规则。

我强烈推荐的一个资源是The Winsock Programmer's FAQ。它是很久以前为 .NET 之前的读者编写的,但其中包含的大部分信息在使用更高级别的网络 API 时仍然非常相关。

或者,不要尝试自己编写低级网络代码。有许多更高级别的 API 使用各种序列化技术来编码整个对象并为您处理所有较低级别的网络传输机制,使您可以专注于自己程序中的增值功能,而不是尝试重新发明轮子。

【讨论】:

    猜你喜欢
    • 2015-06-15
    • 1970-01-01
    • 2020-07-15
    • 1970-01-01
    • 1970-01-01
    • 2021-12-04
    • 2021-12-19
    • 2020-03-22
    • 1970-01-01
    相关资源
    最近更新 更多