一个有关WSASend的疑惑(200分)

  • 主题发起人 主题发起人 Another_eYes
  • 开始时间 开始时间
A

Another_eYes

Unregistered / Unconfirmed
GUEST, unregistred user!
针对同一个socket s,数个线程用WSASend的overlapped方式发送数据(用completion port接收完成通知),是否会出现线程一发出的某个数据包发了一半时插入了线程二的数据包? 既发送的数据出现交叉?(想象起来不会,不过心里不踏实,有经验的帮忙答一下)
 
不会。协议保证了的。完成端口的测试模型,我自己没有建立,但 bartom 做过,据他的
测试的情况,还比其他模型好。
 
谢谢。
其实完成端口实现起来非常简单。 据说效率是所有模型中最高的。 现在我只是不知道工作线程开多少合适,查了一些文章,有的说cpu数*2, 有的说cpu数*2+2, 嗬嗬,一头雾水,也没人说出个所以然来。
另外还有一个疑问,如果一个application开多个completion port是否效率会不如一个completion port?
 
不是啊,我的测试中,服务端向客户端发送数据时是严格排队的。因为事先无法估计
每次能够传送多少数据,所以每次只投递一小块(4096字节,刚好一页),直至传完,
然后发送队列中的第二个数据块。我看过一些资料,基于流的数据必须分成块排队。
我没有更详细地测试,但我从前用事件模型时,一次投递整个数据段,经常丢包。所
以我总是将要投递的包当成用户缓冲区,但每次只传送一个小块,任由系统去完成。

一个Application开多个Completion Port并没有什么问题,但你必须为每个完成端口
建立工作者线程。另外,还得为每个完成端口建立接受连接线程。

按照MS的权威说法,工作者线程的数量是CPU数量加1比较好。我比较同意这种说法。
 
我的完成端口模型是需要进行座位管理的,但张无忌说不用,我不能理解。当一个Socket
连接成功,需要为这个Socket建立一个端口,建立端口的时候需要给定Key值,而这时候
能不能建立端口还不知道,所以需要订座。端口建立成功,再确认这个座位,否则取消这
次预订。连接不论以什么方式断开都需要释放座位。当工作者线程通知某个Socket有IO时
必定通过Key来对应,这时需要对号。“订座”、“确认”、“取消”、“释放”和“对
号”一共五种管理,必须是线程排它的原子操作。我命名为座位管理,似乎感觉这种管理
必不可少。
 
深奥,看不懂 ;-)
按我的理解accept一个连接后一般都需要分配一块与之关联用来存储相关状态的内存吧(一般是一个类实例),只要CreateIOCompletionPort时将这个类作为Key传进去就可以了嘛。如果失败则直接释放这个类实例,成功的话每次GetQueuedCompletionStatus都会传回这个类,直接进行操作就可以了。只要记得socket被关闭(主动调用CloseSocket或者GetQueuedCompletionStatus返回的传输数为0)时释放这个类就可以了。 除非需要broadcast否则没必要记录所有连接的列表。
 
对这方面我也没什么经验, 听课。 [:)]
看来半天MSDN,没搞明白。
 
Boardcast只是其一,重要的是你在退出的时候需要切断所有的连接、终止所有的工作者线
程。如果没有一个客户表,根本不可想象。如果涉及客户登录的话,如果穿插UDP的话,如
果需要知道有多少用户在线......
而且,没有成功地建立连接的CompletionPort就建立对象是不是太早了点?建立对象的过程
中如何实现线程排它?如果不能线程排它当有并发发生时一定会出错!
 
听课……
以前只搞过阻塞式的,UNIX进程并发及fd轮询,还没有好好用过Winsock的完成端口这东东。
 
今天又看了一下《Win32 多线程程序设计》,其中有一个Completion Port的Socket Echo Server例程,其中在衡量线程池的线程数量时用的是CPU数量 * 2+2的公式,这恐怕不是一个定律,在不同的应用环境中不同的公式都会有各自的优缺点,要在自己的项目中做大量的测试才会得出最佳公式。
我觉得建立多个Completion Port应该不能增加性能吧——跟建立过多的线程一样。
 
我的理解就是 Another_eYes的看法,
按我的理解accept一个连接后一般都需要分配一块与之关联用来存储相关状态的内存吧(一般是一个类实例),只要CreateIOCompletionPort时将这个类作为Key传进去就可以了嘛。如果失败则直接释放这个类实例,成功的话每次GetQueuedCompletionStatus都会传回这个类,直接进行操作就可以了。只要记得socket被关闭(主动调用CloseSocket或者GetQueuedCompletionStatus返回的传输数为0)时释放这个类就可以了。 除非需要broadcast否则没必要记录所有连接的列表。
由于我最近比较忙,没时间帮Another_eYes,如果需要讨论, 发sunhuiNO1@hotmail.com
我会尽力帮忙的。
 
Another_eYes说的也没有错。关键是建立这个类实例的时候还没有为这个连接建立的端口。
我重新研究了一下以前的代码。我认为无论是不是需要Boardcast,对连接的管理都是必要
的。例如,判定同一个ID是否登录了两次;客户的分组管理中通知同组客户在线状态;等
等。
先假定需要建立客户列表,再假定每个连接用类来表示,而不是张无忌从前说的结构。
建立类实例的过程往往在Listen线程中,释放类实例的工作却是在Worker线程中。Listen
线程只有一个,可Worker线程却可以有多个。也就是说有可能两个线程都收到一个关闭连
接的IO。这样在一个Worker线程操作列表的时候,另一个Worker线程必须等待,否则可能
造成混乱。这就是我说的必须做到原子操作。
建立的客户连接类实例除了构造和析构外,一定还有其它的方法,并且因为每个服务器处
理的业务不尽相同,最好提供一些重载的方法。但只有涉及构造和析构的操作才可能影响
到列表操作。所以我宁可构造方法和析构方法由服务管理器来调用,而不是由线程来调用。
线程只需要向服务管理器提交一个建立或释放连接的请求而已。建立连接的请求我称为
Order,释放连接的请求我称为Release。在提交这个请求的时候有可能发生一些异常(最简
地说例如最多只接受1000个并发,但现在却有第1001个请求),这时候就不能建立这个类实
例,我称为Cancel。如果一切正常,那就快快建立这个类实例,我称为Occupy或Ensure。
刚才说了,建立类实例并不是由线程来实现的(如果不同的连接要建立不同的实例,在线程
中很难实现),而是由服务管理器实现的。线程如何得到这个实例?得不到。它只需要知道
这个连接的座位号。这个座位号就是Key值。在处理IO的时候必须将Key值与类实例对应,
这个过程我称为Seek,也需要做原子操作的。
我说清楚了吗?你现在还认为多余吗?
 
看看我的框架:
TClient = class
private
TSocketService: TSocketService;
//客户的Socket信息
FSocket: TSocket;
FAddr: TSockAddrIn;
FHostEnt: THostEnt;
//客户的登录信息
FSeat: Integer;
FID: Integer;
FCookie: Integer;
FName: string;
//客户数据包队列
FRecvPacket: TPacket; //收包只有一个
FSendPackets: TPackets; //发包可是一个队列
protected
procedure EnsureLogin(AID, ACookie: Integer; const AName: string); virtual;
public
constructor Create(SocketService: TSocketService; Sock: TSocket; Seat: Integer);
destructor Destroy; override;
//带PWD登录
function Login(const ID, Pwd: string): Boolean; overload;
//无PWD登录
function Login(const ID: string): Boolean; overload;
//如果没有正在传送的包,直接传送它,否则加到传送队列的最后
procedure Send(Packet: TPacket);
//传送一个错误信息
procedure SendError(const Msg: string);
//解析一个流为数据报,当有一个完整的数据报则处理这笔业务
//如果处理业务时涉及线程安全的部分交由TSocketService完成
procedure Receive(Packet: TPacket);
//调用Overlapped
procedure PostRecv;
procedure PostSend;
enbd;

TSocketService = class
private
//客户表(其中包括空座,以避免Delete操作)
FClients: TList;
//空座表
FEmptyList: TList;
FSection: TRTLCriticalSection;
FServicePort: Integer;
FActive: Boolean;
procedure Lock;
procedure Unlock;
//FClients.Count - FEmptyList.Count就是在线人数
function GetOnlineCount: Integer;
//FClients.Count就是峰值在线人数
function GetPeakCount: Integer;
protected
procedure SetActive(Value: Boolean); virtual;
function CreateClient(Sock: TSocket; Seat: Integer): TClient; virtual;
procedure ReleaseClient(Sender: TClient); virtual;
public
constructor Create(ServicePort: Integer);
destructor Destroy; override;
//线程排它操作
//仅仅只从FEmptyList取最后一个项,并从FEmptyList表中删除
//如果FEmptyList没有了,那就是FClient需要加一个空项,将Index返回
function OrderSeat: Integer;
//调用虚拟方法建立类实例,将Seat项加到FClients中
function EnsureSeat(Sock: TSocket; Seat: Integer): TClient;
//仅仅将Seat加到FEmptyList的最后
procedure CancelSeat(Seat: Integer);
//调用虚拟方法释放类实例,并将Seat加到FEmptyList的最后
procedure ReleaseSeat(Seat: Integer);
//呵呵,原来Seat就是FClient中的Index呀
function SeekSeat(Seat: Integer): TClient;

//通知数据已到或已发,由Worker线程调用
procedure Fire(Seat: Integer; Packet: TPacket; Bytes: Integer);
//属性
property OnlineCount: Integer read GetOnlineCount;
property PeakCount: Integer read GetPeakCount;
property Active: Boolean read FActive write SetActive;
end;
 
TO BARTON:
你这种模式在发送数据时,如果数据很大,工作线程会等待吗?
 
后退
顶部