一、阻塞模式
阻塞是socket的缺省方式,也是最常用的方式,即调用结果返回之前,函数阻塞,当前线程会挂
起,
suspend
。
可能造成阻塞的函数有:connect()、accept()、读写函数、gethostbyname()等。
二、再探
send
和
recv
•
send
和
recv
是
socket
编程中两个核心的函数。
•
send
表示发送数据,其实际上并不是直接将数据发送出去,而是将数据先发送到系统对应的
socket
的缓冲区,在
由系统使用
tcp
/
ip
协议进行发送。此时
send
返回的结果只是表明是否成功发送到系统的缓冲区。
•
同样
recv
函数也是从系统的对应的
socket
缓冲区读取数据,而该缓冲区中的数据也是由协议保证的。如果该缓
没有数据,将区别对待:如果对应的
socket
是阻塞的,则
recv
工作在阻塞模式,将一直等到缓冲区中有数据才返
回,否则会一直挂起。
三、非阻塞模式
•
非阻塞和阻塞的概念相对应,指在不能立刻得到结果之前,该函数不会阻塞当前线程,而会立刻返回。
•
如果指定非阻塞模式,程序调用可能造成阻塞的函数时,如果会发生阻塞,这些函数返回
-1
并将
errno
设
为
EAGAIN
或
EWOULDBLOCK
,程序可继续向下运行。可能阻塞的函数对应的任务完成,则再次调用该函数时就
返回
0
表示运行结束。
四、fcntl函数
函数
fcntl
()
针对
(
文件
)
描述符提供控制。若要改变阻塞模式,可如下调用:
非阻塞
I/O
:可将
cmd
设置为
F_SETFL
,将
lock
设置为
O_NONBLOCK
。
异步
I/O
:可将
cmd
设置为
F_SETFL
,将
lock
设置为
O_ASYNC
。
五、非阻塞模式流程
• server端
while (true)
if 有新连接{
建立并记录该新连接;
for (所有的有效连接){
if 该连接中有字符可读{
读入字符串;
for (所有其他的有效连接) {
将该字符串发送给该连接;
}
}
}
}
•
client端
while (true){
if (
与server的连接有字符可读
) {
从该连接读入
;
输出到标准输出上去
;
}
if (
标准输入可读
) {
从标准输入读入
;
发送
到与server的连接中去
.
}
}
六、轮询
•
非阻塞模式可以避免程序死锁,但是需要程序不断检查各个可能阻塞的函数的状态,当一个应用程序使用了非阻
塞模式的套接字,它需要使用一个循环来不听的测试是否一个文件描述符有数据可读(称做轮询,
polling
)。
•
分析上面的程序可以知道
,
不管是
server
还是
client,
它们都不停的轮流查询各个文件描述符
,
一旦可读就读入并进
行处理。
•
应用程序不停的
polling
内核来检查是否
I/O
操作已经就绪,对系统资源的消耗非常大。
server
或者
client
单独执
行时
,CPU
资源的
98%
左右都被其占用。这将是一个极浪费
CPU
资源的操作,因此实际开发中基本不使用。
七、I/O多路复用
•
虽然我们不希望在某一个用户没有反应时阻塞其他的用户,但我们却应该在没有任何用户有反应的情况之下停止
程
序的运行,让出抢占的系统资源,进入阻塞状态。
•
使用
select()
、
poll()
等函数实现对多个
socket
的同步
I/O
操作。它能同时等待多个
socket
描述符,而这些
socke题,
述符其中的任意一个进入读就绪
/
写就绪
/
出错状态,
select()
函数就可以返回。
八、select函数
select方法中,所有文件描述符都是阻塞的。使用select判断一组文件描述符中是否有一个可读(写), 如果没有
就
阻塞
,
直到有一个的时候就被唤醒。
九、fd_set函数
fd_set可以理解为一个集合,这个集合中存放的是文件描述符(file descriptor),这可以是我们所说的普通意义的
文件,也包括Linux下任何设备、管道、FIFO、socket等。
2013.11.21 晚上