擁抱.net core的過程中, 將公司的一套java項目改成了.net core 2.0版的. 裡面的tcp服務被我用msdn的SocketAsyncEventArgs方式重寫了, 然而在測試的過程中發現, 偶爾會出現重啟無法再次綁定監聽的情況. 因為缺乏linux上編程的經驗, 對linux的認 ...
擁抱.net core的過程中, 將公司的一套java項目改成了.net core 2.0版的.
裡面的tcp服務被我用msdn的SocketAsyncEventArgs方式重寫了, 然而在測試的過程中發現, 偶爾會出現重啟無法再次綁定監聽的情況.
因為缺乏linux上編程的經驗, 對linux的認識過於粗淺, 僅憑現有的知識第一反應是, 是不是在asp.net core的結束時沒有清理乾凈, 也不是呀, 在lifetime中記錄了日誌都清楚地列印了.
打開搜索引擎, 搜linux下socket綁定失敗, 找到一條似乎有用的答案, socket options設置reuse address為true.
有道理, 於是在綁定前加了一條:
listener.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.ReuseAddress, true);
並沒有什麼軟用...
於是打算借鑒下開源項目中的做法. 翻了下supersocket, 發現尚未有支持netcore版, 於是跳過. 又翻了下dotnetty, 發現開始監聽和結束兩處都沒什麼區別.
突然想到asp.net core本身不就是能正常重啟監聽嗎? 那看源碼吧, 對了是Kestrel的源碼, 於是GitHub, download, 簡單搜索之後看到如下函數:
[DllImport("libc", SetLastError = true)] private static extern int setsockopt(int socket, int level, int option_name, IntPtr option_value, uint option_len); private const int SOL_SOCKET_OSX = 0xffff; private const int SO_REUSEADDR_OSX = 0x0004; private const int SOL_SOCKET_LINUX = 0x0001; private const int SO_REUSEADDR_LINUX = 0x0002; // Without setting SO_REUSEADDR on macOS and Linux, binding to a recently used endpoint can fail. // https://github.com/dotnet/corefx/issues/24562 private unsafe void EnableRebinding(Socket listenSocket) { var optionValue = 1; var setsockoptStatus = 0; if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { setsockoptStatus = setsockopt(listenSocket.Handle.ToInt32(), SOL_SOCKET_LINUX, SO_REUSEADDR_LINUX, (IntPtr)(&optionValue), sizeof(int)); } else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) { setsockoptStatus = setsockopt(listenSocket.Handle.ToInt32(), SOL_SOCKET_OSX, SO_REUSEADDR_OSX, (IntPtr)(&optionValue), sizeof(int)); } if (setsockoptStatus != 0) { _trace.LogInformation("Setting SO_REUSEADDR failed with errno '{errno}'.", Marshal.GetLastWin32Error()); } }
真相大白. socket option 設置reuse address即可.
然後解釋上面代碼的註釋里也給出了, 在這裡:
https://github.com/dotnet/corefx/issues/24562