当前位置:主页>仓库管理软件> 列表

winsock问题 找仓库进销存

记账软件版1楼: TCP传送数据,WSAAsyncSelect 模型,按照协议,数据已经接受完毕,但是FD_READ消息还是不停的触发,recv返回是-1。这是为什么?能避免吗?

2楼: 用WSAAsyncSelect模型呀。
如果简单的话为什么不用select模型呢?
recv为0表示对方连接已经断开。 如用友财务软件教程

3楼: 【 原文由 cpu 所发表 】

用过 WinSock API 网友们知道:WinSock 编程中有一很方便的地方便是其
息驱动机制,不管是底层 API 的 WSAAsyncSelect() 还是 MFC 的异步Socket类:
CAsyncSocket,都提供了诸如 FD_ACCEPT、FD_READ、FD_CLOSE 之类的消息
供编程人员捕捉并处理。FD_ACCEPT 通知进程有客户方Socket请求连接,
FD_READ通知进程本地Socket有东东可读,FD_CLOSE通知进程对方Socket已
关闭。那么,BSD Socket 是不是真的相形见拙呢?

非也! ''cause cpu love unix so.

BSD UNIX中有一系统调用芳名select()完全可以提供类似的消息驱动机制。
cpu郑重宣布:WinSock的WSAAsyncSeclet()不过是此select()的fork版!

bill也是fork出来的嘛,xixi.

select()的机制中提供一fd_set的数据结构,实际上是一long类型的数组,
每一个数组元素都能与一打开的文件句柄(不管是Socket句柄,还是其他
文件或命名管道或设备句柄)建立联系,建立联系的工作由程序员完成,
当调用select()时,由内核根据IO状态修改fd_set的内容,由此来通知执
行了select()的进程哪一Socket或文件可读,下面具体解释:

#include
#include
#include

int select(nfds, readfds, writefds, exceptfds, timeout)
int nfds;
fd_set *readfds, *writefds, *exceptfds;
struct timeval *timeout;

ndfs:select监视的文件句柄数,视进程中打开的文件数而定,一般设为呢要监视各文件
中的最大文件号加一。
readfds:select监视的可读文件句柄集合。
writefds: select监视的可写文件句柄集合。
exceptfds:select监视的异常文件句柄集合。
timeout:本次select()的超时结束时间。(见/usr/sys/select.h,
可精确至百万分之一秒!)

当readfds或writefds中映象的文件可读或可写或超时,本次select()
就结束返回。程序员利用一组系统提供的宏在select()结束时便可判
断哪一文件可读或可写。对Socket编程特别有用的就是readfds。
几只相关的宏解释如下:

FD_ZERO(fd_set *fdset):清空fdset与所有文件句柄的联系。
FD_SET(int fd, fd_set *fdset):建立文件句柄fd与fdset的联系。
FD_CLR(int fd, fd_set *fdset):清除文件句柄fd与fdset的联系。
FD_ISSET(int fd, fdset *fdset):检查fdset联系的文件句柄fd是否
可读写,>0表示可读写。
(关于fd_set及相关宏的定义见/usr/include/sys/types.h)

这样,你的socket只需在有东东读的时候才读入,大致如下:

...
int sockfd;
fd_set fdR;
struct timeval timeout = ..;
...
for(;;) {
FD_ZERO(&fdR);
FD_SET(sockfd, &fdR);
switch (select(sockfd + 1, &fdR, NULL, &timeout)) {
case -1:
error handled by u;
case 0:
timeout hanled by u;
default:


if (FD_ISSET(sockfd)) {
now u read or recv something;
/* if sockfd is father and
server socket, u can now
accept() */
}
}
}

所以一个FD_ISSET(sockfd)就相当通知了sockfd可读。
至于struct timeval在此的功能,请man select。不同的timeval设置
使使select()表现出超时结束、无超时阻塞和轮询三种特性。由于
timeval可精确至百万分之一秒,所以Windows的SetTimer()根本不算
什么。你可以用select()做一个超级时钟。

FD_ACCEPT的实现?依然如上,因为客户方socket请求连接时,会发送
连接请求报文,此时select()当然会结束,FD_ISSET(sockfd)当然大
于零,因为有报文可读嘛!至于这方面的应用,主要在于服务方的父
Socket,你若不喜欢主动accept(),可改为如上机制来accept()。

至于FD_CLOSE的实现及处理,颇费了一堆cpu处理时间,未完待续。

--
讨论关于利用select()检测对方Socket关闭的问题:

仍然是本地Socket有东东可读,因为对方Socket关闭时,会发一个关闭连接
通知报文,会马上被select()检测到的。关于TCP的连接(三次握手)和关
闭(二次握手)机制,敬请参考有关TCP/IP的书籍。

不知是什么原因,UNIX好象没有提供通知进程关于Socket或Pipe对方关闭的
信号,也可能是cpu所知有限。总之,当对方关闭,一执行recv()或read(),
马上回返回-1,此时全局变量errno的值是115,相应的sys_errlist[errno]
为"Connect refused"(请参考/usr/include/sys/errno.h)。所以,在上
篇的for(;;)...select()程序块中,当有东西可读时,一定要检查recv()或
read()的返回值,返回-1时要作出关断本地Socket的处理,否则select()会
一直认为有东西读,其结果曾几令cpu伤心欲断针脚。不信你可以试试:不检
查recv()返回结果,且将收到的东东(实际没收到)写至标准输出...
在有名管道的编程中也有类似问题出现。具体处理详见拙作:发布一个有用
的Socket客户方原码。

至于主动写Socket时对方突然关闭的处理则可以简单地捕捉信号SIGPIPE并作
出相应关断本地Socket等等的处理。SIGPIPE的解释是:写入无读者方的管道。


在此不作赘述,请详man signal。

以上是cpu在作tcp/ip数据传输实验积累的经验,若有错漏,请狂炮击之。

唉,昨天在hacker区被一帮孙子轰得差点儿没短路。ren cpu(奔腾的心) z80

补充关于select在异步(非阻塞)connect中的应用,刚开始搞socket编程的时候
我一直都用阻塞式的connect,非阻塞connect的问题是由于当时搞proxy scan
而提出的呵呵
通过在网上与网友们的交流及查找相关FAQ,总算知道了怎么解决这一问题.同样
用select可以很好地解决这一问题.大致过程是这样的:

1.将打开的socket设为非阻塞的,可以用fcntl(socket, F_SETFL, O_NDELAY)完
成(有的系统用FNEDLAY也可).

2.发connect调用,这时返回-1,但是errno被设为EINPROGRESS,意即connect仍旧
在进行还没有完成.

3.将打开的socket设进被监视的可写(注意不是可读)文件集合用select进行监视,
如果可写,用
getsockopt(socket, SOL_SOCKET, SO_ERROR, &error, sizeof(int));
来得到error的值,如果为零,则connect成功.

在许多unix版本的proxyscan程序你都可以看到类似的过程,另外在solaris精华
区->编程技巧中有一个通用的带超时参数的connect模块.

4楼: 相信很多人都对网络编程感兴趣,下面我们就来介绍,在网络编程中应用最广泛的编程接口WinSock API。 使用WinSock API的编程,应该了解一些TCP/IP的基础知识。虽然你可以直接使用WinSock API来写网络应用程序,但是,要写出优秀的网络应用程序,还是必须对TCP/IP协议有一些了解的。 1、TCP/IP协议与WinSock网络编程接口的关系 在开始之前,我们先说一下WinSock和TCP/IP到底是什么关系。 我碰到很多人问我:怎样使用WinSock协议编程?其实,这话说的有点错误,WinSock并不是一种网络协议,他只是一个网络编程接口,也就是说,他不是协议,但是他可以访问很多种网络协议,你可以把他当作一些协议的封装。现在的WinSock已经基本上实现了与协议无关。你可以使用WinSock来调用多种协议的功能。 那么,WinSock和TCP/IP协议到底是什么关系呢?实际上,WinSock就是TCP/IP协议的一种封装,你可以通过调用WinSock的接口函数来调用TCP/IP的各种功能.例如我想用TCP/IP协议发送数据,你就可以使用WinSock的接口函数Send()来调用TCP/IP的发送数据功能,至于具体怎么发送数据,WinSock已经帮你封装好了这种功能。 2、TCP/IP协议介绍 现在来介绍一些TCP/IP的原理。TCP/IP协议包含的范围非常的广,他是一种四层协议,包含了各种硬件、软件需求的定义,我们这里只介绍软件方面的知识。TCP/IP协议确切的说法应该是TCP/UDP/IP协议。 UDP协议(User Datagram Protocol 用户数据报协议),是一种保护消息边界的,不保障可靠数据的传输。 TCP协议(Transmission Control Protocol 传输控制协议),是一种流传输的协议。他提供可靠的、有序的、双向的、面向连接的传输。 3、保护消息边界和流 那么什么是保护消息边界和流呢? 保护消息边界,就是指传输协议把数据当作一条独立的消息在网上传输,接收端只能接收独立的消息。也就是说存在保护消息边界,接收端一次只能接收发送端发出的一个数据包。而面向流则是指无保护消息保护边界的,如果发送端连续发送数据,接收端有可能在一次接收动作中,会接收两个或者更多的数据包。 我们举个例子来说,例如,我们连续发送三个数据包,大小分别是2k、4k、8k,这三个数据包都已经到达了接收端的网络堆栈中,如果使用UDP协议,不管我们使用多大的接收缓冲区去接收数据,我们必须有三次接收动作,才能够把所有的数据包接收完。而使用TCP协议,我们只要把接收的缓冲区大小设置在14k以上,我们就能够一次把所有的数据包接收下来,只需要有一次接收动作。 这就是因为UDP协议的保护消息边界使得每一个消息都是独立的。而流传输,却把数据当作一串数据流,他不认为数据是一个一个的消息。所以有很多人在使用TCP协议通讯的时候,并不清楚TCP是基于流的传输,当连续发送数据的时候,他们时常会认识TCP会丢包。其实不然,因为当他们使用的缓冲区足够大时,他们有可能会一次接收到两个甚至更多的数据包,而很多人往往会忽视这一点,只解析检查了第一个数据包,而已经接收的其他据包却被忽略了。所以大家如果要作这类的网络编程的时候,必须要注意这一点。 4、WinSock编程简单流程 下面我们介绍一下Win32平台的WinSock编程方法。通讯必须有服务器端和客户端。我们简单介绍TCP服务器端的大体流程。 对于任何基于WinSock的编程首先我们必须要初始化WinSock DLL库。 int WSAStarup( WORD wVersionRequested,LPWSADATA lpWsAData )。 wVersionRequested是我们要求使用的WinSock的版本。调用这个接口函数可以帮我们初始化WinSock 。然后我们必须创建一个套接字(Socket)。 SOCKET Socket(int af,int type,int protocol); 套接字可以说是WinSock通讯的核心。WinSock通讯的所有数据传输,都是通过套接字来完成的,套接字包含了两个信息,一个是IP地址,一个是Port端口号,使用这两个信息,我们就可以确定网络中的任何一个通讯节点。 当我们调用了Socket()接口函数创建了一个套接字后,我们必须把套接字与你需要进行通讯的地址建立联系,我们可以通过绑定函数来实现这种联系。 int bind(SOCKET s,const struct sockaddr FAR* name,int namelen) ; struct sockaddr_in{ short sin_family ; u_short sin_prot ; struct in_addr sin_addr ; char sin_sero[8] ; } 就包含了我们需要建立连接的本地的地址,包括地址族、IP和端口信息。sin_family字段我们必须把他设为AF_INET,这是告诉WinSock使用的是IP地址族。sin_prot就是我们要用来通讯的端口号。sin_addr就是我们要用来通讯的IP地址信息。 在这里,必须还得提一下有关''大头(big-endian)''小头(little-endian)''。因为各种不同的计算机处理数据时的方法是不一样的,Intel X86处理器上是用''小头''形式来表示多字节的编号,就是把低字节放在前面,把高字节放在后面,而互联网标准却正好相反,所以,我们必须把主机字节转换成网络字节的顺序。WinSock API提供了几个函数。 把主机字节转化成网络字节的函数; u_long htonl(u_long hostlong); u_short htons(u_short hostshort); 把网络字节转化成主机字节的函数; u_long ntohl(u_long netlong); u_short ntohs(u_short netshort) ; 这样,我们设置IP地址和port端口时,就必须把主机字节转化成网络字节后,才能用Bind()函数来绑定套接字和地址。 当绑定完成之后,服务器端必须建立一个监听的队列来接收客户端的连接请求。 int listen(SOCKET s,int backlog); 这个函数可以让我们把套接字转成监听模式。如果客户端有了连接请求,我们还必须使用 int accept(SOCKET s,struct sockaddr FAR* addr,int FAR* addrlen); 来接受客户端的请求。现在我们基本上已经完成了一个服务器的建立,而客户端的建立的流程则是初始化WinSock,然后创建Socket套接字,再使用 int connect(SOCKET s,const struct sockaddr FAR* name,int namelen) ; 来连接服务端。 下面是一个最简单的创建服务器端和客户端的例子:服务器端的创建: WSADATA wsd; SOCKET sListen; SOCKET sclient; UINT port = 800; int iAddrSize; struct sockaddr_in local , client; WSAStartup( 0x11 , &wsd ); sListen = Socket ( AF_INET , SOCK_STREAM , IPPOTO_IP ); local.sin_family = AF_INET; local.sin_addr = htonl( INADDR_ANY ); local.sin_port = htons( port ); bind( sListen , (struct sockaddr*)&local , sizeof( local ) ); listen( sListen , 5 ); sClient = accept( sListen , (struct sockaddr*)&client , &iAddrSize ); 客户端的创建: WSADATA wsd; SOCKET sClient; UINT port = 800; char szIp[] = "127.0.0.1"; int iAddrSize; struct sockaddr_in server; WSAStartup( 0x11 , &wsd ); sClient = Socket ( AF_INET , SOCK_STREAM , IPPOTO_IP ); server.sin_family = AF_INET; server.sin_addr = inet_addr( szIp ); server.sin_port = htons( port ); connect( sClient , (struct sockaddr*)&server , sizeof( server ) ); 当服务器端和客户端建立连接以后,无论是客户端,还是服务器端都可以使用 int send( SOCKET s,const char FAR* buf,int len,int flags); int recv( SOCKET s,char FAR* buf,int len,int flags); 函数来接收和发送数据,因为,TCP连接是双向的。当要关闭通讯连结的时候,任何一方都可以调用 int shutdown(SOCKET s,int how); 来关闭套接字的指定功能。再调用 int closeSocket(SOCKET s) ; 来关闭套接字句柄。这样一个通讯过程就算完成了。 注意:上面的代码没有任何检查函数返回值,如果你作网络编程就一定要检查任何一个WinSock API函数的调用结果,因为很多时候函数调用并不一定成功。上面介绍的函数,返回值类型是int的话,如果函数调用失败的话,返回的都是SOCKET_ERROR。 5、WinSock编程的模型 上面介绍的仅仅是最简单的WinSock通讯的方法,而实际中很多网络通讯的却很多难以解决的意外情况。 例如,WinSock提供了两种套接字模式:锁定和非锁定。当我们使用锁定套接字的时候,我们使用的很多函数,例如accpet、send、recv等等,如果没有数据需要处理,这些函数都不会返回,也就是说,你的应用程序会阻塞在那些函数的调用处。而如果使用非阻塞模式,调用这些函数,不管你有没有数据到达,他都会返回。所以有可能我们在非阻塞模式里,调用这些函数大部分的情况下会返回失败,所以就需要我们来处理很多的意外出错。 这显然不是我们想要看到的情况。我们可以采用WinSock的通讯模型来避免这些情况的发生。 WinSock提供了五种套接字I/O模型来解决这些问题。他们分别是select(选择),WSAAsyncselect(异步选择),WSAEventselect (事件选择,overlapped(重叠) , completion port(完成端口) 。 我们在这里详细介绍一下select,WSAASyncselect两种模型。 select模型是最常见的I/O模型。使用 int select( int nfds , fd_set FAR* readfds , fd_set FAR* writefds,fd_set FAR* exceptfds,const struct timeval FAR * timeout ) ; 函数来检查你要调用的Socket套接字是否已经有了需要处理的数据。 select包含三个Socket队列,分别代表: readfds ,检查可读性,writefds,检查可写性,exceptfds,例外数据。 timeout是select函数的返回时间。例如,我们想要检查一个套接字是否有数据需要接收,我们可以把套接字句柄加入可读性检查队列中,然后调用select,如果,该套接字没有数据需要接收,select函数会把该套接字从可读性检查队列中删除掉,所以我们只要检查该套接字句柄是否还存在于可读性队列中,就可以知道到底有没有数据需要接收了。 WinSock提供了一些宏用来操作套接字队列fd_set。 FD_CLR( s,*set) 从队列set删除句柄s。 FD_ISSET( s, *set) 检查句柄s是否存在与队列set中。 FD_SET( s,*set )把句柄s添加到队列set中。 FD_ZERO( *set ) 把set队列初始化成空队列。 WSAAsyncselect(异步选择)模型: WSAASyncselect模型就是把一个窗口和套接字句柄建立起连接,套接字的网络事件发生时时候,就会把某个消息发送到窗口,然后可以在窗口的消息响应函数中处理数据的接收和发送。 int WSAAsyncselect( SOCKET s, HWND hWnd , unsigned int wMsg , long lEvent ) ; 这个函数可以把套接字句柄和窗口建立起连接, wMsg 是我们必须自定义的一个消息。 lEvent就是制定的网络事件。包括FD_READ , FD_WRITE , FD_ACCEPT,FD_CONNECT,FD_CLOSE 。几个事件。例如,我需要接收FD_READ , FD_WRITE , FD_CLOSE 的网络事件。可以调用 WSAAsyncselect( s , hWnd , WM_SOCKET , FD_READ | FD_WRITE | FD_CLOSE ) ; 这样,当有FD_READ , FD_WRITE 或者 FD_CLOSE网络事件时,窗口hWnd将会收到WM_SOCKET消息,消息参数的lParam标志了是什么事件发生。 其实大家应该见过这个模型,因为MFC的CSocket类,就是使用这个模型。 上面为大家介绍了WinSock编程的一些方法。当然,WinSock编程远不止这些知识,如果想要学好网络编程,还是需要不断的学习和研究的。

5楼: 谢谢楼上的资料,能否针对我的问题进行回答

6楼: 为什么我的问题老是这个结果涅

记账软件版7楼: 具体问题就要看看你的代码了

8楼: 顶

9楼: 如何判断发送缓冲区里是否还有未发送的数据? http://www.delphibbs.com/delphibbs/dispq.asp?lid=3452905

上面的可写,你的则反之
在你接收数据之前判断Socket是否(有数据)可读,是明智的.
//检查Socket是否可读, 可读返回True,否则相反
function SocketInRecv(const S: TSocket): BOOL;
var
FDSet: TFDSet;
TimeVal: TTimeVal;
ErrorCode: Integer;
begin
FD_ZERO(FDSet);
FD_SET(S, FDSet);
TimeVal.tv_sec := 0;
TimeVal.tv_usec := 500;
Result := (select(0, @FDSet, nil, nil, @TimeVal) > 0);
ErrorCode := WSAGetLastError;
if Result then Result := ReceiveBuf(S, FDSet) > 0;
if ErrorCode <> 0 then WinSock2.WSASetLastError(ErrorCode);
end;

case Fm_MainClient.Client.Events of
FD_CLOSE: Fm_MainClient.DoCloseClient;
FD_READ: begin
if SocketInRecv(Fm_MainClient.Client.Socket) then
Fm_MainClient.OnClientRead(Fm_MainClient.Client);
end;
end;

10楼: 还掉了个: 其实这些都是VCL里面的,你仔细研究VCL就能解决很问题。
function ReceiveBuf(const FSocket: TSocket; var Buf; const Count: Integer = -1): Integer;


begin
Result := -1;
if Count = -1 then
ioctlsocket(FSocket, FIONREAD, Longint(Result))
else
Result := recv(FSocket, Buf, Count, 0);
end;
以上只适合非重叠IO的SOCKET

11楼: to jfyes大侠:
不好意思前几天没上来,刚才看见您的回复。但是有几点您可能没看明白:
其一,我用的是WSAAsyncSelect 模型而您提供的代码很明显是select模型,二者完全不一样。
其二,用ReceiveBuf>0来判断缓冲区是否有数据再决定是否接受我觉得有点画蛇添足,最起码在我这里是这样的。
您所说的vcl代码是那个控件的代码?如果是Tclientsocket 和Tserversocket的话我大概看过,好像他们使用的也不是WSAAsyncSelect 模型

12楼: recv返回是-1可认为对方已断开连接,这个socket已经无效了。 如商品进销存表格下载

13楼: 我自己用WSASYNCSELECT做个组件,封装了socket api 模范TSERVERSOCKET 需要的话通知我
QQ:8701892

记账软件版14楼: 你的问题可能是因为.另服务器跟客户端连接的socket没有close

15楼: to truest9:
recv返回是-1并不完全是对方已断开连接,看看帮助就知道了


to metalchen:
我的客户端数据发送完之后还不要close。

16楼: 我说的,你试过没有。 为什么不试试呢?
//==========
recv返回是-1
你要检查为什么返回-1,如果错误码是WSAEWOULDBLOCK
Result := send(FSocket, Buf, Count, 0);
if Result = SOCKET_ERROR then// = -1
begin
ErrorCode := WSAGetLastError;
if (ErrorCode <> WSAEWOULDBLOCK) then
begin
Error(Self, eeSend, ErrorCode);
Disconnect(FSocket);
if ErrorCode <> 0 then
raise ESocketError.CreateResFmt(@sWindowsSocketError,
[SysErrorMessage(ErrorCode), ErrorCode, ''send'']);
end;
end;

=====================================
http://www.51see.com/html/bbs/public/200308131152297103.html
Windows网络编程系列教程之四:Select模型 (1)
--------------------------------------------------------------------------------
讲一下套接字模式和套接字I/O模型的区别。先说明一下,只针对Winsock,如果你要骨头里挑鸡蛋把UNIX下的套接字概念来往这里套,那就不关我的事。
套接字模式:阻塞套接字和非阻塞套接字。或者叫同步套接字和异步套接字。
套接字模型:描述如何对套接字的I/O行为进行管理。
Winsock提供的I/O模型一共有五种:
select,WSAAsyncSelect,WSAEventSelect,Overlapped,Completion。今天先讲解select。

1:select模型(选择模型)
先看一下下面的这句代码:
int iResult = recv(s, buffer,1024);
这是用来接收数据的,在默认的阻塞模式下的套接字里,recv会阻塞在那里,直到套接字连接上有数据可读,把数据读到buffer里后recv函数才会返回,不然就会一直阻塞在那里。在单线程的程序里出现这种情况会导致主线程(单线程程序里只有一个默认的主线程)被阻塞,这样整个程序被锁死在这里,如果永远没数据发送过来,那么程序就会被永远锁死。这个问题可以用多线程解决,但是在有多个套接字连接的情况下,这不是一个好的选择,扩展性很差。Select模型就是为了解决这个问题而出现的。
再看代码:

int iResult = ioctlsocket(s, FIOBIO, (unsigned long *)&ul);
iResult = recv(s, buffer,1024);

这一次recv的调用不管套接字连接上有没有数据可以接收都会马上返回。原因就在于我们用ioctlsocket把套接字设置为非阻塞模式了。不过你跟踪一下就会发现,在没有数据的情况下,recv确实是马上返回了,但是也返回了一个错误:WSAEWOULDBLOCK,意思就是请求的操作没有成功完成。看到这里很多人可能会说,那么就重复调用recv并检查返回值,直到成功为止,但是这样做效率很成问题,开销太大。
感谢天才的微软工程师吧,他们给我们提供了好的解决办法。
先看看select函数
int select(
int nfds,
fd_set FAR *readfds,
fd_set FAR *writefds,
fd_set FAR *exceptfds,
const struct timeval FAR *timeout
);
第一个参数不要管,会被系统忽略的。第二个参数是用来检查套接字可读性,也就说检查套接字上是否有数据可读,同样,第三个参数用来检查数据是否可以发出。最后一个是检查是否有带外数据可读取。
参数详细的意思请去看MSDN,这里限于篇幅不详细解释了。
最后一个参数是用来设置select等待多久的,是个结构:

17楼: struct timeval {
long tv_sec; // seconds
long tv_usec; // and microseconds
};
如果将这个结构设置为(0,0),那么select函数会马上返回。
说了这么久,select的作用到底是什么?
他的作用就是:防止在在阻塞模式的套接字里被锁死,避免在非阻塞套接字里重复检查WSAEWOULDBLOCK错误。
他的工作流程如下:
1:用FD_ZERO宏来初始化我们感兴趣的fd_set,也就是select函数的第二三四个参数。
2:用FD_SET宏来将套接字句柄分配给相应的fd_set。
3:调用select函数。
4:用FD_ISSET对套接字句柄进行检查,如果我们所关注的那个套接字句柄仍然在开始分配的那个fd_set里,那么说明马上可以进行相应的IO操作。比如一个分配给select第一个参数的套接字句柄在select返回后仍然在select第一个参数的fd_set里,那么说明当前数据已经来了,马上可以读取成功而不会被阻塞。



///=========================================================

如果你还不明白就没蛰了

18楼: to jfyes:
错误码是WSAEWOULDBLOCK
你所说的是select模型,而我用的是WSAAsyncSelect模型。我现在的问题不是如何知道有是否有数据到来,而是我已经知道用数据到来(WSAAsyncSelect模型有windows消息通知),但是我调用recv的时候按我的协议数据已经接受完毕,但是FD_READ消息还是不停的触发,recv返回是-1,WSAGetLastError返回WSAEWOULDBLOCK

19楼: 举个例子吧,发送方发送10B的数据,先发送长度10,然后再发送数据
接受端先收到长度10之后根据长度接受10B的数据,这时候按说数据已经接受完毕,而且发送放也没有新数据发送,为什么接收端的FD_READ消息还会不停触发呢

20楼: 会有这么怪,是不是没有真正的接完呢?接受完后,数据包真的完整。

记账软件版21楼: 数据肯定接收完毕,而且数据包也肯定完整。
因为我曾经是判断recv连续3次返回-1就跳过循环,解析数据包,这时候数据包完全正确
但是这个现象让我百思不得其解

22楼: 应该是你的网络有广播消息。 如仓库进销存

23楼: 我是tcp的连接,广播消息对这个有影响吗?这个说法有没官方的资料说明?

24楼: 因为你每个问题的分都很多啊

25楼: 丁页 一一 口合!

26楼: 问题: 如何用delphi实现局域网内的UDP组播的发送与接收? ( 积分: 100 )


分类: 局域网 / 通讯

来自: yyy_fcz, 时间: 2005-05-23 22:43:00, ID: 3082274
如何用delphi实现局域网内的UDP组播的发送与接收?

来自: skywhl, 时间: 2005-05-30 18:31:09, ID: 3089215
我也想知道啊~~~

来自: yyy_fcz, 时间: 2005-05-30 19:10:01, ID: 3089242
已经解决了!
unit udp;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, winsock,
StdCtrls;

const
WM_SOCK = WM_USER + 1; //自定义windows消息
UDPPORT = 6543; //设定UDP端口号

//D类地址224.0.0.0 - 239.255.255.255
//若为224.0.0.1则本机也能收到,否则本机收不到,其它机器能收到。
MY_GROUP = ''224.0.0.2'';


(*
* Argument structure for IP_ADD_MEMBERSHIP and IP_DROP_MEMBERSHIP.
* Delphi5自带的winsock.pas中没有ip_mreq的定义。
*)

type
ip_mreq = record
imr_multiaddr: in_addr; (* IP multicast address of group *)
imr_interface: in_addr; (* local IP address of interface *)
end;
TIpMReq = ip_mreq;
PIpMReq = ^ip_mreq;

type
Tfrmmain = class(TForm)
Button1: TButton;
Edit1: TEdit;
Memo1: TMemo;
Edit2: TEdit;
Label1: TLabel;
Label2: TLabel;
Label3: TLabel;
procedure FormCreate(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
s: TSocket;
addr: TSockAddr;
FSockAddrIn : TSockAddrIn;
mreq:ip_mreq;
//利用消息实时获知UDP消息
procedure ReadData(var Message: TMessage); message WM_SOCK;
public
{ Public declarations }
procedure SendData(Content: String);
end;

var
frmmain: Tfrmmain;

implementation

{$R *.DFM}

procedure Tfrmmain.FormCreate(Sender: TObject);
var
TempWSAData: TWSAData;
//optval: integer;
begin
Edit1.Text := MY_GROUP;
// 初始化SOCKET
if WSAStartup($101, TempWSAData)=1 then


showmessage(''StartUp Error!'');

s := Socket(AF_INET, SOCK_DGRAM, 0);
if (s = INVALID_SOCKET) then //Socket创建失败
begin
showmessage(inttostr(WSAGetLastError())+'' Socket创建失败'');
CloseSocket(s);
//exit;
end;
//发送方SockAddr绑定
addr.sin_family := AF_INET;
addr.sin_addr.S_addr := INADDR_ANY;
addr.sin_port := htons(UDPPORT);
if Bind(s, addr, sizeof(addr)) <> 0 then
begin
showmessage(''bind fail'');
end;

{optval:= 1;
if setsockopt(s,SOL_SOCKET,SO_BROADCAST,pchar(@optval),sizeof(optval)) = SOCKET_ERROR then
begin
showmessage(''无法进行UDP广播'');
end;}

mreq.imr_multiaddr.S_addr := inet_addr(pchar(MY_GROUP));//htonl(INADDR_ALLHOSTS_GROUP);
mreq.imr_interface.S_addr := htonl(INADDR_ANY);
if setsockopt(s,IPPROTO_IP,IP_ADD_MEMBERSHIP,pchar(@mreq),sizeof(mreq)) = SOCKET_ERROR then
begin
showmessage(''无法进行UDP组播'');
end;


WSAAsyncSelect(s, frmmain.Handle , WM_SOCK, FD_READ);
//接收端SockAddrIn设定
FSockAddrIn.SIn_Family := AF_INET;
FSockAddrIn.SIn_Port := htons(UDPPORT);

label3.Caption := ''端口:''+inttostr(UDPPORT);
end;

procedure Tfrmmain.FormClose(Sender: TObject; var Action: TCloseAction);
begin
CloseSocket(s);
end;

procedure Tfrmmain.ReadData(var Message: TMessage);
var
buffer: Array [1..4096] of char;
len: integer;
flen: integer;
Event: word;
value: string;
begin
flen:=sizeof(FSockAddrIn);
Event := WSAGetSelectEvent(Message.LParam);
if Event = FD_READ then
begin
len := recvfrom(s, buffer, sizeof(buffer), 0, FSockAddrIn, flen);
value := copy(buffer, 1, len);
Memo1.Lines.add(value)
end;
end;

procedure Tfrmmain.SendData(Content: String);
var
value{,hostname}: string;
len: integer;
begin
//FSockAddrIn.SIn_Addr.S_addr := INADDR_BROADCAST;


FSockAddrIn.SIn_Addr.S_addr := inet_addr(pchar(MY_GROUP));
value := Content;
len := sendto(s, value[1], Length(value), 0, FSockAddrIn, sizeof(FSockAddrIn));
if (WSAGetLastError() <> WSAEWOULDBLOCK) and (WSAGetLastError() <> 0) then
showmessage(inttostr(WSAGetLastError()));
if len = SOCKET_ERROR then
showmessage(''send fail'');
if len <> Length(value) then
showmessage(''Not Send all'');
end;

procedure Tfrmmain.Button1Click(Sender: TObject);
begin
senddata(Edit2.text);
end;

end.

问题讨论没有结束 ...

27楼: 不知楼上引用的大段文字想说明什么?不知道有没有仔细看过之前的讨论。

记账软件版28楼: 用WSAAsyncSelect模型不行,那其他的模型会有这样的问题吗?

29楼: WSAAsyncEventSelect模型也有

30楼: WSAAsyncEventSelect也有,不会吧,我的程序都没有,你有没有在别的机上测试

31楼: 因为困扰很久了才有此一问
现在的解决方法有些简单,计数3次这样的情况,就跳过
jfyes,能把你接收的代码贴出来一点看看么?

32楼: WSAAsyncSelect 我很少用。
下面是
WSAAsyncEventSelect
这个是我的一个客户端Socket,用了很久
{ TClientWorkThread }

constructor TClientWorkThread.Create(AClientSocket: TClientSocket);
var
ErrorCode: Integer;
begin
hEvent := WSACreateEvent;
if hEvent = WSA_INVALID_EVENT then
raise ESocketError.Create(SysErrorMessage(GetLastError));
ErrorCode := WSAEventSelect(AClientSocket.FSocket, hEvent, FD_READ or FD_CLOSE); // ''WSAEventSelect'';
if ErrorCode <> 0 then begin
AClientSocket.Error(eeConnect, ErrorCode);
if ErrorCode <> 0 then
AClientSocket.DoException(AClientSocket, Format(sWindowsSocketError, [SysErrorMessage(ErrorCode), ErrorCode, ''WSAEventSelect'']));
end;
inherited Create(AClientSocket);
end;

procedure TClientWorkThread.Execute;
var
ErrorCode: Integer;
WNet: TWSANETWORKEVENTS;
begin
while not Terminated do
begin
ErrorCode := WSAWaitForMultipleEvents(
1, //事件数量
@hEvent, //事件处理函数


False, //有一个事件触发时就返回
FClientSocket.FTimeOut, // 超时时间 无穷等待
False //函数返回时,不执行I/O例程
);

if Terminated or (ErrorCode = WAIT_IO_COMPLETION) then
begin
Break;
end
else
begin
WSAResetEvent(hEvent);
if ErrorCode = WSA_WAIT_TIMEOUT then
begin
FClientSocket.DoReadTimeOut;
end
else if (ErrorCode = WAIT_OBJECT_0) then
begin
FillChar(WNet, SizeOF(WNet), 0);
ErrorCode := WinSock2.WSAEnumNetworkEvents(FClientSocket.SocketHandle, HEvent, @WNet);
case WNet.lNetworkEvents of
FD_READ: FClientSocket.DoClientRead(Self);
FD_CLOSE: begin
FClientSocket.Close;
end;
else FClientSocket.Error(eeGeneral, ErrorCode);
end;
end;
end;
end;
//线程完成送闭事件
if HEvent <> WSA_INVALID_EVENT then begin
WSACloseEvent(HEvent);
HEvent := WSA_INVALID_EVENT;
end;
end;

33楼: FD_READ: FClientSocket.DoClientRead(Self);

jfyes兄弟,问题就在这里边,可是你没有代码。

34楼: {

function ReceiveBuf(const FSocket: TSocket; var Buf;
const Count: Integer = -1): Integer;
begin
Result := -1;
if Count = -1 then
ioctlsocket(FSocket, FIONREAD, Longint(Result))
else
Result := recv(FSocket, Buf, Count, 0);
end;
}

procedure TClientSocket.DoClientRead(Sender: TObject);
var
Buffer: PChar;
Buf: array[0..MAX_BUFSIZE - 1] of Char;
ALength: Integer;
ReadLen: Integer;
begin
ReadLen := 0;
ALength := 0;
try
ReadLen := ReceiveBuf(SocketHandle, Buf);
//这里如果要统计总流所有Socket的,就得要加上所有
Inc(FRecvAllBytes, ReadLen);
if Assigned(OnSocketDataTrans) then OnSocketDataTrans(self, tsRecv, ReadLen);
except on E: Exception do
DoException(Self, ''DoClientRead: OnSocketDataTrans'');
end;

while ReadLen > 0 do


begin
try
FillChar(Buf, SizeOf(Buf), #0);
ALength := ReceiveBuf(SocketHandle, Buf, SizeOf(Buf));
//DoException(Self, Format(''接收一次总长度:%d, Len: %d'', [ReadLen, ALength]));
if ALength < 0 then begin
DoException(Self, ''DoClientRead: A-ReceiveBuf ALength < 0'');
Exit;
end;
except on E: Exception do begin
if ALength < 0 then begin
DoException(Self, ''DoClientRead: B-ReceiveBuf ALength < 0'');
Exit;
end;
DoException(Self, ''DoClientRead: ReceiveBuf error'');
end;
end;

Dec(ReadLen, ALength);
try
OnClientRead(Self, @Buf, ALength);
except on E: Exception do begin
DoException(Self, ''TClientSocket.DoClientRead faild!'');
Exit;
end;
end;
end; //while do
end;

记账软件版35楼: jfyes:
看明白了,不过你的处理方法还是和我的问题不一样。你在每次接收前都用ioctlsocket(FSocket, FIONREAD, Longint(Result)) 察看了一下接收缓冲区中的数据长度,然后根据这个长度接收。
但是我的问题是我不需要察看缓冲区中的数据长度,我的协议已经告诉我要接受的数据长度。
另外多说一句,《windows网络编程》中不推荐使用ioctlsocket查询缓冲区长度,意思好像是效率不高

36楼: 把你的代码贴出来看看

37楼: var
buf:array[0..bufsize-1] of char;
leftlen,count:integer;
begin
//********** 接收数据长度**************
leftlen:=4;
while (leftlen>0) do
begin
count:=recv(sock,buf,leftlen,0);
if count=0 then
exit; //出错
if count=SOCKET_ERROR then
begin
if wsagetlasterror=WSAEWOULDBLOCK then
begin
sleep(10);
continue;
end else
exit; //出错
end;
dec(leftlen,count);
end;
move(buf,leftlen,4);
//**************************************
//********** 接收数据******************
while (leftlen>0) do
begin
count:=recv(sock,buf,leftlen,0);
if count=0 then
exit; //出错
if count=SOCKET_ERROR then
begin
if wsagetlasterror=WSAEWOULDBLOCK then
begin
sleep(10);
continue;
end else
exit; //出错
end;
dec(leftlen,count);
end;
//******** 接收完毕,处理。。。
end;

38楼: 把你的程序发给我看看。jf_yes@126.com

39楼: 不好意思,单位的东西,不便传播

40楼: [:D][:D][:D][:(]