阻塞SOCKET通讯问题。(200分)

Z

zlbati9

Unregistered / Unconfirmed
GUEST, unregistred user!
我使用阻塞SOCKET,服务端已写好从TServerClientThread继承下来的线程。
在客户端,我也使用一个从TThread继承下来的线程来通讯。仿照DELPHI的帮助,
procedure TClientSocketThread.Execute;
begin
TheStream := TWinSocketStream.Create(aSocket, 60000);
try
{ fetch and process commands until the connection or thread is terminated }
while (not Terminated) and (Form1.ClientSocket1.Active) do
begin
ReceiveData(TheStream);
end;
finally
TheStream.Free;
end;
end;
其中ReceiveData是一个读写TWinSocketStream的过程。我用此完成了传一个1M文件(当然,自己定义了每次传的包的结构)。但是,我的客户端
在接受完毕后就不能再次完成上述工作了。第一次用ClientSocket1.Socket.SendBuf(buffer, sizeof(databuffer));来发出请求的,在线程内部是直接读写
TWinSocketStream来发出请求。
是不是不正确?当我再次发出请求时,无反应。也就是说,在线程外部向服务端发请求时,应该用哪种方法?
请大家帮帮我!
 
希望大家给点意见。很明确,在客户端用TWinSocketStream来和服务端联系,但TWinSocketStream
是线程的成员变量。你们都是用哪种方法来触发和服务端联系的?我是在线程外面ClientSocket1.Socket.SendBuf(buffer, sizeof(databuffer))
我觉得有问题。
 
不明白你说什么,为什么非要用阻塞式呢?用异步模式不是很好吗?
 
因为要连几百个客户端,异步不是很好。我就是问用哪种方法来触发和服务端联系的。
 
我现在知道你说什么了,你是在用户线程里接收服务器的数据,在主线程发送数据给服务器。
你这样做多累啊,这样要在主线程和用户线程间进行同步工作。
一开始用户线程挂起;
主线程中按下一个按钮或其它操作就将需要发送的数据传给用户线程,并唤醒它,让它发送数据;
用户线程发送完数据后等待服务器返回数据;
用户线程接收返回数据完毕后通知主线程获取返回数据,然后自己将自己挂起,等待新的命令。

我建议你不要用阻塞式socket,用非阻塞式的socket可以省很多事。
 
用异步sokcet一样可以支持几百个客户端,客户端连接服务器后,一般都是处于
空闲状态,当需要访问服务器时就发送一个命令过去,服务器端在onclientread事件里
获得客户端的命令和客户端socket,如果这个命令要求执行耗时操作,那么马上建一个线程
来服务它,否则可以直接处理。这样的话,你的服务器上的线程数目就会大大减少,只有在
处理客户请求时才启动新的线程。如果你是当客户端一连上来马上用一个线程服务它,那么
来多少个客户就建多少个线程,假如有几百万个客户端,岂不是要建几百万个线程?
而且这些线程大多时间是空闲的,这样很浪费资源。
 
万分感谢你的答复。问题解决马上给分。

你的第一次答复知道了我问的问题。但是,我很想知道,我现在的通讯是双向的,无法将客户端用户线程
挂起,不知道服务器何时就来了信息。也就是说,读写操作都在客户端用户线程里处理,但有时又是
客户端主线程提请求。

关于第2次答复,是这样的。我现在无法使用异步的方式(各种原因)。我很清楚异步的写法。
可以用一个TLIST记载客户请求包,开辟一个线程专门处理TLIST里的请求包。
请帮我看看,谢谢!
 
并没有什么异步比同步好的理论。
比如缴费。
客户方----------->银行方
<-----------
客户方发包过去必须收到银行一个同步的确认信息就必须时阻塞。 然后再反馈回一个执行结果
就可以是异步信息,非阻塞。
------
服务端选择线程阻塞模式:客户端是否阻塞都没有问题。


【我用此完成了传一个1M文件(当然,自己定义了每次传的包的结构)。但是,我的客户端
在接受完毕后就不能再次完成上述工作了。第一次用ClientSocket1.Socket.SendBuf(buffer, sizeof(databuffer));来发出请求的,在线程内部是直接读写
TWinSocketStream来发出请求。】
如果客户端选择阻塞模式: 创建TWinSocketStream ---发送数据---等待---接收---释放TWinSocketStream.
开始下一次交互对话,就应该再创建一个TWinSocketStream.
如果你想循环的发送接收数据应该再ReceiveData()函数里面创建和释放TWinSocketStream

 
给一点代码吧。 CB 的。但应该可以找到你想要的东西。


Execute 函数中:
while(true)
{

if (!EXIT_FLAG)
{
try
{
TList *pList=AccList->LockList(); //AccList线程链表加锁
if (pList->Count>0)
{
Node=(TAccNode *)pList->Items[0];
HasGetValue=true;
pList->Delete(0);
}
}
__finally
{
AccList->UnlockList();//AccList线程链表解锁
}
if (HasGetValue)
{
ProcAcc(Node);
delete Node;
Node==NULL;
}
HasGetValue=false;

}
else
break;

Application->ProcessMessages();
}
Terminate();
---------------------
ProcAcc(Node)函数:
------部分函数
TWinSocketStream *Stream=new TWinSocketStream(CS->Socket,10000);
try
{
Stream->Write(sTmpPack.c_str(),sTmpPack.Length());
ReadRsp(Stream); //等待数据并处理
}
__finally
{
delete Stream;
Stream=NULL;
}
------------------------


 
我指的是客户端本身的主线程和你从Tthread继承下来的用户线程之间的同步,这2个线程都
在客户端里啊。我写个简单的流程给你看看:
首先你创建用户线程时要把它挂起:clientThread := TClientSocketThread.create(true);

然后主线程里要发送数据给服务器时:
TForm1.onButton1Clicke(Sender: TObject);
begin
clientThread.buffer := 'command'; //buffer是用户线程中的成员用于存储发送数据,是string类型
clientThread.needResponse := true; //这个命令是否需要返回数据
clientThread.resume; //唤醒用户线程让它去发送命令
//如果需要返回数据则等待
end;

用户线程的execute方法:
procedure TClientSocketThread.Execute;
begin
TheStream := TWinSocketStream.Create(aSocket, 60000);
try
{ fetch and process commands until the connection or thread is terminated }
while (not Terminated) and (Form1.ClientSocket1.Active) do
begin
TheStream.Write(buffer, Length(buffer) + 1);//发送数据
if needResponse then
begin
if TheStream.WaitForData(1000000) then
begin
ReceiveData(TheStream);
//通知主线程获取返回数据
suspend;
end
else
//超时处理
end;
end;
finally
TheStream.Free;
end;
end;

这只是一个想法,具体实现可能有很多问题,希望对你有帮助。
 
刚刚贴完,就发现了新的问题。
如果多线程访问TList 可能导致冲突。
可以使用TEvent +TCriticalSection 来控制对TList的访问。
有个class 是 TThreadList 可以安全的读写。 上面代码有用到。
 
TO DiamondKing:感谢你的回答!
你是说开始下一次交互对话,就应该再创建一个TWinSocketStream?是这样吗?
那我的客户端用户线程就不能如下写喽?
procedure TClientSocketThread.Execute;
begin
TheStream := TWinSocketStream.Create(aSocket, 60000);------这个就要放到ReceiveData里面去???
try
{ fetch and process commands until the connection or thread is terminated }
while (not Terminated) and (Form1.ClientSocket1.Active) do
begin
ReceiveData(TheStream);
end;
finally
TheStream.Free;
end;
end;
那是不是我在客户端用户线程外面ClientSocket1.Socket.SendBuf(buffer, sizeof(databuffer));也是对的??
你试过每次TWinSocketStream创建吗,效率如何?谢谢你的帮助!
 
感谢2位大虾的帮助,我先小结一下。
zhjwjan大虾的意见是在客户端用户线程里读写,客户端主线程提请求。使用同一个TWinSocketStream。
DiamondKing大虾的意见使用链表记载请求,也是客户端主线程提请求加入链表,每有请求,单独创建TWinSocketStream进行读写。
是这样吧?!我先去试试。



 
1你是说开始下一次交互对话,就应该再创建一个TWinSocketStream?是这样吗?
2那我的客户端用户线程就不能如下写喽?
3那是不是我在客户端用户线程外面ClientSocket1.Socket.SendBuf(buffer, sizeof(databuffer));也是对的??
4你试过每次TWinSocketStream创建吗,效率如何?

--1 是
--2 应该在ReceiveData里面创建。
-- 3 我觉得不妥。 你最好是在线程中创建 TClientSocket.
--- 4 不是试过,一直都这么在用,上面得例子里面就有。不过一般都是同步+异步一起。
一个同步发起(同时得到确认) ,一个异步等待接收。
你可以使用链表,并不是一定要。

还有我不是大虾,只是说一点个人意见。


 
to DiamondKing:
再次看了你的BCB的例子,发现所有的请求都是客户端发起的,是这样吗?就是说,每次将到来的
服务断的回复,你都是心里清楚的。

但要是我的服务端在客户端没有请求的情况下也给客户端发数据应该如何处理呢?

还有很感谢你的帮助。
 
发现所有的请求都是客户端发起的,是这样吗? ---是。因为你必须得到同步的确认信息。
-----
那也很Easy.我再贴服务端给客户端发的代码。原理是一样的。
 
void __fastcall TSvrThread::ClientExecute()
{
//---- Place thread code here ----

ShowSysMsg(rsSvrThreadStart, clRed);
memset(Buffer,0,BufLen);
iBufPos=0;
TWinSocketStream *Stream;
while(!Terminated &amp;&amp;ClientSocket->Connected)
{
try
{
Stream=new TWinSocketStream(ClientSocket,FormMain->IbsParams->TimeOut*1000);
try
{
if (Stream->WaitForData(FormMain->IbsParams->TimeOut*1000))
{
try
{
iRcvCnt=Stream->Read(Buffer+iBufPos,BufLen-iRcvCnt);
}
catch(...)
{
iRcvCnt=0;
}
if (iRcvCnt==0)//对方连接已经断开
ClientSocket->Close();
else
{
if (FormMain->Params->WriteFile)//对接收数据写日志
{
AnsiString sTmpPath=ExtractFilePath(Application->ExeName);
AnsiString sTmpPacket=AnsiString(Buffer);
WriteDmpFile(sTmpPath,sTmpPacket,pfSERVERRECEIVE); //服务端接收包
}

iBufPos+=iRcvCnt;
ProcRcvData(Stream);//处理数据***********
}
} //end if

}
__finally
{
delete Stream;
Stream=NULL;
}


}
catch(...){}

} //end while


Terminate();


}
---------------------------------------
服务端给客户端主动发数据。服务端使用线程阻塞模型。
从TServerClientThread继承。



 
明白你的意思了。
你不知道什么时候得到数据,可以考虑在客户端开一个异步的Socket Server进行监听。
 
用线程池来解决问题比较好,用异步的效率太低了
 
顶部