使用select函数就可以实现非阻塞编程:异步
//服务端
/*fd_set其实这是一个数组的宏定义,实际上是一long类型的数组,每一个数组元素都能与一打开的文件句柄(socket、文件、管道、设备等)建立联系,建立联系的工作由程序员完成,当调用select()时,由内核根据IO状态修改fd_set的内容,由此来通知执行了select()的进程哪个句柄可读。*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/select.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define N 64
typedef struct sockaddr SA;
int main(int argc, char *argv[])
{
int listenfd, connfd;
char buf[N];
fd_set global_rdfs, current_rdfs;
struct sockaddr_in myaddr;
if (argc < 3)
{
printf("Usage : %s <my_ip> <my_port>\n", argv[0]);
exit(-1);
}
if ((listenfd = socket(PF_INET, SOCK_STREAM, 0)) < 0)
{
perror("fail to socket");
exit(-1);
}
bzero(&myaddr, sizeof(myaddr));
myaddr.sin_family = PF_INET;
myaddr.sin_port = htons(atoi(argv[2]));
myaddr.sin_addr.s_addr = inet_addr(argv[1]);
if ((bind(listenfd, (SA *)&myaddr, sizeof(myaddr))) < 0)
{
perror("fail to bind");
exit(-1);
}
if (listen(listenfd, 5) < 0)
{
perror("fail to listen");
exit(-1);
}
FD_ZERO(&global_rdfs);
FD_SET(listenfd, &global_rdfs);
int i, maxfd = listenfd;
while ( 1 )
{
//一开始 默认listenfd 准备就绪 select筛选listenfd
current_rdfs = global_rdfs;
printf("select start...\n");
/*再此阻塞 直到 i/o准备就绪 即客户端请求连接了*/
if (select(maxfd+1, ¤t_rdfs, NULL, NULL, NULL) < 0)
{
perror("fail to select");
exit(-1);
}
printf("select after...\n");
printf("maxfd = %d\n", maxfd);
#if 1
/* 证明了由内核根据IO状态修改current_rdfs的内容,由此来通知执行了select()的进程哪个句柄可读*/
if (FD_ISSET(3, ¤t_rdfs)) {
printf("3 in current_rdfs\n");
}else printf("3 is not in current_rdfs\n");
if (FD_ISSET(3, &global_rdfs)) {
printf("3 in global_rdfs\n");
}else printf("3 is not in global_rdfs\n");
#endif
for (i=0; i <= maxfd; i++)
{
printf("for times %d...\n", i);
if (FD_ISSET(i, ¤t_rdfs))
{
printf("i = %d in current_rdfs...\n", i);
if (i == listenfd)
{
printf("i == %d listenfd start...\n", i);
if ((connfd = accept(i, NULL, NULL)) < 0)
{
perror("fail to accept");
continue;
}
printf("kkkkk...connfd = %d\n", connfd);
FD_SET(connfd, &global_rdfs);
//maxfd 改变
maxfd = (maxfd < connfd ? connfd : maxfd);
}
else
{
printf("listenfd = %d .... ooooo\n" , listenfd);
if (recv(i, buf, N, 0) <= 0)
{
close(i); //掉线关闭套接字描述符
FD_CLR(i, &global_rdfs); //清楚集合中的该套接字描述符
printf("peer closed\n");
}
else
{
send(i, buf, N, 0);
}
}
}
} // end for
printf("start while()...\n");
} // end while
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>
#define N 64
typedef struct sockaddr SA;
int main(int argc, char *argv[])
{
int sockfd;
char buf[N];
struct sockaddr_in servaddr;
if (argc < 3)
{
printf("Usage : %s <serv_ip> <serv_port>\n", argv[0]);
exit(-1);
}
if ((sockfd = socket(PF_INET, SOCK_STREAM, 0)) < 0)
{
perror("fail to socket");
exit(-1);
}
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = PF_INET;
servaddr.sin_port = htons(atoi(argv[2]));
servaddr.sin_addr.s_addr = inet_addr(argv[1]);
if ((connect(sockfd, (SA *)&servaddr, sizeof(servaddr))) < 0)
{
perror("fail to connect");
exit(-1);
}
while ( 1 )
{
printf("<client> ");
fgets(buf, N, stdin);
if (strncmp(buf, "quit", 4) == 0)
{
close(sockfd);
exit(0);
}
printf("sockfd = %d\n",sockfd);
send(sockfd, buf, N, 0);
recv(sockfd, buf, N, 0);
printf("<from server> %s", buf);
}
return 0;
}
分析以上select函数: 当建立连接时
再建立一个连接时:
可以方发现多个连接:服务端监听套接字不变,连接套接字 4 5 6...... 加, 并且当某一个连接的客户 io准备就绪时,内核会选择具体的current_rdfs集合中的某一个,实现多路复用。
比如 :客户端套接字3 与服务端连接套接字5的通信:
比如:客户端套接字3 与服务端连接套接字4的通信
另外扩展:点击打开链接