请教一个有关socket接收数据的多线程问题?!(100分)

  • 主题发起人 主题发起人 ranyang
  • 开始时间 开始时间
R

ranyang

Unregistered / Unconfirmed
GUEST, unregistred user!
有cs系统!客户端用tcpclientsocket接收数据,由于接收的数据量大,整个程序就一个主线程,然后在接收数据和解析数据时就没办法操作别的。现在我想开一个线程专门接收和解析socket数据,要怎么做呀?我不会做这种!就是socket等于是在线程里等待和接收的,不知道我说明白了没?!大家会的帮帮我!
 
用 TcpClientSocket 的非阻塞模式不行吗?
 
http://www.delphibbs.com/keylife/iblog_show.asp?xid=23210
 
我想知道怎么在线程里响应tclient的read事件呀!!!可以这样吗?!
 
问题: socket多线程通讯,请高人相助! ( 积分: 100 )
分类: 局域网 / 通讯

来自: NeverMind, 时间: 2005-08-08 16:57:00, ID: 3160151
client端:
type
TClientThread = class(TThread)
private
{ Private declarations }
IP: string;
PortNo: integer;
SendFile: string;
ClientSocket: TClientSocket;
reConnectTime: integer;
iPackID: integer;
iLeftSize: integer;
PackCount: integer;
fileStream: TFileStream;
protected
procedure Execute; override;
procedure SetReConnectTime(Time: integer);
public
constructor Create(CreateSuspended: Boolean;HostIP: string;HostPort: integer;FileName: string);
destructor Destory;
procedure ClientSocketError(Sender: TObject;Socket: TCustomWinSocket;ErrorEvent: TerrorEvent; var ErrorCode: Integer);
procedure ClientSocketConnect(Sender: TObject;Socket: TCustomWinSocket);
procedure ClientSocketOnRead(Sender: TObject;Socket: TCustomWinSocket);
function Open: Boolean;
function GetPackCount(var FileName:String):Integer;
procedure UpdateData(var iLeftSize: integer; DataPack: TPackRecord);
property ConnectTime: integer read reConnectTime write SetReConnectTime;
end;

implementation


procedure TClientThread.ClientSocketConnect(Sender: TObject;
Socket: TCustomWinSocket);
var
PackHead: TPackHead;
begin
ClientSocket.Active := True;
PackHead.PackType := ptFirst;
PackHead.ThreadID := Self.ThreadID;
PackHead.FileName := Copy(SendFile,StringLocation('/',SendFile)+1,Length(SendFile)-StringLocation('/',SendFile)+1);
PackHead.PackCount := PackCount;
ClientSocket.Socket.SendBuf(PackHead,SizeOf(PackHead));
end;

procedure TClientThread.ClientSocketError(Sender: TObject;
Socket: TCustomWinSocket; ErrorEvent: TerrorEvent;
var ErrorCode: Integer);
begin
ClientSocket.Close;
ErrorCode := 0;
end;

procedure TClientThread.ClientSocketOnRead(Sender: TObject;
Socket: TCustomWinSocket);
var
Protocol: TProtocols;
Command: TProtocol;
DataPack: TPackRecord;
begin
while (not Terminated) and (not suspended) do
begin
Protocol := [START,NEXT,OVER];
DelayTime(10);
Socket.ReceiveBuf(Command,Socket.ReceiveLength);
if (Command in Protocol) and (SendFile <> '') then
begin
case Command of
START: begin
fileStream := TFileStream.Create(SendFile,fmOpenRead,fmShareDenyWrite);
iLeftSize := fileStream.Size;
end;
OVER: begin
ClientSocket.Close;
Terminate;
Destory;
end;
end;
UpdateData(iLeftSize,DataPack);
end;
end;
end;

constructor TClientThread.Create(CreateSuspended: Boolean; HostIP: string;
HostPort: integer;FileName: string);
begin
inherited create(true);
FreeOnTerminate := True;
ClientSocket := TClientSocket.Create(nil);
IP := HostIP;
PortNo := HostPort;
with ClientSocket do
begin
Address := IP;
Port := PortNo;
ClientType := ctNonBlocking;
OnError := ClientSocketError;
OnConnect := ClientSocketConnect;
OnRead := ClientSocketOnRead;
end;

SendFile := FileName;
PackCount := GetPackCount(SendFile);
end;


procedure TClientThread.SetReConnectTime(Time: integer);
begin
if ReConnectTime <> Time then
ReConnectTime := Time;
end;


procedure TClientThread.Execute;
begin
Open;
end;

function TClientThread.Open: Boolean;
var
i: integer;
begin
i := 0;
while i < reConnectTime do
begin
ClientSocket.Open;
DelayTime(150);
if ClientSocket.Active then
Break;
inc(i);
end;
result := ClientSocket.Active;
end;

destructor TClientThread.Destory;
begin
ClientSocket.Free;
fileStream.Free;
inherited free;
end;

function TClientThread.GetPackCount(var FileName: String): Integer;
var
Stream: TFileStream;
begin
Stream := TFileStream.Create(FileName,fmOpenRead,fmShareDenyWrite );
if (Stream.Size mod CACHESIZE) <> 0 then
PackCount := (Stream.Size div CACHESIZE)+1
else
PackCount := Stream.Size div CACHESIZE;
Stream.Free;
result := PackCount;
end;

procedure TClientThread.UpdateData(var iLeftSize: integer;DataPack: TPackRecord);
begin
if iLeftSize <= CACHESIZE then
begin
with DataPack do
begin
PackType := ptLast;
Size := iLeftSize;
PackID := iPackID+1;
fileStream.Read(Cache,iLeftSize);
iLeftSize := 0;
end;
end
else
begin
with DataPack do
begin
PackType :=ptNormal;
Size :=CACHESIZE;
PackID :=iPackID+1;
Inc(iPackID);
fileStream.Read(Cache,CACHESIZE);
iLeftSize:=iLeftSize - CACHESIZE;
end;
end;
ClientSocket.Socket.SendBuf(DataPack,SizeOf(DataPack));
//DelayTime(100);
end;


//*************************************************************************//
Server端



type
TServerThread = class(TServerClientThread)
private
{ Private declarations }
ReceiveFileName :String;
fileStream :TFileStream;
iPackID :cardinal;
protected
//procedure Execute; override;
procedure ClientExecute;override;
public
Constructor Create(CreateSuspended:Boolean;ASocket:TServerClientWinSocket);virtual;
destructor Destory;virtual;
procedure OnAccept;
procedure ClientRead;
//function StartConnect: Boolean;virtual;
end;


implementation

procedure TServerThread.ClientExecute;
var
Stream : TWinSocketStream;
begin
while not Terminated and ClientSocket.Connected do
begin
try
Stream := TWinSocketStream.Create(ClientSocket, 60000);
try
if Stream.WaitForData(60000) then
begin
OnAccept;
fileStream := TFileStream.Create('d:/'+ReceiveFileName,fmCreate,fmShareExclusive);
ClientRead;
Destory;
end
else
ClientSocket.Close;
finally
Stream.Free;
end;
except
HandleException;
end;
end;
end;

procedure TServerThread.ClientRead;
var
PackType :TPackTypes;
DataRecord :TPackRecord;
Protocol :TProtocol;
begin
while not terminated and ClientSocket.Connected do
begin
PackType :=[ptNormal,ptLast];
ClientSocket.ReceiveBuf(DataRecord,ClientSocket.ReceiveLength);
if (DataRecord.PackType in PackType) and (DataRecord.PackID = iPackID+1) then
begin

fileStream.Write(DataRecord.Cache,DataRecord.Size);
case DataRecord.PackType of
ptNormal:
begin
iPackID :=DataRecord.PackID;
Protocol :=NEXT;
ClientSocket.SendBuf(Protocol,SizeOf(Protocol));
end;
ptLast:
begin
Protocol :=OVER;
Sleep(50);
ClientSocket.SendBuf(Protocol,SizeOf(Protocol));
DelayTime(100);
self.Destory;
end;
end;
end;
end;
end;

constructor TServerThread.Create(CreateSuspended: Boolean;ASocket: TServerClientWinSocket);
begin
inherited Create(false,ASocket);
FreeOnTerminate := True;
end;

destructor TServerThread.Destory;
begin
Terminate;
fileStream.Free;
inherited free;
end;

procedure TServerThread.OnAccept;
var
MainPack :TPackHead;
Command :TProtocol;
PackType :TPackTypes;
begin
ClientSocket.ReceiveBuf(MainPack,ClientSocket.ReceiveLength);
PackType :=[ptFirst];
if MainPack.PackType in PackType then
begin
ReceiveFileName :=MainPack.FileName;
Command :=START;
ClientSocket.SendBuf(Command,SizeOf(Command));
end;
end;

end.



//**************************************************************************//
公用部分
const CACHESIZE=1024; //若不是1024,则在测试传送文本时产生乱码,不解!---问题2

type
TProtocol = (START,NEXT,OVER);
TProtocols = set of TProtocol;
TPackType = (ptFirst,ptNormal,ptLast);
TPackTypes = set of TPackType;
TNetStatus = (nsNone,nsConnected,nsConnectFail,nsTransOver);

TPackRecord = packed record
PackType :TPackType;
Size :Integer;
PackID :Cardinal;
Cache :Array [1..CACHESIZE] of Char;
end;

type
TPackHead = packed Record
PackType :TPackType;
ThreadID :Integer;
FileName :String[255];
PackCount :Integer;
end;


var
tsThread: TList;
CS: TRTLCriticalSection;
hMutex: THandle = 0;

procedure DelayTime(_T :Cardinal);
function StringLocation(strTraget:string;strSource:string):Integer;
implementation


procedure DelayTime(_T:Cardinal);
var
StartTime :Cardinal;
begin
StartTime :=GetTickCount;
while (GetTickCount-StartTime) < _T do
Application.ProcessMessages;
end;


function StringLocation(strTraget:string;strSource:string):Integer;
var
iIndex:Integer;
begin
iIndex:=Length(strSource)-1;
while strTraget<>strSource[iIndex] do
begin
iIndex := iIndex -1 ;
end;
if strTraget=strSource[iIndex] then
Result:=iIndex
else
Result:=-1;
end;

end.

呵呵,东西多了点,让大伙看着受累了!我现在就是单文件传送,都很不稳定:1.)传送文件,有时候成功,有时候不成功;2.)见公用部分第一行;3.)传送速度慢!
盼高人相助,不胜感激!

来自: NeverMind, 时间: 2005-08-09 9:15:06, ID: 3160657
是不是太长了,大伙都不愿意看呢?

来自: lxw5214, 时间: 2005-08-09 9:29:03, ID: 3160681
用indy的组件,本身就是多线程的,应该更方便。还没详细研究过socket,惭愧!

来自: smokingroom, 时间: 2005-08-09 9:38:12, ID: 3160692
你这样写代码,完全失去了TCP连接的优势,得到的效果,跟UDP没什么两样。TCP连接的优
势是什么?根本用不着你的把数据拆分成一个一个的数据包,接收后再一个一个分拆、判断
。。。其实只需要使用ReceiveBuf()与SendBuf()就可以了。你肯定会问,Client与Server
端如何知道要传送什么文件?文件大小是什么?其实你只要参考一下FTP的原理,什么都
明白了。

来自: 13708782004, 时间: 2005-08-09 9:53:08, ID: 3160735
www.51merit.com
上有

来自: NeverMind, 时间: 2005-08-09 11:30:22, ID: 3160910
非常感谢smokingroom!我对FTP的原理知之甚少,还请指点一二!
13708782004,呵呵,上面的东东下不了啊!

来自: djh_djh, 时间: 2005-08-09 12:35:44, ID: 3161004
反对 smokingroom,

TCP 也要拆包的,
如果包太大了, 某个客户端就会带宽用完,
 别人的数据就发不了了,特别是如果别人用的是UDP 全会丢掉

流量控制是要做的


没有见到你的线程 中有锁

来自: jlyin, 时间: 2005-08-09 15:21:34, ID: 3161259
客户端没必要用多线程;

在SERVERSOCKET1中可选择非阻塞方式或线程阻塞方式;
在ClientSOCKET1中可选择非阻塞方式或阻塞方式;

不用这么麻烦吧

来自: NeverMind, 时间: 2005-08-09 16:59:28, ID: 3161387
jlyin,我是想在客户端同时传送多个文件到server端,client与server是N:M得关系,如果不用多线程,那用什么方法呢?

来自: jlyin, 时间: 2005-08-09 17:13:20, ID: 3161411
不用多线程也示尚不可,
我测试的聊天程序,
SERVER只开一个SOCKET端口,按线连接:
ServerSocket1.Socket.Connections.SendText('&Iacute;&oslash;&sup1;&Uuml;:'+TempStr);
同一台电脑可开多个客户端,效果相当,

你客户端用多线程,是否每个线程的通讯,选择服务器的SOCKET不同?

来自: NeverMind, 时间: 2005-08-09 17:54:44, ID: 3161449
jlyin,是的

来自: NeverMind, 时间: 2005-08-11 8:27:18, ID: 3162978
怎么莫人回答俺的问题啊

来自: painboy, 时间: 2005-08-11 10:08:55, ID: 3163133
以下是用线程和阻塞方式写的。
const FileBufferSize = 1024;

type TSendThread = Class(TThread)
private
ClientSocket : TCustomWinSocket;
procedure EXecute; override;
end;

type TRecvThread = class(TServerClientThread)
procedure ClientExecute; override;
end;

//这里是CLIENT端(一般都是发送方)
procedure TSendThread.Execute;
var
FileBuffer : Array [0..FileBufferSize-1] of byte;
FileToSend : file of byte;
ReadNumber : integer;
theStream : TWinSocketStream;
begin
theStream := TWinSocketStream.Create(ClientSocket,120000);
AssignFile(FileToSend,'d:/abc.zip');
Reset(FileToSend);

while (not Terminated) and (ClientSocket.Connected) do begin
BlockRead(FileToSend,FileBuffer,FileBufferSize,ReadNumber); //读1024字节
if ReadNumber>0 then
theStream.Write(FileBuffer,ReadNumber); //发1024字节
if (ReadNumber=0) or (ReadNumber<>FileBufferSize) then break; //文件读完,退出
end;

CloseFile(FileToSend);
theStream.Free;
ClientSocket.Close;
end;


//这里是SERVER端(一般都是接收方)
procedure TRecvThread.ClientExecute;
var
FileBuffer : Array [0..FileBufferSize-1] of byte;
FileHandle : Integer;
ReadNumber : integer;
theStream : TWinSocketStream;
begin
theStream := TWinSocketStream.Create(ClientSocket,120000);
FileHandle:=FileCreate('d:/abc.zip');

while (not Terminated) and (ClientSocket.Connected) do begin
if theStream.WaitForData(10) then begin
try
theStream.Read(FileBuffer,ReadNumber); //接收数据流
if ReadNumber<>0 then
FileWrite(FileHandle,FileBuffer,ReadNumber); //写入文件
else
Terminate; //对方关闭连接,传输完毕。线程结束
end;
except
Terminate; //传输出错,线程结束
end;
end;
end;

FileClose(FileHandle);
theStream.Free;
ClientSocket.Close;
end;

当客户端要传输文件时,先连接入服务端,而服务端就会启动一接收线程准备接收。然后,客户端就可用TSendThread.Create来启动传输线程进入传输了。
当然,这里只给出了文件的传输和接收部分,好多差错检测以及传输之前的连接、文件信息的交换就由大家处理啦!比较简单,但代码会不少!

来自: NeverMind, 时间: 2005-08-18 15:06:38, ID: 3171540
多人接受答案了。

得分大富翁: painboy-100,
 
我不想考虑服务端!而且服务段不是用delphi开发的。我的client接收数据,
我想在把socketclient放在线程里怎么接收?!
 
我刚写了一个,你可以参考。
最简单的办法,使用IdThreadComponent。这个太简单了。
在他的onrun事件里写Threadrecv.Synchronize(read);
read为你接收数据的程序。
我程序的一部分
procedure TfrmMain.read;
var
stringdd:string;
begin
if OutputCommQueue.Count<>0 then
begin
IdTCPClient1.Write(string(OutputCommQueue.Pop^));
try
stringdd:=IdTCPClient1.ReadLn(#$7e#$0d);
except end;
if stringdd<>'' then
begin
WriteInCommQueue(stringdd);
end;
end;
sleep(300);
end;
end;
更简单的办法把read放到timer中,500MS一次,indy dome中的chat例子就是这样做的。
但是上面的方法在数据比较大的情况下,尤其应为其为阻塞式,所以会导致程序很慢。
这时就应该新开个线程做读取,这在indy demo的 indy TCP demo里也有详细的例子。例子你可以自己看,不过它那种写法对不了解多线程的人困难比较大。
我最初是这样做的,很简单:
使用thread object 向导建立一个单元,自己写也行,代码如下
unit hhf;

interface

uses
Classes,MainForm;

type
Thrwww = class(TThread)
private
protected
procedure Execute; override;
end;

implementation

procedure Thrwww.Execute;
begin
//write;
frmMain.read; //你在这里可以加一个判断,无线程中断的话,一直执行。干脆加个while (true)也行
end;
end.



完工。
线程调用:
在要调用的地方
Thrwww.creat(false);
就这么简单
别忘了在uses里加上引用单元
 
六楼强啊
 
我是初学!不太懂!楼上大哥也帮帮忙!
 
网中戏 大哥,您才是真正的高手呢,我之前还承蒙您的照顾呢。
我的indy都是从这里学的,dfw里有几位前辈给了不少指点。
3个月前,我连什么是indy和多线程都不知道,[:)]。
都是项目逼出来的啊。
ranyang,感觉indy部分你应该没有什么问题了,抽时间看看多线程的编程部分,如果只是现在的项目的话,先简单看看delphi的thread object 向导就行,你会发现线程的编写其实很简单。
我也是新手,学习的delphi时间不长。
我的QQ:522549887
大家共同学习。
 
unit u_clientsocketthread;

interface

uses
Classes, ScktComp, SysUtils;

type
TclientsocketThread = class(TThread)
private
IP: string;
str: string;
Port: integer;
ClientSocket: TClientSocket;
ReConnectTime: integer;
{ Private declarations }
protected
procedure Execute; override;
public
constructor Create(HostIP: string;HostPort: integer);
destructor Destory;
procedure ClientSocketDisconnect(Sender: TObject;
Socket: TCustomWinSocket);
procedure ClientSocketConnecting(Sender: TObject;
Socket: TCustomWinSocket);
procedure ClientSocketError(Sender: TObject;Socket:
TCustomWinSocket;ErrorEvent: TerrorEvent; var ErrorCode: Integer);
procedure ClientSocketConnect(Sender: TObject;Socket:
TCustomWinSocket);
procedure ClientSocketOnRead(Sender: TObject;Socket: TCustomWinSocket);
procedure SetReConnectTime(Time: integer);
function Open: Boolean;
procedure UpdateCaption;
end;

implementation

uses umain;

{ Important: Methods and properties of objects in visual components can only be
used in a method called using Synchronize, for example,

Synchronize(UpdateCaption);

and UpdateCaption could look like,

procedure Tclientsocket.UpdateCaption;
begin
Form1.Caption := 'Updated in a thread';
end; }

{ Tclientsocket }

constructor TclientsocketThread.Create(HostIP: string;HostPort: integer);
begin
inherited create(False);
ClientSocket := TClientSocket.Create(nil);
IP := HostIP;
Port := HostPort;
with ClientSocket do
begin
Address := IP;
Port := Port;
ClientType := ctNonBlocking;
OnError := ClientSocketError;
OnConnect := ClientSocketConnect;
OnRead := ClientSocketOnRead;
OnConnecting :=ClientSocketConnecting;
OnDisconnect :=ClientSocketDisconnect;
end;
FreeOnTerminate := True;
end;

destructor TclientsocketThread.Destory;
begin
ClientSocket.Free;
inherited free;
end;

procedure TclientsocketThread.ClientSocketDisconnect(Sender: TObject;
Socket: TCustomWinSocket);
begin
//
end;

procedure TclientsocketThread.ClientSocketConnecting(Sender: TObject;
Socket: TCustomWinSocket);
begin

end;

procedure TclientsocketThread.ClientSocketError(Sender: TObject;Socket:
TCustomWinSocket;ErrorEvent: TerrorEvent; var ErrorCode: Integer);
begin
ClientSocket.Close;
ErrorCode := 0;
end;

procedure TclientsocketThread.ClientSocketConnect(Sender: TObject;Socket:TCustomWinSocket);
begin
//
end;

procedure Tclientsocketthread.UpdateCaption;
begin
Form1.ListBox1.Items.Add(str);
end;

procedure TclientsocketThread.ClientSocketOnRead(Sender: TObject;Socket: TCustomWinSocket);
begin //
while (not Terminated) do
begin
str:= Clientsocket.socket.ReceiveText;
Synchronize(UpdateCaption);
end;
end;

function TclientsocketThread.Open: Boolean;
var
i: integer;
begin
i := 0;
while i < reConnectTime do
begin
ClientSocket.Open;
sleep(150);
if ClientSocket.Active then
Break;
inc(i);
end;
result := ClientSocket.Active;
end;

procedure TclientsocketThread.SetReConnectTime(Time: integer);
begin
if ReConnectTime <> Time then
ReConnectTime := Time;
end;

procedure Tclientsocketthread.Execute;
begin
{ Place thread code here }
Open;
end;

end.

我这样写!对吗?!可是没法调用!!!帮帮我

我在主窗体里这样调用:
test: Tclientsocketthread;

test:= Tclientsocketthread.create('127.0.0.1',123);

可是我发信息给他,没有任何反应Z!
 
在线程的执行体是 Execute 部分,
结果你的线程只是 Open 了连接,线程就结束了,
线程结束了,你的线程还干什么了,没有用了。

在 Execute 里面处理你的接收,
一般用线程模式,应该使用阻塞模式通讯,
否则你应该在你的 Execute 部分来个死循环,别让线程一Open Socket 就 Over 了。
 
感觉你这样做的太费劲了,其实你的creat事件等,交给主线程就OK了,太复杂了。当然也可以,楼上说的没错。我的接收线程事放在FORMAIN里的,只用了下面几句。
//定义接收idTcpclient接收数据线程
type
TRcvThread = class(TThread)
private
protected
procedure Execute; override;
end;
var
frmMain: TfrmMain;
。。。。。。。。。。。。。。。。。。。。。。。。。。。。
procedure TRcvThread.Execute;
begin
while not Terminated do
begin
if not frmMain.IdTCPClient1.Connected then
Terminate
else
try
frmMain.read;//读数据函数
except
end;
end;
end;
。。。。。。。。。。。。。。。
用这个掉用
RcvThread := TRcvThread.Create(True);
RcvThread.FreeOnTerminate:=True;
RcvThread.Resume;
当然也最简单的使用 TRcvThread.Create(False);调用
其实你的connect等完全可以在主线程里完成的。
TClientSocket这里感觉比必要这么麻烦啊?在主线程直接一拖图标多好,这太费劲了,而且放在这个位置,感觉也很不合理,挺浪费的。好像跟你的要求不太一样了。其实你要求的代码30来行就OK了。没太搞懂你为什么这样写?
 
谢谢!!!!
 
后退
顶部