我写的winsock程序运行都正常,总感觉方法太笨,望高人指点.(20分)

  • 主题发起人 主题发起人 wugwdelphi
  • 开始时间 开始时间
W

wugwdelphi

Unregistered / Unconfirmed
GUEST, unregistred user!
程序采用异步方式,此为服务器端,以下代码解决多客户端通过同一个端口连入后要求读写操作时的处理
原理就是对已连接的SOCKET,对其fd_read和fd_close事件进行监测,而此时当客户端发送数据来时我只能知道此类事件的发生,而无法确定是哪个SOCKET中的,于是遍历所有与客户端相连的SOCKET状态(fd_set),如果多线程这样就肯定混乱.感觉非常别扭,有没有什么办法只要知道客户端事件发生就知道其对应的SOCKET号.
 
以下是关键处理代码,请帮看看
procedure TForm1.processsocketmsg(var msg : Tmessage);
procedure recordinfo(info : string);
begin
memo1.Lines.add(info);
end;
var
Client_Addr: TSockAddr;
ClientLen: Integer;
recv_buff : Tsock_info;
ret : integer;
i,j : integer;
xfdset : Tfdset;
xtimeval : TTimeVal;
begin
edit3.text := '';
for i := 0 to clients.Count - 1 do begin
fd_zero(xfdset);
fd_set(integer(clients),xfdset);

xtimeval.tv_sec := 0;
xtimeval.tv_usec := 50;
ret := select(0,@xfdset,nil,nil,@xtimeval);
client_socket := integer(clients);
if (ret>0) then begin
case wsagetselectevent(msg.LParam) of
fd_read : begin
ret := recv(client_socket,recv_buff,sizeof(recv_buff),0) ;
if ret=socket_error then begin
showmessage('read data error');
end else if ret>0 then begin
recordinfo(timetostr(recv_buff.time) + ' : ' + recv_buff.info);
end;
end;
fd_close : begin
closesocket(client_socket);
clients.Remove(pointer(client_socket));

{删除listbox1中对应的项}
for j := 0 to listbox1.count - 1 do begin
if pos(inttostr(client_socket),listbox1.Items[j])>0 then listbox1.Items.Delete(j);
end;
end;
end;
end;
end;
{如果accept,记录新SOCKET}
case wsagetselectevent(msg.LParam) of
fd_accept : begin
FillChar(Client_Addr,Sizeof(Client_Addr),0);
ClientLen := Sizeof(Client_Addr);
Client_Socket := Accept(sockserver,@Client_Addr,@ClientLen);

if client_socket <> invalid_socket then begin
listbox1.Items.Add(inttostr(Client_Socket) + ' : ' + inet_ntoa(client_addr.sin_addr));
clients.Add(pointer(Client_socket));
button2.Enabled := true;
button3.Enabled := true;

end;
setsockopt(client_socket,sol_socket,so_sndbuf,pointer(sock_buff),sizeof(integer));
setsockopt(client_socket,sol_socket,so_rcvbuf,pointer(sock_buff),sizeof(integer));
wsaasyncselect(Client_Socket,handle,wm_socket,fd_read or fd_close);
end;
end;
end;
 
我在书上看到说sock的data属性可存放指针型,将每个客户端的信息写到那里面,
 
可以用链表将Socket保存起来,还有其他方法。具体要看你是怎么应用的.
如果用链表,要先定义一个Socket链表结构,有新的Socket连接进来,就写入链表,管理
起来,也相对容易.
 
to jfyes :你现在就是用的你说的结构.
to p17624: 你说的跟jfyes是一样的.
你们二位说的只是存的地方不一样,这无关紧要,肯定是存好的,不然我怎么通过fd_set找到是哪个发生相应事件了呢,我现在的问题是通过wsaasyncselect(Client_Socket,handle,wm_socket,fd_read or fd_close);进行消息绑定之后,无法知道是哪个客户端上发生的事件,有什么办法确切知道事件一发生就能得到是从哪个客户端来的fd_read,fd_close事件,而不是象我现在这样再调用fd_set全部判断一遍.
 
这个问题关键就在于,作为服务端我只知道有读/关闭事件发生,从这个单纯的事件如何直接知道服务端上的哪个socket对应的客户端上发生了这样事件,难道非要再遍历所有的服务端上的socket,用fd_set取得其对应的客户端状态以此来判断吗?
因为socket(tcp)通讯本就是一对一的既然SOCKET.DLL知道有某个事件发生,而其提供的函数fd_set也可以判断出来,难道就没有一个直接的办法一下就知道发生事件的客户端对应的服务端的socket吗?
 
呵呵,分数不够再加,我这儿刚挣了点分.
 
昨天没有看你上面的代码?现在看了下.
===================
for i := 0 to clients.Count - 1 do begin
fd_zero(xfdset);
fd_set(integer(clients),xfdset);

xtimeval.tv_sec := 0;
xtimeval.tv_usec := 50;
ret := select(0,@xfdset,nil,nil,@xtimeval);
client_socket := integer(clients);
if (ret>0) then begin
case wsagetselectevent(msg.LParam) of
===================
既然是异步选择模型,为什么又要用Select呢?
不知你有没有看Delphi中的TCustomWinSocket,看看它的异步选择模型是怎么回事!
可以直接把TCustomWinSocket异步选择模型搬到你的程序里来可行的.
在这个Message消息结构中wParam就是另一端Socket的标识,不用你再Select得到Socket标识.

type
TCMSocketMessage = record
Msg: Cardinal;
Socket: TSocket;
SelectEvent: Word;
SelectError: Word;
Result: Longint;
end;
procedure Tfm_server.ServerWndProc(var Message: TCMSocketMessage);
function CheckError: Boolean;
var
ErrorCode: Integer;
begin
if Message.SelectError <> 0 then
begin
Result := False;
ErrorCode := Message.SelectError;
case Message.SelectEvent of
FD_ACCEPT: CSocketAccept(Message.Socket);
FD_CLOSE: CSocketClose(Message.Socket);
FD_READ: CSocketRead(Message.Socket);
end;
OnSocketError(Message.Socket, ErrorCode); //这个是你定义的错误事
end else Result := True;
end;

begin
with Message do
if CheckError then
case SelectEvent of //以下是你自己定义的事件
FD_ACCEPT: CSocketAccept(Message.Socket);
FD_CLOSE: CSocketClose(Message.Socket);
FD_READ: CSocketRead(Message.Socket);
end;
end;
 
//要拦截这个消息 ,你定的
procedure ServerWndProc(var Message: TCMSocketMessage); Message CM_SOCKETMESSAGE;
 
谢谢,我在看到你的回答之前实验出来了 msg.wParam是对应SOCKET,其中wParamLo也是,高位的wParamHi始终为0,因为从规则上一个端口最大的连接容量65536,所而只需要用到低位.
虽然我知道了,仍得谢谢您.使用select是可以的,那一写法是我在最初没办法的时候用的,所以感觉很笨.
 
后退
顶部