windows 双工命名管道在某个写入时挂起
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/8069172/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Duplex named pipe hangs on a certain write
提问by MGB
I have a C++ pipe server app and a C# pipe client app communicating via Windows named pipe (duplex, message mode, wait/blocking in separate read thread).
我有一个 C++ 管道服务器应用程序和一个 C# 管道客户端应用程序,通过 Windows 命名管道(双工、消息模式、在单独的读取线程中等待/阻塞)进行通信。
It all works fine (both sending and receiving data via the pipe) until I try and write to the pipe from the client in response to a forms 'textchanged' event. When I do this, the client hangs on the pipe write call (or flush call if autoflush is off). Breaking into the server app reveals it's also waiting on the pipe ReadFile call and not returning. I tried running the client write on another thread -- same result.
一切正常(通过管道发送和接收数据),直到我尝试从客户端写入管道以响应表单 'textchanged' 事件。当我这样做时,客户端挂在管道写入调用上(如果自动刷新关闭,则刷新调用)。闯入服务器应用程序显示它也在等待管道 ReadFile 调用而不返回。我尝试在另一个线程上运行客户端写入 - 结果相同。
Suspect some sort of deadlock or race condition but can't see where... don't think I'm writing to the pipe simultaneously.
怀疑某种死锁或竞争条件,但看不到哪里......不要认为我正在同时写入管道。
Update1: tried pipes in byte mode instead of message mode - same lockup.
更新 1:在字节模式下尝试管道而不是消息模式 - 相同的锁定。
Update2: Strangely, if (and only if) I pump lots of data from the server to the client, it cures the lockup!?
更新 2:奇怪的是,如果(且仅当)我将大量数据从服务器泵送到客户端,它就可以解决锁定问题!?
Server code:
服务器代码:
DWORD ReadMsg(char* aBuff, int aBuffLen, int& aBytesRead)
{
DWORD byteCount;
if (ReadFile(mPipe, aBuff, aBuffLen, &byteCount, NULL))
{
aBytesRead = (int)byteCount;
aBuff[byteCount] = 0;
return ERROR_SUCCESS;
}
return GetLastError();
}
DWORD SendMsg(const char* aBuff, unsigned int aBuffLen)
{
DWORD byteCount;
if (WriteFile(mPipe, aBuff, aBuffLen, &byteCount, NULL))
{
return ERROR_SUCCESS;
}
mClientConnected = false;
return GetLastError();
}
DWORD CommsThread()
{
while (1)
{
std::string fullPipeName = std::string("\\.\pipe\") + mPipeName;
mPipe = CreateNamedPipeA(fullPipeName.c_str(),
PIPE_ACCESS_DUPLEX,
PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT,
PIPE_UNLIMITED_INSTANCES,
KTxBuffSize, // output buffer size
KRxBuffSize, // input buffer size
5000, // client time-out ms
NULL); // no security attribute
if (mPipe == INVALID_HANDLE_VALUE)
return 1;
mClientConnected = ConnectNamedPipe(mPipe, NULL) ? TRUE : (GetLastError() == ERROR_PIPE_CONNECTED);
if (!mClientConnected)
return 1;
char rxBuff[KRxBuffSize+1];
DWORD error=0;
while (mClientConnected)
{
Sleep(1);
int bytesRead = 0;
error = ReadMsg(rxBuff, KRxBuffSize, bytesRead);
if (error == ERROR_SUCCESS)
{
rxBuff[bytesRead] = 0; // terminate string.
if (mMsgCallback && bytesRead>0)
mMsgCallback(rxBuff, bytesRead, mCallbackContext);
}
else
{
mClientConnected = false;
}
}
Close();
Sleep(1000);
}
return 0;
}
client code:
客户端代码:
public void Start(string aPipeName)
{
mPipeName = aPipeName;
mPipeStream = new NamedPipeClientStream(".", mPipeName, PipeDirection.InOut, PipeOptions.None);
Console.Write("Attempting to connect to pipe...");
mPipeStream.Connect();
Console.WriteLine("Connected to pipe '{0}' ({1} server instances open)", mPipeName, mPipeStream.NumberOfServerInstances);
mPipeStream.ReadMode = PipeTransmissionMode.Message;
mPipeWriter = new StreamWriter(mPipeStream);
mPipeWriter.AutoFlush = true;
mReadThread = new Thread(new ThreadStart(ReadThread));
mReadThread.IsBackground = true;
mReadThread.Start();
if (mConnectionEventCallback != null)
{
mConnectionEventCallback(true);
}
}
private void ReadThread()
{
byte[] buffer = new byte[1024 * 400];
while (true)
{
int len = 0;
do
{
len += mPipeStream.Read(buffer, len, buffer.Length);
} while (len>0 && !mPipeStream.IsMessageComplete);
if (len==0)
{
OnPipeBroken();
return;
}
if (mMessageCallback != null)
{
mMessageCallback(buffer, len);
}
Thread.Sleep(1);
}
}
public void Write(string aMsg)
{
try
{
mPipeWriter.Write(aMsg);
mPipeWriter.Flush();
}
catch (Exception)
{
OnPipeBroken();
}
}
回答by kmcguire
If you are using separate threads you will be unable to read from the pipe at the same time you write to it. For example, if you are doing a blocking read from the pipe then a subsequent blocking write (from a different thread) then the write call will wait/block until the read call has completed and in many cases if this is unexpected behavior your program will become deadlocked.
如果您使用单独的线程,您将无法在写入管道的同时读取管道。例如,如果您正在从管道进行阻塞读取,然后是后续阻塞写入(来自不同线程),那么写入调用将等待/阻塞,直到读取调用完成,并且在许多情况下,如果这是意外行为,您的程序将陷入僵局。
I have not tested overlapped I/O, but it MAY be able to resolve this issue. However, if you are determined to use synchronous calls then the following models below may help you to solve the problem.
我没有测试重叠 I/O,但它可能能够解决这个问题。但是,如果您决定使用同步调用,那么下面的模型可能会帮助您解决问题。
Master/Slave
主从
You could implement a master/slave model in which the client or the server is the master and the other end only responds which is generally what you will find the MSDN examples to be.
您可以实现一个主/从模型,其中客户端或服务器是主服务器,而另一端仅响应,这通常是您会发现的 MSDN 示例。
In some cases you may find this problematic in the event the slave periodically needs to send data to the master. You must either use an external signaling mechanism (outside of the pipe) or have the master periodically query/poll the slave or you can swap the roles where the client is the master and the server is the slave.
在某些情况下,如果从站定期需要向主站发送数据,您可能会发现这有问题。您必须使用外部信号机制(管道外部)或让主服务器定期查询/轮询从服务器,或者您可以交换客户端是主服务器和服务器是从服务器的角色。
Writer/Reader
作家/读者
You could use a writer/reader model where you use two different pipes. However, you must associate those two pipes somehow if you have multiple clients since each pipe will have a different handle. You could do this by having the client send a unique identifier value on connection to each pipe which would then let the server associate the two pipes. This number could be the current system time or even a unique identifier that is global or local.
您可以使用写入器/读取器模型,其中使用两个不同的管道。但是,如果您有多个客户端,则必须以某种方式关联这两个管道,因为每个管道都有不同的句柄。您可以通过让客户端在连接到每个管道时发送一个唯一标识符值来实现此目的,然后让服务器关联这两个管道。该数字可以是当前系统时间,甚至是全局或本地的唯一标识符。
Threads
线程
If you are determined to use the synchronous API you can use threads with the master/slave model if you do not want to be blocked while waiting for a message on the slave side. You will however want to lock the reader after it reads a message (or encounters the end of a series of message) then write the response (as the slave should) and finally unlock the reader. You can lock and unlock the reader using locking mechanisms that put the thread to sleep as these would be most efficient.
如果您决定使用同步 API,如果您不想在从端等待消息时被阻塞,则可以使用具有主/从模型的线程。但是,您将希望在读取消息(或遇到一系列消息的结尾)后锁定阅读器,然后写入响应(如从站应该)并最终解锁阅读器。您可以使用将线程置于休眠状态的锁定机制来锁定和解锁读取器,因为这将是最有效的。
Security Problem With TCP
TCP 的安全问题
The loss going with TCP instead of named pipes is also the biggest possible problem. A TCP stream does not contain any security natively. So if security is a concern you will have to implement that and you have the possibility of creating a security hole since you would have to handle authentication yourself. The named pipe can provide security if you properly set the parameters. Also, to note again more clearly: security is no simple matter and generally you will want to use existing facilities that have been designed to provide it.
使用 TCP 而不是命名管道的损失也是最大的可能问题。TCP 流本身不包含任何安全性。因此,如果安全是一个问题,您将不得不实施它,并且您有可能创建一个安全漏洞,因为您必须自己处理身份验证。如果正确设置参数,命名管道可以提供安全性。此外,再次更清楚地注意:安全性不是一件简单的事情,通常您会希望使用旨在提供它的现有设施。
回答by Chris Dickson
I think you may be running into problems with named pipes message mode. In this mode, each write to the kernel pipe handle constitutes a message. This doesn't necessarily correspond with what your application regards a Message to be, and a message may be bigger than your read buffer.
我认为您可能会遇到命名管道消息模式的问题。在这种模式下,对内核管道句柄的每次写入都构成一条消息。这不一定与您的应用程序认为的消息相对应,并且消息可能大于您的读取缓冲区。
This means that your pipe reading code needs two loops, the inner reading until the current [named pipe] message has been completely received, and the outer looping until your [application level] message has been received.
这意味着您的管道读取代码需要两个循环,内部读取直到完全接收到当前的 [命名管道] 消息,外部循环直到您的 [应用程序级别] 消息被接收。
Your C# client code does have a correct inner loop, reading again if IsMessageComplete
is false:
您的 C# 客户端代码确实具有正确的内部循环,如果IsMessageComplete
为 false ,则再次读取:
do
{
len += mPipeStream.Read(buffer, len, buffer.Length);
} while (len>0 && !mPipeStream.IsMessageComplete);
Your C++ server code doesn't have such a loop - the equivalent at the Win32 API level is testing for the return code ERROR_MORE_DATA.
您的 C++ 服务器代码没有这样的循环 - Win32 API 级别的等效代码正在测试返回代码 ERROR_MORE_DATA。
My guess is that somehow this is leading to the client waiting for the server to read on one pipe instance, whilst the server is waiting for the client to write on another pipe instance.
我的猜测是以某种方式导致客户端等待服务器在一个管道实例上读取,而服务器正在等待客户端在另一个管道实例上写入。
回答by HOHO
It seems to me that what you are trying to do will rather not work as expected. Some time ago I was trying to do something that looked like your code and got similar results, the pipe just hanged and it was difficult to establish what had gone wrong.
在我看来,您尝试做的事情不会按预期进行。前段时间我试图做一些看起来像你的代码并得到类似结果的东西,管道只是挂了,很难确定出了什么问题。
I would rather suggest to use client in very simple way:
我宁愿建议以非常简单的方式使用客户端:
- CreateFile
- Write request
- Read answer
- Close pipe.
- 创建文件
- 写请求
- 阅读答案
- 关闭管道。
If you want to have two way communication with clients which are also able to receive unrequested data from server you should rather implement two servers. This was the workaround I used: here you can find sources.
如果您想与客户端进行双向通信,并且这些客户端也能够从服务器接收未请求的数据,您应该实现两个服务器。这是我使用的解决方法:here you can find sources。