现在正在写一个客户端的socket程序,多线程通信,但不是windows平台,操作系统没有提供异步消息,所以得用select检测,然后发送异步消息给制定线程。详细说明如下:
线程A处理应用发来消息,进行socket的connect,send,recv,disconnect,dns等的消息,使用非阻塞。比如connect,线程A调用connect后就返回,然后线程B调用select轮询所有系统中已经创建的socket(加到FD_SET里),当FD_ISSET检测到可写的时候,表明connect成功,然后发消息给线程A,执行相应的回调函数。线程B在系统启动时就已经创建,线程B会对所有已经创建的socket进行select,来检测每个socket是否可写,写读。
但是本人对select的理解不深,如果线程B检测到某个socket可读,代表哪些情况?如果可写,又代表哪些情况?我能明白的是,比如对于connect,如果发送成功,这时select会检测到可写。对于send,检测到可写代表什么意思?
请大家帮忙,最好能提供详细的select的资料,说明可读可写各自代表的意思,包括出错的检测等。谢谢!还有一个问题是,比如对于recv,如果检测到可读,就让线程A读取消息,线程B并不读取,在线程A读取之前,会一直检测到读消息的,我该如何区分第二次读到消息,是A还未读取的,还是A已经读取过后收到的新消息?
说得有点乱,但是感觉这里真的挺复杂。windows平台,系统已经提高了异步消息,一旦connect成功,会收到消息,一旦收到disconnect消息,也会收到消息,然后处理就是了。我现在的平台不支持这样的操作,所以,我得实现这样的异步机制,感觉好难啊。主要还是对select的理解不够。看了不少资料,感觉还是理解不透。
线程A处理应用发来消息,进行socket的connect,send,recv,disconnect,dns等的消息,使用非阻塞。比如connect,线程A调用connect后就返回,然后线程B调用select轮询所有系统中已经创建的socket(加到FD_SET里),当FD_ISSET检测到可写的时候,表明connect成功,然后发消息给线程A,执行相应的回调函数。线程B在系统启动时就已经创建,线程B会对所有已经创建的socket进行select,来检测每个socket是否可写,写读。
但是本人对select的理解不深,如果线程B检测到某个socket可读,代表哪些情况?如果可写,又代表哪些情况?我能明白的是,比如对于connect,如果发送成功,这时select会检测到可写。对于send,检测到可写代表什么意思?
请大家帮忙,最好能提供详细的select的资料,说明可读可写各自代表的意思,包括出错的检测等。谢谢!还有一个问题是,比如对于recv,如果检测到可读,就让线程A读取消息,线程B并不读取,在线程A读取之前,会一直检测到读消息的,我该如何区分第二次读到消息,是A还未读取的,还是A已经读取过后收到的新消息?
说得有点乱,但是感觉这里真的挺复杂。windows平台,系统已经提高了异步消息,一旦connect成功,会收到消息,一旦收到disconnect消息,也会收到消息,然后处理就是了。我现在的平台不支持这样的操作,所以,我得实现这样的异步机制,感觉好难啊。主要还是对select的理解不够。看了不少资料,感觉还是理解不透。
另外,我的线程只判断是否可以可写,具体操作得发送给专门处理数据的线程。自己并不recv,或者send,在数据没有recv之前,会一直检测到可读,这是我的第二个问题。另外,如果服务器主动中断连接,这时也会检测到可读,但实际上什么也读不到的。这又是一个情况。
只要你注册了 可读检查
只要缓冲区里头有数据 select就会返回
你的意思我懂 就是在select返回可读的时候 发送一个消息给上层让上层调用recv来读数据对吧
但是因为是多线程的原因 可能用户还没调用recv的时候 就又执行到select哪里了对吧。。
很明确的告诉你。。你用select的话 这种程序结构无法实现最好是读了 直接把读的数据以消息的形式发送给上层。。
硬要这个弄的话 可以换模型 linux 用epoll的边缘模式 windows用完成端口吧- -
select 认为只要socket的发送缓冲区没有满 就是可写的
协议只需根据收到的数据来组包 包头文不是会有长度吗?
长度不够就先存起来 等有足够的数据再返回好了
貌似boost里面也挺供相关的库
connect成功:select会检测到可写。相对容易些
可以send:select会检测到可写。(只要写缓冲区没满,严格来说,是写缓冲区的空余空间大于吃水线water-,就是可以send)
可以recv:select会检测到可读。(只要读缓冲区不为空,严格来说,是读缓冲区的占用空间超过吃水线water-,就是可以recv)
disconnect: select 检测到可读,但recv返回0.
现在初步考虑,使用状态机和回调函数(类似与阻塞)来区别未读和新消息,区别connect成功和可以send。只是对出错的情况,暂时还不太清楚,应该如何考虑。因为出错,select既检测到可读,也检测到可写。烦!
如果套接字出错
select会返回 不管你注册了写监听或者读监听
如果是对方关闭了 你调用recv会返回0 调用send会正常(但是逻辑上不可能收到数据不做判断就send对吧)
如果是套接字出问题 会返回-1 。。直接closesocket就行了呢
还有select模型一般不建议监听可写事件0 0!!
Select模型:避免在套接字调用过程中被无辜“锁定”的应用程序,采取一种有序的方式,同时进行对多个套接字的管理。
Select函数:用于判断套接字上是否存在数据,或者能否向一个套接字写入数据。
设计select的目的:防止应用程序在套接字处于锁定模式时,在一次I/O绑定调用(如期send/recv)中,被迫进入锁定状态,同时防止套接字处于非锁定模式中时,产生WSAEWOULDBLOCK错误。除非满足事先参数定义的条件,否则select会在进行I/O操作时锁定
Int select(int nfds. //可忽略保证与早期版本兼容
Fd_set FAR* readfds, //用于检查可读性
Fd_set FAR* writefds, //检查可写性
Fd_set FAR* exceptfds, //用于例外数据
Const struct timeval FAR* timeout)
中间三个参数任意两个都可以为空,但至不有一个不能为空,在不为空的的集合中要包含至少一个套接字句柄,否则select便没有任何东西可以等待。最后一个timeval结构用于设定select最多等待I/O操作多久的时间,若timeval为NULL则select会无限期锁定或停顿下去。直到至少有一个描述符符合指定条件后结束。
Struct timeval { long tv_sec; //以秒为单位
long tv_usec; //以毫秒为单位
}
Select 成功完成后:会在fd_set结构中,返回刚好在末完成有I/O操作的所有套接字句柄的总量
用select对套接字进行侦视之前必须设置好fd_set结构,然后再调用select,winsock提供下列宏来针对I/O活动,对fd_set进行处理与检查:
FD_CLR(s , *set):从set中删除套接字
FD_ISSET(s , *set):检查S是否set集合中的一名成员,若是则返回TRUE
FD_SET(s , *set ):将套接字S加入集合set中
FD_ZERO( *set):将set初始化为空集合
Select:返回后会修改每一个fd_set结构,删除那些不存在待决I/O操作的套接字句柄。
1)首先用select对出错检测。检测到错误即退出
2)线程A执行send并且返回EWOULDBLOCK后,此socket状态置sending;执行connect并且返回EINPROGRESD后,此socket状态置connecting.只有状态在sending或者connecting的时候才用select检测可写。
3)最后用select监测可读.
4)如果可以recv,但recv返回0,说明远端断开连接。