多线程数据库+网络,各位网络,线程高手快快看过来(只要回答都有分!)(300分)

  • 主题发起人 主题发起人 yanliangjr9702
  • 开始时间 开始时间
Y

yanliangjr9702

Unregistered / Unconfirmed
GUEST, unregistred user!
现在有一个服务器端,多个客户端。每个客户端每过30秒向服务器提交一次数据。服务器收
到数据后先分析数据然后再将数据写到数据库中。在整个过程进行的同时还要把数据库中的
数据显示出来,每隔1分钟刷新一次数据库。
我的总体思路是用多线程完成,其中显示数据部分用一个线程,其他部分(客户和服务器的
连接,服务器接收客户提交的数据,把数据写到数据库中去)用多个线程。现在有如下的问
题:
1.我作过这样的实验,用API函数来设计线程。我的程序如下:
(数据表名称:text,字段有两个:Ip和ReceText,分别用来储存接收信息的来源IP和起内容)。
function ThreadProc(Var receive:string,Var IpStr:string):LongInt;Stdcall;
begin
query1.close;
query1.sql.clear;
query1.sql.add('insert into text (Ip,ReceText)');
query1.sql.add('values("'+receive+'","'+IpStr+'")');
query1.execusql;
end;
在ServerSocket1的OnClientRead()事件中添加调用这个函数的程序:
Rece:=Socket.ReceiveText;
IpStr:=Socket.RemoteAddress;
try
Hthread1:=CreateThread(nil,0,@ThreadProc(Rece,IpStr),0,nil,ThreadID);
Finally
If (Hthread1=0) then
ShowMessage('创建线程失败!');
end;
我写这个线程的用意很简单,就是当服务器来一个客户的提交时产生一个线程,然后再在这
个线程中完成写数据库的操作。但是,在运行时产生了"Variable
required "样的错误。我现在的疑问是:
1.当用API函数产生线程时,如何传递参数?(在显示数据时由于不需要传递参数,就可以用API函数)。
2.我的这个思路对不对?如果不对,清问为什么?
(服务器的服务类型时"非阻塞")。
2.于上述方法不行,我就改用下面的方法。这是在《Delphi 4.0核心编程》中的一个例子,
我改编了一下,显示用API函数写的线程,而写操作用了一个其中的类。代码如下:
//定义的类:
TQueryThread=class (TThread)
private
FSession:TSession;
FQuery:TQuery;
FDataSource:TDataSource;
FQueryException:Exception;
procedure connectDatasource;
procedure ShowqueryError;
protected
procedure execute;override;
public
constructor create(Session:Tsession;
query:Tquery;
datasource:Tdatasource);virtual;
end;
//类的实现:
constructor TqueryThread.create(Session:Tsession;query:Tquery;datasource:Tdatasource);
begin
inherited create(true);
Fquery:=query;
Fdatasource:=datasource;
FreeOnterminate:=true;
resume;
end;
procedure TqueryThread.execute;
begin
try
FQuery.Open;
synchronize(connectdatasource);
except
Fqueryexception:=Exceptobject as exception;
synchronize(showqueryError);
showmessage('发生错误1!');
end;
end;
procedure TqueryThread.connectDatasource;
begin
FdataSource.DataSet:=fquery;
end;
procedure TqueryThread.ShowqueryError;
begin
application.ShowException(Fqueryexception);
ShowMessage('发生错误2!');
end;
procedure Runbackgroundquery(session:Tsession;query:Tquery;datasource:Tdatasource);
begin
Tquerythread.create(Session,query,datasource);
end;
{$R *.DFM}
procedure TForm1.Timer1Timer(Sender: TObject);
var //每隔1秒钟重新访问一次数据库.
i,j:integer;
begin
query1.close;
query1.sql.Clear;
query1.sql.add('select * from test');
query1.open;
runbackgroundquery(session1,query1,datasource1);
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
//创建时打开ServerSocket
serversocket1.open;
end;

procedure TForm1.ServerSocket1Accept(Sender: TObject;
Socket: TCustomWinSocket);//如果客户端连接成功,则调用此过程.
begin
showmessage('连接成功!');
end;

procedure TForm1.ServerSocket1ClientRead(Sender: TObject;
Socket: TCustomWinSocket);//当有一个客户端提交数据时,调用这个过程.
Var //然后产生一个线程,将数据写入数据库.
Rece,IpStr:string;
begin
Rece:=socket.ReceiveText;
IpStr:=socket.remoteaddress;
query1.close;
query1.sql.Clear;
query1.sql.add('insert into test (Ip,text)');
query1.sql.add('values("'+IpStr+'","'+Rece+'")');
query1.ExecSQL;
Runbackgroundquery(session1,query1,datasource1);
end;
此刻运行正常,向数据库中写数据也正常。但是,每接收一次数据就产生一次"Error creating curor handle "的错误。这是为什么?
后来我把客户端的程序改了一下,在OnWrite()事件中把socket.sendtext()该为:
while not falsedo
begin
sleep(10);
socket.sendtext();
end;
即每过0.01秒把数据循环发一次.但在服务器端接收时却把这些按不同的包发过来的数据
当成一个包处理了:把所有数据写到一条记录中去了!于是我就想到是不是由于相临两次发
送的包之间的间隔太短,才使得服务器认为这是一个数据包?(事实证明的确是这样.后来我
把时间间隔改了一下,改成1秒后,服务器又可认出不同的包了).那么会不会产生这样的情
况:两个来自不同客户端的数据包在同时到达服务器时被服务器认为是同一个包或者是只
能处理一个而将其中一个包丢弃掉?
3. 后来我又看到有的资料上介绍类似于我这个问题的解决办法是用"阻塞线程"解决.但是,
如何用"阻塞线程"解决这个问题?当一个客户端连接到服务器时,服务器如何判断的出此
时已经有一个客户连接?服务器又如何自动的分配一个线程给新连接的客户?这个线程时
怎样实现的?当数据处理完毕之后,服务器又怎样自动的去关闭这个线程?由于我的客户
端比较多,假设又100个,当出现好多个客户同时连接到服务器时,服务器如何给这些连
接一一分配线程?会不会出现由于线程分配不过来而丢失数据的现象?如果会出现这种现
象,该如何解决?会不会象打印队列那样编一个队列?
用C++builder写时用到了这个函数是什么意思?
TwinSocketStream * pStream= new TwinSocketStream(ClientSocket,60000);
(不知那位大虾那里有“阻塞线程”的程序,请参考一下,谢谢!)
 
长了点 过段时间再来拜访..
 
我很急的......
是长了点,但个部分是独立的呀!
大家可以先回答第三个问题。
 
下载大富翁离线包要很长时间,乘这个时间回答一下你的问题吧。
你的第一段程序出错的原因是因为:Borland数据库引擎本身不是多线程的,只有通过TSession组
件才能使他成为线程安全的。在后台运行一个现成查询,得注意让每个qurey对象运行在自己的对话中
,每个后台查询都需要一个和他对应的的单独的对话对象。线程的Query对象的SessionName属性
要设置为TSession组的名称。还有一个线程的Tquery元件不能与他将要用到的的线程环境中的Tdatasource
相连。只能在主线程或Synchronize方法中连接TDatasource。因此你需要在设计时完成连接而非
运行时完成连接。
下面是部分例子代码:
unit unit1;
interface
uses
windows,message,sysutils........//很多,不全写出来了
Type
Tform1 = Class(Tform)
DBGrid1:TDBGrid;
.
DataSource1:Tdatasource;
DataSource2:Tdatasource;
Session1:TSession;
Session2:TSession;
Query1:Tquery;
Query2:Tquery;
.
Procedure Btn1Click(Sender:Tobject);
end;
TqueryThread: =Class(TThread)
Private
FSession:Tsession;
Fquery:Tquery;
FQueryException:Exception;
procedure connectDataSource;
Procedure ShowQueryError;
protected
procedure Execute:override;
public
constructor Create(Session:TSession;Query:Tquery;Datesource:Tdatasource);
virtual;
end;
var
Form1:Tform1;
implementation;
constructor TqueryThread.Create(Session:TSession;Query:Tquery;Datesource:Tdatasource);
begin
inherited Create(True);
Fquery:=query;
Fdatasource:=Datasource;
FreeonTerminate:=true;
resume;
end;
procedure TqueryThread.Execute;
begin
try
Fquery.open;
Synchronze(connectDataSource);
execpt
FQueryexception:=Exceptobject as Exception
Sychronize(ShowQueryError);
end;
end;
procedure TqueryThread.connectdataSource;
begin
Fdatasource.dataset:=Fquery;
end;
procedure TqueryThread.showqueryerror
begin
application.showexception(Fqueryexception);
end;
Procedure runquery(Session:Tsession;Query:Tquery;Datasource:Tdatasource);
begin
TqueryThread.Create(Session,Query,Datesource);
end;
{$R *DFM}
procedure TForm1.Btn1Click(Sender:Tobject);
begin
runquery(Session1,Query1,Datasource1);
runquery(Session2,Query2,Datasource2);
end;
 
To bluely:
>>在后台运行一个现成查询,得注意让每个qurey对象运行在自己的对话中,每个后台查询
>>都需要一个和他对应的的单独的对话对象。线程的Query对象的SessionName属性
>>要设置为TSession组的名称。
我是让每个query运行在自己的对话中呀!这里我用了两个query,我也设置了两个Session,
在设计时我就让query1和session1相连,query2和 session2相连的呀!
>>还有一个线程的Tquery元件不能与他将要用到的的线程环境中的Tdatasource
>>相连。只能在主线程或Synchronize方法中连接TDatasource。因此你需要在设计时完成
>>连接而非运行时完成连接。
什么意思?是不是将不要在设计时而要在运行时将query和 datasource 相连?
如果是这样,我也是这样作的呀!?
你的这个例子和我上面的程序是一模一样的.
我把问题精简一下:
我的第一个问题是:为什么当用API函数产生线程时不能传递参数?
第二各问题是:为什么用这个程序时老是产生"Error creating curor handle "的错误
提示?
第三个问题是:如何用"阻塞线程"写程序?有这方面的例子没有?
 
第一个问题:
看来你对API函数不太了解。用CreateThread()函数创建线程时,你只能给出线程执行体
的指针,你如何能在里边赋值?它的倒数第三个参数你赋了0值,一般情况下,线程是从这里
传递参数的指针的。你可以定义如下:
PRecvData = ^RecvData;
RecvData = record
Ip:string[16];
ReceText:string;
end;

var
pmydata:PRecvData;
function ThreadProc:LongInt;Stdcall;
begin
query1.close;
query1.sql.clear;
query1.sql.add('insert into text (Ip,ReceText)');
query1.sql.add('values("'+pmydata^.receive+'","'+pmydata^.IpStr+'")');
query1.execusql;
end;

在ServerSocket1的OnClientRead()事件中添加调用这个函数的程序:
pmydata^.Rece:=Socket.ReceiveText;
pmydata^.IpStr:=Socket.RemoteAddress;
try
Hthread1:=CreateThread(nil,0,@ThreadProc,pmydata,nil,ThreadID);
Finally
If (Hthread1=0) then
ShowMessage('创建线程失败!');
end;
 
steve老兄说的对。我是没有把API函数没有搞懂。刚才我翻了一下书,果然书上说
LpParameter是用来向函数中传递参数的。
但是,我刚才试了一下,但程序还是不行。从客户端向服务器端发信息,每次到下面
程序时就出现错误:
pmydata^.Rece:=Socket.ReceiveText;
pmydata^.IpStr:=Socket.RemoteAddress;
不知道这是为什么?
不如steve老兄你也试试,看一下这是为什么?
 
问题太长了吧!
 
怎么老是没有人呢?
我可等的有点焦急了.....
 
主要是第三个问题,请大家关注第三个问题!
 
多人接受答案了。
 
我有一类似的程序,往数据库中插记录(用insert语句)使用内存会不断增大,不知你的有没有此问题?
 
后退
顶部