背景:我一开始建立一个套接字的时候,发现系统内核将它设置为了阻塞IO模式。这时候我想将其设置为阻塞IO模式。使用fcntl()和iocntl()函数实现。
1、int fcntl(int fd, int cmd, ... /* arg */ );
int flag;
flag = fcntl(sockfd, F_GETFL, 0);
flag |= O_NONBLOCK;
fcntl(sockfd, F_SETFL, flag);
2、iocnt
int b_on =1;
ioctl(sock_fd, FIONBIO, &b_on);
多路复用I/O:
accept会阻塞,新建立好的newfd里面read数据也会阻塞。可以使用多线程或者多进程解决。
多路复用解决:把所有fd(文件描述符)放入集合内,监控关注的fd,一个或者几个有数据的时候,退出来,判断到底哪个是有数据了。
linux下每个进程默认情况下最多可以打开1024个文件描述符fd,文件描述符的特点:
非负整数;
从最小的数字分配;
每个启动进程默认打开0,1,2三个文件描述符。
多路复用不仅支持套接字fd,也支持普通文件的fd。
linux下如何实现多路复用?
fd_set集合(数组):里面存放了该进程的文件描述符。0~1024
每一位代表一个文件描述符, 为了监控每个文件描述符,又开辟了一个fd_set类型数组,将fd_set集合中要监控的数组位置设置为1,监控的文件描述符放入集合里面,1024/8,每位表示一个文件描述符,新开辟的数组可以监控多少个文件描述符1024/8,这样算下来还是有些多了。
再想一个办法,拿到最大的文件描述符maxfd,开辟一个maxfd+1大小的数组,但是在内核里面是32位的CPU,开辟数组的时候一般是四个字节的整数倍,比如开辟maxfd+1 = 0+1/3+1/7+1大小的数组。
多路复用模型:
把关心的文件描述符fd加入到监控集合中;
调用select或者poll函数监控集合fd_set中那些文件描述符(阻塞等待集合中一个或多个文件描述符有数据);
当有数据时,退出select或者poll阻塞;
依次判断哪个文件描述符有数据;
依次处理有数据的文件描述符的数据。
实现过程:
1、fd_set(内核中一个数组),调用一些函数围绕它操作。
设置一个读rset:读集合; FD_ZERO()清除集合里面的数据,FD_SET()把关心的fd加入集合,FD_CLR()从集合中清除fd,FD_ISSET()判断fd是否在set中。
void FD_CLR(int fd, fd_set *set);
int FD_ISSET(int fd, fd_set *set);
void FD_SET(int fd, fd_set *set);
void FD_ZERO(fd_set *set);
2、调用select监控集合
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
参数:nfds= maxfd+1(因为数组是从0)开始的,readfds读集合,writefds写集合,exceptfds异常集合,timeout超时:
一般情况下:读结合填写rset, 写集合填NULL(不需要阻塞),异常集合填(带外数据填0)其它情况填NULL,timeout有结构体:
struct timeval{
long tv_sec; //秒
long tv_usec; //微秒
}
时间单位:1秒(s) = 10^3毫秒(ms)=10^6微秒(us) = 10^9纳秒(ns) = 10^12皮秒(ps)
注意:select退出后,集合是有数据的集合(关心的数据已经加入集合)
3、判断
if(FD_ISSET(fd, rset)){
如果要监听的套接字fd有数据,则有新的客户端连接,则accept;
如果已经建立连接的套接字有数据,则read读取客户端数据。
}
实例:
服务器:
void sig_child_handle(int signo){
if(SIGCHLD == signo){
//NULL:状态不许要,WNOHANG非阻塞方式
waitpid(-1, NULL, WNOHANG);
}
}
void cli_data_handle(int *arg);
int main(int argc, char *argv[])
{
//1.create socket
int sockfd;
struct sockaddr_in sin;
signal(SIGCHLD, sig_child_handle);
if((sockfd = socket(AF_INET, SOCK_STREAM, 0))<0){
perror("create socket");
return -1;
}
//2.bind ip
//2.define a struct, clear and fill it
bzero(&sin, sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_port = htons(SERV_PORT); //transform internet byte order
//优化1:让服务程序绑定在任意IP上
sin.sin_addr.s_addr = htonl(INADDR_ANY);
if(inet_pton(AF_INET, SERV_IP_ADDR, (void *)&sin.sin_addr.s_addr) != 1){
perror("inet_pton");
return 1;
}
if( ( bind(sockfd, (struct sockaddr *)(&sin), sizeof(sin)) ) == -1){
perror("bind");
exit(1);
}
//3.listen
if(listen(sockfd, BACKLOG)<0){
perror("listen");
exit(1);
}
printf("server start\n");
//4.accepet()阻塞客户端请求
int newfd;
struct sockaddr_in cin;
socklen_t addrlen = sizeof(cin);
while(1){
pid_t pid = -1;
if((newfd = accept(sockfd, (struct sockaddr *)&cin, &addrlen))<0){
perror("accept");
break;
}
//创建一个子进程,用于处理连接的客户端的交互数据
if((pid = fork()) < 0){
perror("fork");
exit(1);
}
if(pid > 0){//父进程
close(newfd);
}else if(pid == 0){//子进程
close(sockfd);
char ipv4_addr[16]; //xxx.xxx.xxx.xxx
if( inet_ntop(AF_INET, (void *)&cin.sin_addr, ipv4_addr, sizeof(cin)) == NULL){
perror("inet_top");
exit(1);
}
printf("client(%s:%d)\n", ipv4_addr, htons(cin.sin_port));
cli_data_handle(&newfd);
return 0;
}
}
//6.close
close(sockfd);
return 0;
}
void cli_data_handle(int *arg){
int newfd = *(int *)arg;
printf("child handling process:newfd=%d\n", newfd);
//5.write() 与newfd读写数据
char buf[128];
char resp_buf[138];
int ret = -1;
while(1){
bzero(buf, strlen(buf));
do{
ret = read(newfd, buf, 127);
}while(ret<0 && errno==EINTR);
if(ret < 0){
perror("read");
exit(1);
}
//客户端已经关闭
if(!ret){
break;
}
printf("recive data:%s\n", buf);
bzero(resp_buf, 138);
strncpy(resp_buf, SERV_RESP_STR, strlen(SERV_RESP_STR));
strcat(resp_buf, buf);
do{
ret = write(newfd, resp_buf, strlen(resp_buf));
}while(ret <0 &&EINTR == errno);
if( !strncasecmp(buf, QUIT_STR, strlen(buf)-1) ){
printf("client(%d) exiting\n", newfd);
break;
}
}
close(newfd);
}
客户端:
/*./client SERV_IP_ADDR SERV_PORT */
#include "inet.h"
void usage(char *s){
printf("\n%s serv_ip serv_port", s);
printf("\n\t serv_ip: server ip address");
printf("\n\t sert_port: server port(>5000)\n\n");
}
int main(int argc, char *argv[])
{
int port;
if(argc != 3){
usage(argv[0]);
exit(1);
}
//1.create socket
int sockfd;
if((sockfd = socket(AF_INET, SOCK_STREAM, 0))<0){
perror("create socket");
exit(1);
}
//2.连接服务器
//填充sockaddr_in 结构体变量,
port =atoi(argv[2]);
if(port < 5000){
usage(argv[0]);
exit(1);
}
struct sockaddr_in sin;
bzero(&sin, sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_port = htons(port);//网络字节序端口号
#if 0
client_addr.sin_addr.s_addr = inet_addr(SERV_IP_ADDR);
#else
if(( inet_pton(AF_INET, argv[1], (void *)&sin.sin_addr)) != 1){
perror("inet_pton");
exit(1);
}
#endif
if(connect(sockfd, (struct sockaddr *)&sin, sizeof(sin)) < 0){
perror("connect");
exit(1);
}
printf("client start....\n");
#if 0
#else
fd_set rset;
struct timeval tout;
int maxfd = -1;
char buf[128];
int ret;
while(1){
FD_ZERO(&rset);
FD_SET(0, &rset);
FD_SET(sockfd, &rset);
maxfd = sockfd;
tout.tv_sec = 5;
tout.tv_usec = 0;
select(maxfd+1, &rset, NULL, NULL, &tout);
if(FD_ISSET(0, &rset)){ //如果文件描述符在集合rset里面,表示键盘有输入
//读取键盘输入,发送到网络套接字
bzero(buf, 128);
do{
ret = read(0, buf, 127);
}while(ret<0 && EINTR == errno);
if(ret < 0 ){
perror("read");
continue;
}
if(!ret) break; /* 键盘没有输入数据*/
if(write(sockfd, buf, strlen(buf)) < 0){
perror("error");
break;
}
if(!strncasecmp(buf, QUIT_STR, strlen(QUIT_STR)) ){
printf("client(%d) is exiting\n", sockfd);
break;
}
}
if(FD_ISSET(sockfd, &rset)){ //服务器给发送过来的数据
//读取套接字内容
bzero(buf, 127);
do{
ret = read(sockfd, buf, 127);
}while(ret<0 && EINTR == errno);
if(ret < 0 ){
perror("read");
continue;
}
if(!ret) break;
printf("server said: %s\n", buf);
if((strlen(buf) > strlen(SERV_RESP_STR)) &&
(!strncasecmp(buf+strlen(SERV_RESP_STR), QUIT_STR, strlen(QUIT_STR)))){
printf("client is exiting!\n");
break;
}
}
}
#endif
//4.close()关闭套接字
close(sockfd);
return 0;
}