dotnet / runtime

.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.
https://docs.microsoft.com/dotnet/core/
MIT License
15.16k stars 4.72k forks source link

Unix Socket Client Send WouldBlock Server Receive Data Error #23384

Closed caozhiyuan closed 4 years ago

caozhiyuan commented 7 years ago
        /// <summary>
        /// Exchange a message with the host.
        /// </summary>
        /// <param name="message">Message to send.</param>
        /// <returns>Message sent by the host.</returns>
        internal void Send(string message)
        {
            if (!this.isContinue)
            {
                Console.WriteLine($"isContinue {this.isContinue}");
                return;
            }
            if (this.connected)
            {
                var nioBuffers = new List<ArraySegment<byte>>();
                nioBuffers.Add(new ArraySegment<byte>(Encoding.UTF8.GetBytes(message)));

                SocketError errorCode;
                long localWrittenBytes = this.clientSocket.Send(nioBuffers, SocketFlags.None, out errorCode);
                if (errorCode != SocketError.Success && errorCode != SocketError.WouldBlock)
                {
                    throw new SocketException((int)errorCode);
                }
                if (errorCode == SocketError.WouldBlock)
                {
                    this.isContinue = false;
                    Console.WriteLine(message);
                    var sendArgs = new SocketAsyncEventArgs();
                    sendArgs.BufferList = nioBuffers;
                    sendArgs.Completed += this.SendArgs_Completed;
                    bool pending = this.clientSocket.SendAsync(sendArgs);
                    if (!pending)
                    {
                        Console.WriteLine($"pending false BytesTransferred {sendArgs.BytesTransferred}");
                    }
                    else
                    {
                        Console.WriteLine($"pending true");
                    }
                }
                else
                {
                    Console.WriteLine($"{nioBuffers.Sum(n => n.Array.Length)} {localWrittenBytes}");
                }
            }
            else
            {
                throw new SocketException((int)SocketError.NotConnected);
            }
        }

https://github.com/caozhiyuan/DotNetty/tree/dev/examples/SocketAsyncClient

https://github.com/caozhiyuan/DotNetty/tree/dev/examples/SocketAsyncServer

qq 20170830084137

[EDIT] Add C# syntax highlighting by @karelz

caozhiyuan commented 7 years ago

Server in Windows and Client in Ubuntu

caozhiyuan commented 7 years ago

when send wouldblock,it has sent some bytes qq 20170830191527

tmds commented 7 years ago

@caozhiyuan TCP is a 'stream' protocol. This means data sent in 1 operation by one peer, can arrive at the other peer in separate messages. It looks like you are observing a message being cut because it would go over the peer recv buffer size. Then you hit the WouldBlock on the next send because the OS is waiting for the peer to receive data.

IvanAntipov commented 7 years ago

I made a repro without DotNetty

Socket.Send returns 0, but 29200 bytes are transmited in network. (NonBlocking mode is enabled)

To Run test Depoloy TestTCP on Ubuntu14.04 1.1 Build 1.2 Copy files 1.3 Enable connections to port 8183 ( ufw allow 8183) 1.4 Start listener ( dotnet TestTCP.dll syncnoblock) On Windows (client) side 2.1 dotnet TestTCP get __your_ubuntu_ip_address

TestTCPSyncNoBlock.zip origin from https://github.com/Azure/DotNetty/issues/286

caozhiyuan commented 7 years ago

@tmds @IvanAntipov linux it always returns 0, 0 means no buffer send. in windows no this problem(29200 bytes are transmited in network)

tmds commented 7 years ago

@IvanAntipov please share the console output of client&server when running the steps.

IvanAntipov commented 7 years ago

@tmds

please share the console output of client&server when running the steps.

client server

caozhiyuan commented 7 years ago

@IvanAntipov both in windows , no this problem. now in linux ,I set socket blocking to true , dotnetty works ok.

tmds commented 7 years ago

@IvanAntipov Interesting. Can you share the output when running strace -f -e sendmsg dotnet TestTCP.dll syncnoblock?

IvanAntipov commented 7 years ago

@tmds

Can you share the output when running strace -f -e sendmsg dotnet TestTCP.dll syncnoblock?

root@test9:/usr/src/testtcp# strace -f -e sendmsg dotnet TestTCP.dll syncnoblockProcess 16245 attached Process 16244 attached Process 16246 attached Process 16247 attached Process 16248 attached Process 16249 attached ListenSyncSendNoBlock Process 16250 attached Process 16251 attached Bind Wait Process 16252 attached [pid 16251] +++ exited with 0 +++ Process 16253 attached Send Process 16254 attached [pid 16250] sendmsg(33, {msg_name(0)=NULL, msg_iov(1)=[{"<!DOCTYPE html>\r\n<html class=\" a"..., 127135}], msg_controllen=0, msg_flags=0}, 0) = 29200 [pid 16250] sendmsg(33, {msg_name(0)=NULL, msg_iov(1)=[{"pan class=\"x-phlinktext\">\320\230\320"..., 97935}], msg_controllen=0, msg_flags=0}, 0) = -1 EAGAIN (Resource temporarily unavailable) Sent count 0 Wait [pid 16253] +++ exited with 0 +++ [pid 16254] +++ exited with 0 +++ [pid 16250] +++ exited with 0 +++

tmds commented 7 years ago

@IvanAntipov Send should have returned 29200 (return value from first sendmsg call). Are you running .NET Core 2.0 release on the Linux machine?

IvanAntipov commented 7 years ago

@tmds

Send should have returned 29200 (return value from first sendmsg call). Are you running .NET Core 2.0 release on the Linux machine?

Yes, I running .Net Core 2.0 on Linux (Ubuntu 14.04). It should, but it doesnt :)

tmds commented 7 years ago

I think we arrive here: https://github.com/dotnet/corefx/blob/fe0e443ac01ee5a5afefb302b89f1504c3988eda/src/System.Net.Sockets/src/System/Net/Sockets/Socket.cs#L1210-L1220

errorCode is WouldBlock and 0 is returned instead of bytesTransferred.

tmds commented 7 years ago

Summary: Socket.Send returns 0 instead of the amount of bytes transferred when data is partially sent over a non-blocking socket.

\cc @stephentoub @geoffkizer @karelz

caozhiyuan commented 7 years ago

@tmds https://github.com/dotnet/corefx/blob/fe0e443ac01ee5a5afefb302b89f1504c3988eda/src/System.Net.Sockets/src/System/Net/Sockets/SocketPal.Unix.cs#L224

tmds commented 7 years ago

Minimal repro:

            var listener = new TcpListener(IPAddress.Loopback, 0);
            listener.Start();
            var client = new TcpClient();
            client.Connect(listener.LocalEndpoint as IPEndPoint);
            Socket socket = listener.AcceptSocket();

            socket.Blocking = false;
            var data = new byte[100_000_000];
            SocketError error;
            int bytesSent = socket.Send(data, 0, data.Length, SocketFlags.None, out error);

            Console.WriteLine($"{bytesSent} - {error}");

outputs:

0 - WouldBlock

strace shows:

sendmsg(26, {msg_name=NULL, msg_namelen=0, msg_iov=[{iov_base="\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., iov_len=100000000}], msg_iovlen=1, msg_controllen=0, msg_flags=0}, 0) = 2772174
sendmsg(26, {msg_name=NULL, msg_namelen=0, msg_iov=[{iov_base="\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., iov_len=97227826}], msg_iovlen=1, msg_controllen=0, msg_flags=0}, 0) = -1 EAGAIN (Resource temporarily unavailable)

The output should have been:

2772174 - Success
caozhiyuan commented 7 years ago

@tmds @stephentoub is has any idea when to fix it, we need use it online

caozhiyuan commented 7 years ago

@tmds wo now use Socket.SendAsync(operation) , async method no bug

tmds commented 7 years ago

@caozhiyuan that is the preferred way of working: :+1: