400百分﹕關于Socket通信問題(會者不難﹐另100分另開貼)(300分)

  • 主题发起人 主题发起人 Jelly0228
  • 开始时间 开始时间
J

Jelly0228

Unregistered / Unconfirmed
GUEST, unregistred user!
用TClientSocket與TServerSocket寫了一個局域網通信程序﹐可以發送聊天信息以及傳送檔案。
Server與Client是寫在同一支程式里面﹐目的是同一台機器即可以是客戶端也可以是Server端。
Client用非阻塞模式﹐Server用阻塞模式﹐采用TCP協議。

Server與Client的通信過程大致是這樣的﹕
C:請求連接﹔
S:接受連接﹔
C:如果沒有傳送檔案的話﹕發送聊天字符串內容;
否則﹕發送聊天字符串內容+要傳送的檔案信息(文件名+文件大小);
S:接收﹔
如果沒有傳送檔案的話﹕返回給C成功收到聊天內容的信息﹔
否則: 告知C已做好准備﹐請傳送檔案﹔
C:如果沒有傳送檔案的話﹕顯示成功發送信息(本次通信工作完成)﹔
否則: 開始傳送檔案;
S:接收傳送的檔案﹔
返回給C成功收到檔案的信息﹔本次通信工作完成。

我的問題有二﹕
1.因為每次通信都是在C/S多次一問一答中完成的﹐那么﹐在多客戶通信的情況下﹐如何正確的溝通呢?例如同時有A﹑B兩客人同時與Server通信﹐在這一問一答的模式中﹐如何確保Server能正確的返回信息給正確的客戶?
2.在傳送大文件時﹐需要將文件切割﹐一塊一塊地傳送。看過一些資料﹐大致是將文件寫入文件流中﹐再用文件流的一些相關的方法傳送。請問如何切割,在接收端如何將這些塊組合起來成為一個完整的檔案??

這個問題比較急﹐一旦搞定﹐馬上結貼。謝謝各位了﹗
 
其实这两个问题你都不必操心
1.Server接受连接后会创建一个新的Socket来与客户交互,所以两个客人自然对应两个Socket;
2.发送时你只管直接发就好了,接收时你不是先传送了文件的大小了吗,等待接收那么多数据,直接将他们连在一起就行了.
 
weiwei81123,
非常感謝你的幫助﹐不過我還是有不明白的地方﹐希望繼續賜教。
1.請問﹐假設A﹑B 兩個連接都是來自同一台機器(例如這台機器同一內容發送了兩次)﹐那么Server如何對這兩個連接做出正確的應答呢??
如你所說﹐一次連接會創建一個新的Socket﹐那么就拿發送一次字符串來說﹐要一問一答三次才能完成這次工作﹐那么是不是就會創建三次Socket?如果是這樣﹐那么如何能保証他們之間能正確的應答呢?
2.請問如何連起來呢??
 
连接上去后Socket会自动开个线程连接,你说的三次Socket,只是3次信息交换,每个Socket连接之间没有联系的。

var
sRecv:string;
bufSend:pointer;
iLength:Integer;
begin
sRecv:=Socket.ReceiveText;
sRecv:=copy(sRecv,1,5);
if sRecv=MP_REFUSE then
memo1.Lines.Add('被拒绝!')
else if sRecv=MP_ACCEPT then
begin
fsSend:=TFileStream.Create(OpenDialog1.FileName,fmOpenRead);
bStart:=False;
memo1.Lines.Add('开始发送!');
TickCount:=GetTickCount;
//iBYTEPERSEND是个常量,每次发送包的大小。
Socket.SendText(MP_FILEPROPERTY+inttostr(Trunc(fsSend.Size/iBYTEPERSEND)+1));
//创建文件流并发送文件长度。
end else if sRecv=MP_NEXTWILLBEDATA then
begin
Socket.SendText(MP_NEXTWILLBEDATA);
//通知接收端。继续传送数据。
end else if sRecv=MP_DATA then
begin
//发送数据。
if not bStart then
begin
memo1.Lines.Add('发送数据。。。!');
bStart:=True;
end;
if fsSend.Position< fsSend.Size-1 then//还有数据没有发送。
begin
iLength:=fsSend.Size-1-fsSend.Position;
if iLength>iBYTEPERSEND then
iLength:=iBYTEPERSEND;
GetMem(bufSend,iLength+1);
try
fsSend.Read(bufSend^,iLength);
Socket.SendBuf(bufSend^,iLength);
finally
FreeMem(bufSend,iLength+1);
end;{of try}
end else//没有数据需要发送了。
begin
Socket.SendText(MP_END);//文件传送结束。
memo1.Lines.Add('文件传送结束!'+IntToStr(GetTickCount-TickCount));
fsSend.Free; // <--------------------
end;
end else if sRecv=MP_ABORT then
begin
memo1.Lines.Add('中止!');
fsSend.Free;
end;
end;



procedure TForm1.ssClientRead(Sender: TObject; Socket: TCustomWinSocket);
var
sTemp,sFileName:string;
bufRecv:Pointer;
iLength:Integer;
begin
iLength:=Socket.ReceiveLength;
GetMem(bufRecv,iLength);
try
Socket.ReceiveBuf(bufRecv^,iLength);
sTemp:=StrPas(PChar(bufRecv));
sTemp:=Copy(sTemp,1,5);
if sTemp=MP_QUERY then
begin
sTemp:=Trim(StrPas(PChar(bufRecv)));
sFileName:=ExtractFileName(Copy(sTemp,6,Length(STemp)));

fsRecv:=TFileStream.Create('TempFile/'+sFileName,fmCreate);
//如果愿意接收数据。
memo1.Lines.Add ('-----------------------------------');
memo1.Lines.Add ('文件传送['+sFileName+']开始接收!');
TickCounts:=GetTickCount;
Socket.SendText(MP_ACCEPT);
//通知发送端发送数据。
bStarts:=False;
// end
// else
// Socket.SendText(MP_REFUSE+'OVER!!');
end else if sTemp=MP_FILEPROPERTY then
begin

Socket.SendText(MP_NEXTWILLBEDATA);
//接收文件长度并要求继续传送数据。
end else if sTemp=MP_NEXTWILLBEDATA then
begin
Socket.SendText(MP_DATA);
//要求发送端发送数据。
//准备好接收数据。
end else if sTemp=MP_OVER then
begin
memo1.Lines.Add ('MP_OVER');
fsRecv.Free;
end else if sTemp=MP_END then//文件传送结束。
begin
memo1.Lines.Add ('文件传送结束!'+IntToStr(GetTickCount-TickCounts));
fsRecv.Free;
end else if sTemp=MP_ABORT then
begin
memo1.Lines.Add ('MP_ABORT');
fsRecv.Free;
end else if sTemp=MP_CHAT then
begin
end else
begin
if not bStarts then
begin
memo1.Lines.Add('正在接收文件...');
bStarts:=True;
end;
fsRecv.WriteBuffer(bufRecv^,iLength);//
Socket.SendText(MP_NEXTWILLBEDATA);
end;
finally
FreeMem(bufRecv,iLength);

end;
end;


 
1.你理解错了,是这样的:
Client请求连接
Server接受连接,这时创建一个新的Socket与Client交互,连接就建立了.
之后的数据收发都用这个Socket,在OnClientRead/OnClientWrite各函数
中的参数Socket都是它,到连接被关闭后该Socket就释放了.
所以一问一答三次用的都是同一个Socket,至于同一台机器建立的两个连
接,它们也是由两个不同的Socket来负责交互的,不必但心.
2.将收到的数据顺次写入一个流中即可.
 
謝謝rEgSpy 和 weiwei81123的幫助﹐我現在正在按你們提供的方法測試﹐﹐如有什么問題還要請多多請教喔﹗﹗
 
再試試看﹐﹐呵呵﹐﹐
 
weiwei81123,
你好﹐再請教你一個問題﹐為支持多用戶發送/接收﹐我將Client也改成了阻塞模式﹐全部的讀寫都在一個TThread的派生類中進行。
但有時總是出現一個這樣的錯誤信息﹕'Windows socket error:對一個已連線的通訊端發出連線要求。(10056),on API 'connect''.我猜想大意是本來Socket已經連接了﹐但又請求了一次。但我實在找不到問題出在哪兒??
這是我的Client端線程execute代碼﹕
procedure TClientTread.Execute;
var
S: TWinSocketStream;
SBuf: array [0..1023] of Char;
RecText: String;
A: String;
begin
inherited;
FreeOnTerminate:=True;
SK:= F_SelectAddress.ClientSocket;
try
SK.Active:=False;
SK.Host:=FAIp; //遠端主機地址﹐由線程的Create方法的參數傳入
SK.Active:=True;

Synchronize(LoadPicWhenSending);
A:=MyNote+FileProperty+SendStr;
SK.Socket.SendText(A);

While (not Terminated) and (SK.Active) do
begin
try
S:=TWinSocketStream.Create(SK.Socket,10000);
FillChar(SBuf,SizeOf(SBuf),0);
if S.WaitForData(10000) then
begin
if S.Read(SBuf,SizeOf(SBuf))=0 then
begin
SK.Close;
Terminate;
end;

RecText:=SBuf;
if RecText=SendText then
// CreateFileStream
// Synchronize(CreateFileStream)
else if RecText=ReceiveText then
begin
// Synchronize(LoadPicWhenOK);
SK.Close;
Terminate;
end;
end
else begin
SK.Close;
// Synchronize(LoadPicWhenBad);
Terminate;
end;
finally
s.Free;
Terminate;
end;
end;
Sk.Close;
Terminate;
except
Sk.Close;
// Synchronize(LoadPicWhenBad);
Terminate;
end;
end;
 
多线程造成的?
可能你连续进行两次操作,造成两个线程请求同一个Socket连接,后一个没有等待前一个结束
 
weiwei81123﹐
很是感激你耐心回答我的問題。
想跟你就此問題討論一下﹕
1.在線程的Execute過程中﹐有一段這樣的代碼﹕
SK.Active:=False;// SK是Form上的TClientSocket對象﹔
SK.Host:=FAIp; //遠端主機地址﹐由線程的Create方法的參數傳入
SK.Active:=True;
這樣的話是不是說明每次線程被創建并被執行時﹐都會創建新的Socket??那是不是會不會出現多個線程請求同一個Socket連線的問題了呢?
2.又或者問題會不會出在Server那兒?ServerSocket也是阻塞模式﹐所有的讀寫也都是在線程是讀寫的。在Delphi的幫助文檔中看到﹐TServerSocket的.ThreadCacheSize屬性指定最大能被重用的線程數(默認為10)。ServerSocket在線程被執行完后﹐沒有被Free掉﹐而是保存在緩沖區內﹐新的連線可再利用緩沖區內的線程而無需重新創建。我在想會不會因為線程沒有被Free掉而導致Socket沒有釋放??
提出來跟你商討﹐等下改成0試試﹗

 
会创建新Socket的是服务器端的ServerSocket,ClientSocket不会的
我想这个问题应该与服务器无关吧
 
謝了謝了~~尤其是weiwei81123~
 
后退
顶部