阻塞与非阻塞
阻塞说的不是某一个函数,说的是文件描述符的一种属性。因为文件描述符有阻塞属性,所以对这个文件描述符的相关操作是阻塞的。
阻塞 I/O
发生阻塞的函数:
读操作:
read
、
recv
、
recvfrom
写操作:
write
、
send
其他操作:
accept
、
connect
总结:阻塞并不是函数的属性,是文件描述符的属性
非阻塞 I/O
非阻塞实现:fcntl
int flag = fcntl(sockfd,F_GETFL,0); // 读 flagflag |= O_NONBLOCK; // 修改 flagfcntl(sockfd,F_SETFL,flag); // 写 flag
多路复用
构建一张有关文件描述符的表,然后调用函数,当这些文件描述符中某个准备好,函数返回告诉哪个描述符就绪,进行相应操作。

select()函数
int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);
功能:函数可以监视多个文件描述符,监视这些文件描述符是否处于
"
就绪
"
状态。
参数:
nfds:
最大的文件描述符
+1
readfds
:读集合,放在这个集合中的文件描述符会被监视是否具备读属性;
writefds
:写集合,放在这个集合中的文件描述符会被监视是否具备写属性;
exceptfds:异常属性,放在这个集合中的文件描述符会被监视是否具备异常属性;
返回值:成功返回满足要求的文件描述符的个数,失败返回
-1
一旦有文件描述符
"
就绪
"
1.
解除阻塞
2.
从集合中删除除了刚刚解除其阻塞的文件描述符之外的所有文件描述符。
#include <sys/time.h>#include <sys/types.h>#include <unistd.h>void FD_CLR(int fd, fd_set *set); 从集合中删除一个文件描述符 fdint FD_ISSET(int fd, fd_set *set); 判断集合中有没有这个文件描述符 fdvoid FD_SET(int fd, fd_set *set); 将一个文件描述符 fd 添加到集合中void FD_ZERO(fd_set *set); 清空集合int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);nfds : 集合中最大文件描述符 +1fd_set :集合类型readfds : 监测文件描述符的读属性writefdsexceptfdstimeout : 超时时间:在规定时间内进行阻塞,超过这个时间就解除阻塞 0 :不阻塞 >0 : 超时时间 NULL :一直阻塞struct timeval {long tv_sec; /* seconds */long tv_usec; /* microseconds */};
fd_set myset;
while(1)
{
FD_ZERO(&myset);
FD_SET(0,&myset);//标准输入设备文件
FD_SET(newfd,&myset); //客户端
socket
文件描述符
select(newfd+1,&myset,NULL,NULL,NULL);
if(FD_ISSET(0,&myset)) //标准输入解除阻塞
{
scanf( ... );
send( ... );
}
if(FD_ISSET(newfd,&myset)) //newfd 解除阻塞
{
recv( ... );
}
}
定义一个读属性的集合;
把相关方的文件描述符添加到读属性的集合;
调用
select
检测读属性的集合
,
只要集合中任意一个文件描述符就绪
,
才解除阻塞;
判断谁就绪
,
做相应的操作;
示例:
/*
服务端键盘输入发送字符串客户端,客户端键盘输入发送字符串给服务端
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <pthread.h>
int fd;
void *recv_mess(void *arg)
{
char buf[50];
int ret;
pthread_detach(pthread_self());
while(1) {
bzero(buf, 50);
ret = recv(fd, buf, sizeof(buf),0);
if (ret<=0) break;
printf("client recv mess=%s\n",buf);
}
return NULL;
}
int main(void)
{
int ret;
//1.要有电话
fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd==-1) {
perror("socket");
exit(1);
}
//2.绑卡
#if 0
struct sockaddr_in client;
client.sin_family = AF_INET; //ipv4
client.sin_port = htons(12346);
client.sin_addr.s_addr = inet_addr("192.168.10.251");
//inet_aton("192.168.10.251", &server.sin_addr);
ret = bind(fd, (struct sockaddr *)&client, sizeof(client));
if (ret==-1) {
perror("bind");
exit(1);
}
#endif
//3.拨号 请求连接返回成功建立连接
struct sockaddr_in server;
server.sin_family = AF_INET; //ipv4
server.sin_port = htons(12345);
//server.sin_addr.s_addr = inet_addr("192.168.10.251");
inet_aton("192.168.10.251", &server.sin_addr);
ret = connect(fd, (struct sockaddr *)&server, sizeof(server));
if (ret==-1) {
perror("connect");
exit(1);
}
system("netstat -an | grep 12345"); // 查看连接状态
//创建线程
pthread_t tid;
pthread_create(&tid, NULL, recv_mess, NULL);
//4.收发数据
char buf[50] ;
while (1) {
bzero(buf, 50);
printf("pls input your string:");
scanf("%s", buf);
ret = send(fd, buf, strlen(buf), 0);
printf("send=%d\n", ret);
}
//5.挂电话
close(fd);
return 0;
}
/*
服务端键盘输入发送字符串客户端
sanf fd=0
客户端键盘输入发送字符串给服务端
recv fd=newfd
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
int main(void)
{
int ret;
//1.要有电话
int fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd==-1) {
perror("socket");
exit(1);
}
int on=1;
setsockopt(fd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on)); //重用端口号 on设置为真
//2.绑卡
struct sockaddr_in server;
server.sin_family = AF_INET; //ipv4
server.sin_port = htons(12345);
server.sin_addr.s_addr = inet_addr("192.168.10.251");
//inet_aton("192.168.10.251", &server.sin_addr);
ret = bind(fd, (struct sockaddr *)&server, sizeof(server));
if (ret==-1) {
perror("bind");
exit(1);
}
//3.开机 监听
listen(fd, 5);
//4.接听 连接连接
struct sockaddr_in client; //做客户端来电显示对象 ip+port
socklen_t len=sizeof(client);
printf("tcp sever have started ...\n");
system("netstat -an | grep 12345"); // 查看连接状态
int newfd = accept(fd, (struct sockaddr *)&client, &len); //建立连接前处于阻塞状态
if (newfd==-1) {
perror("accept");
exit(1);
}
// 打印连接服务器端的客户端的ip和port
printf("ip=%s\n", inet_ntoa(client.sin_addr));
printf("port=%d\n", ntohs(client.sin_port));
printf("len=%d\n", len);
printf("newfd=%d\n", newfd);
//5.收发数据
// 键盘输入 fd1=0 , 客户端输入 fd2=newfd,用select 监控fd1,fd2
//1)定义一个读属性的集合
fd_set myset;
char buf[50];
while(1) {
//2)把相关的文件描述符添加到读属性的集合
FD_ZERO(&myset);
FD_SET(0, &myset);
FD_SET(newfd, &myset);
//3)调用select检测读属性的集合,只要集合中任意一个文件描述符就绪,才解除阻塞
ret=select(newfd+1, &myset, NULL, NULL, NULL);
printf("select ret=%d\n", ret);
//4)判断谁就绪,做相应的操作
if(FD_ISSET(0, &myset)) {
//键盘输入
bzero(buf, 50);
//printf("pls a string:");
scanf("%s", buf);
ret = send(newfd, buf, strlen(buf), 0);
printf("send=%d\n", ret);
}
if(FD_ISSET(newfd, &myset)) {
//客户端输入
bzero(buf, 50);
ret = recv(newfd, buf, sizeof(buf), 0);
printf("recv mess:%s\n", buf);
}
}
//6.挂电话
close(newfd);
close(fd);
return 0;
}
poll() 函数
#include <poll.h>int poll(struct pollfd *fds, nfds_t nfds, int timeout);
功能:检测
fds
数组中的文件描述符对应需要检测的属性,如果具备检测的属性,则解除阻塞。
参数:
fds
:文件描述符所在结构体的数组。
struct pollfd {int fd; /* 文件描述符 */short events; /* 检测的事件 */short revents; /* 返回的事件 */用于判定检测的事件是否发生,不是自己填充,当发生检测事件时由系统填充,用户判断}
nfds
:检测的个数
(
结构体数组的成员个数
)
timeout
:
>0 :
具体阻塞时间
(
毫秒
) 0
:不阻塞
<0
:一直阻塞
返回值:
成功:非
0
,
0
表示到了阻塞时间还没有发生检测事件
失败:
-1
,并置错误
struct pollfd {int fd; /* 文件描述符 */short events; /* 请求事件 */POLLERR 异常POLLRDNORM POLLIN 读POLLWRNORM 写short revents; /* 返回事件 */ fds[0].revents&POLLIN 用于判断};
fds
:存放文件描述符的结构体数组首地址
nfds
:文件描述符的个数
timeout
: 超时时间
毫秒为单位
0
:直接返回
>0 :
阻塞时间
-1
:永远阻塞
返回值:成功大于
0
,失败返回
-1
struct pollfd fds[2];fds[0].fd = 0;fds[0].events = POLLIN; // 可读事件fds[1].fd = fd;fds[1].events = POLLIN;poll(fds,2,-1); // 一直阻塞 , 直到某个文件描述符就绪if(fds[0].revents & POLLIN) // 判断这个文件描述符实际发生的事件与可读事件是否一致{scanfsend}if(fds[1].revents & POLLIN){recv}
/*
服务端键盘输入发送字符串客户端,客户端键盘输入发送字符串给服务端
poll函数
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <pthread.h>
#include <poll.h>
int main(void)
{
int ret;
int fd;
//1.要有电话
fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd==-1) {
perror("socket");
exit(1);
}
//2.绑卡
#if 0
struct sockaddr_in client;
client.sin_family = AF_INET; //ipv4
client.sin_port = htons(12346);
client.sin_addr.s_addr = inet_addr("192.168.10.251");
//inet_aton("192.168.10.251", &server.sin_addr);
ret = bind(fd, (struct sockaddr *)&client, sizeof(client));
if (ret==-1) {
perror("bind");
exit(1);
}
#endif
//3.拨号 请求连接返回成功建立连接
struct sockaddr_in server;
server.sin_family = AF_INET; //ipv4
server.sin_port = htons(12345);
//server.sin_addr.s_addr = inet_addr("192.168.10.251");
inet_aton("192.168.10.251", &server.sin_addr);
ret = connect(fd, (struct sockaddr *)&server, sizeof(server));
if (ret==-1) {
perror("connect");
exit(1);
}
system("netstat -an | grep 12345"); // 查看连接状态
//4.收发数据
char buf[50] ;
//创建pollfd数组
struct pollfd fds[2];
//分别初始化
fds[0].fd = 0; //键盘输入
fds[0].events = POLLIN;//注册事件
fds[1].fd = fd; //读服务器端数据的fd
fds[1].events = POLLIN;
while (1) {
//调用poll 监控fds数组的fd是否就绪
poll(fds, 2, -1); //没就绪全部阻塞
if(fds[0].revents & POLLIN) {
bzero(buf, 50);
printf("pls input your string:");
scanf("%s", buf);
ret = send(fd, buf, strlen(buf), 0);
printf("send=%d\n", ret);
}
if(fds[1].revents & POLLIN) {
bzero(buf, 50);
ret = recv(fd, buf, sizeof(buf),0);
if (ret<=0) break;
printf("client recv mess=%s\n",buf);
}
}
//5.挂电话
close(fd);
return 0;
}
/*
服务端键盘输入发送字符串客户端,客户端键盘输入发送字符串给服务端
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
void *send_mess(void *arg)
{
int newfd = *((int *)arg);
int ret;
char buf[50] ;
pthread_detach(pthread_self());
while (1) {
bzero(buf, 50);
printf("pls input your string:");
scanf("%s", buf);
ret = send(newfd, buf, strlen(buf), 0);
printf("send=%d\n", ret);
}
}
int main(void)
{
int ret;
//1.要有电话
int fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd==-1) {
perror("socket");
exit(1);
}
int on=1;
setsockopt(fd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on)); //重用端口号 on设置为真
//2.绑卡
struct sockaddr_in server;
server.sin_family = AF_INET; //ipv4
server.sin_port = htons(12345);
server.sin_addr.s_addr = inet_addr("192.168.10.251");
//inet_aton("192.168.10.251", &server.sin_addr);
ret = bind(fd, (struct sockaddr *)&server, sizeof(server));
if (ret==-1) {
perror("bind");
exit(1);
}
//3.开机 监听
listen(fd, 5);
//4.接听 连接连接
struct sockaddr_in client; //做客户端来电显示对象 ip+port
socklen_t len=sizeof(client);
printf("tcp sever have started ...\n");
system("netstat -an | grep 12345"); // 查看连接状态
int newfd = accept(fd, (struct sockaddr *)&client, &len); //建立连接前处于阻塞状态
if (newfd==-1) {
perror("accept");
exit(1);
}
// 打印连接服务器端的客户端的ip和port
printf("ip=%s\n", inet_ntoa(client.sin_addr));
printf("port=%d\n", ntohs(client.sin_port));
printf("len=%d\n", len);
//创建线程
pthread_t tid;
pthread_create(&tid, NULL, send_mess, &newfd);
//5.收发数据
char buf[50];
while (1) {
bzero(buf, 50);
ret = recv(newfd, buf, sizeof(buf), 0);
printf("recv mess:%s\n", buf);
printf("ret=%d\n", ret);
if (ret==0) break;
}
//6.挂电话
close(newfd);
close(fd);
return 0;
}