首先,看看这个程序服务端设计的基本逻辑,其实非常简单,就在一个while(1)循环里面不停地轮询 accept 和 select函数。
有人可能问,accept不是会阻塞,直到有客户端连接进来的吗?
其实当你的socket套接字设置成非阻塞模式,那么accept也不会阻塞。
1.那怎么弄非阻塞呢?
这就涉及到 fcntl函数。fcntl能改变文件的属性。看看他的原型:int fcntl(int fd, int cmd);失败返回-1(当然这个函数还有很多用途,但这里只谈谈他如何实现非阻塞)
三行代码:
- long val = fcntl(sockfd,F_GETFL);
- val|=O_NONBLOCK;
- fcntl(sockfd,F_SETFL,val);
这时候 把sockfd 作为参数给 accept,accept就不会再阻塞。
2.select()函数:select函数能够同时监听多个文件描述符,若其中一个或多个文件描述符有反应(读或写),select就会返回。
原型:int select(nfds, readfds, writefds, errfds, timeout)
fd_set *readfds, *writefds, *errfds; //第2,3,4个参数都是文件描述符集,对Socket编程比较有用的是 readfds。
struct timeval *timeout; //控制select()如何返回,是非阻塞,还是阻塞一定时间返回,还是直接有文件描述符有响应才返回。
函数参数:
1.nfs:最大的文件描述符+1,这个不能错,若不能确定,写一个算法找出来,下面提供的代码有
2.可读文件描述符集。什么意思呢,就是这个集中的文件描述符随时可能给服务器写数据,那作为服务器,就要监测这些文件描述符,若不关心可读,填NULL。
3.可写文件描述符集。这里不用到,就不说了。若不关心,可填NULL
4.异常文件描述符集。若不关心,可填NULL。
5.时间控制结构体。这个结构体里面有2个成员。一个代表秒,一个代表毫秒。两个都设成0表示select将会非阻塞返回。
对此,还有一些列的宏提供给select用:
1.FD_SET(); 用来把文件描述符加到文件描述符集中
2.FD_ZERO(); 清空文件描述符集中的所有描述符
3.FD_ISSET();判断某个文件描述符有没有响应。
注意:select每返回一次后,都要重新清空文件描述符集,和重新把文件描述符加到文件描述符集中。
下面给出服务端的代码:
-
- #include"myhead.h"
-
- struct client_list
- {
- int sock;
- struct client_list *next;
- };
-
-
- struct client_list *head = NULL;
-
- struct client_list *init_list(struct client_list*head)
- {
- head = malloc(sizeof(struct client_list));
- head->sock = -1;
- head->next = NULL;
- return head;
- }
-
-
- int add_sock(struct client_list*head,int new_sock)
- {
- struct client_list *p = head;
- struct client_list *new_node = malloc(sizeof(struct client_list));
- new_node->sock = new_sock;
- new_node->next = NULL;
-
- while(p->next!=NULL)
- {
- p = p->next;
- }
- p->next = new_node;
- return 0;
- }
-
-
- int find_max(struct client_list*head)
- {
- struct client_list *p = head->next;
- if(p==NULL)
- return 0;
- int max_sd = p->sock;
- for(p;p!=NULL;p=p->next)
- {
- if(max_sd < p->sock)
- max_sd = p->sock;
- }
- return max_sd;
- }
-
-
- int write_to_client(struct client_list*head,char *wbuf,int size)
- {
- struct client_list *p=head;
- for(p=head->next;p!=NULL;p=p->next)
- {
- write(p->sock,wbuf,size);
- }
- return 0;
- }
-
-
- void show_client_list(struct client_list*head)
- {
- struct client_list *p = head;
- if(p->next == NULL)
- {
- printf("IS A EMPTY LIST!\n");
- return ;
- }
- else
- {
- puts("client_list is :");
- for(p =head->next; p!=NULL;p = p->next)
- {
- printf("%d ",p->sock);
- }
- printf("\n");
- }
- }
- <pre class="cpp" name="code">
- int del_node(struct client_list*head,int sock)
- {
- struct client_list *p = head->next;
- struct client_list *q = head;
- while(p!=NULL)
- {
- if(p->sock == sock)
- {
- q->next = p->next;
- free(p);
- p = NULL;
- }
- else
- {
- p = p->next;
- q = q->next;
- }
- }
- return 0;
- }
-
- int main(int argc, char const *argv[])
- {
- char rbuf[50]={0};
- char wbuf[50]={0};
-
- int sockfd,size,on=1;
- int new_sock;
- int max_sd;
- struct client_list *pos;
- struct timeval timeout = {0,0};
- fd_set fdset;
- long val;
-
- head = init_list(head);
- pos = head;
-
- struct sockaddr_in saddr;
- struct sockaddr_in caddr;
- size = sizeof(struct sockaddr_in);
- bzero(&saddr,size);
-
- saddr.sin_family = AF_INET;
- saddr.sin_port = htons(8888);
- saddr.sin_addr.s_addr = htonl(INADDR_ANY);
-
- sockfd = socket(AF_INET,SOCK_STREAM,0);
- setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on));
-
-
- val = fcntl(sockfd,F_GETFL);
- val|=O_NONBLOCK;
- fcntl(sockfd,F_SETFL,val);
-
- bind(sockfd,(struct sockaddr*)&saddr,size);
- listen(sockfd,10);
- while(1)
- {
- new_sock = accept(sockfd,(struct sockaddr*)&caddr,&size);
- if (new_sock!= -1)
- {
- puts("new node come!\n");
- printf("new_sock = %d\n",new_sock);
- add_sock(head,new_sock);
-
- show_client_list(head);
- }
-
- max_sd = find_max(head);
- FD_ZERO(&fdset);
- pos = head;
-
- if(pos->next != NULL)
- {
- for(pos=head->next;pos!=NULL;pos=pos->next)
- {
- FD_SET(pos->sock,&fdset);
- }
- }
-
- select(max_sd+1,&fdset,NULL,NULL,&timeout);
-
- for(pos=head->next;pos!=NULL;pos = pos->next)
- {
- if(FD_ISSET(pos->sock,&fdset))
- {
- bzero(rbuf,50);
- read(pos->sock,rbuf,50);
- printf("%s\n",rbuf);
- if(strcmp(rbuf,"quit")==0)
- {
- del_node(head,pos->sock);
- }
- write_to_client(head,rbuf,50);
-
- }
- }
- }
- return 0;
- }</pre>
- <pre></pre>
- <br>
客户端代码:
-
- #include"myhead.h"
-
- int main(int argc, char const *argv[])
- {
- int sockfd;
- char rbuf[50]={0};
- char wbuf[50]={0};
- char ipbuf[50]={0};
- int port;
- int max_sd;
-
- int size,on=1;
- int ret;
- fd_set fdset;
- struct sockaddr_in saddr;
- size = sizeof(struct sockaddr_in);
-
- saddr.sin_family = AF_INET;
- saddr.sin_port = htons(8888);
- saddr.sin_addr.s_addr = inet_addr("192.168.152.128");
-
- sockfd = socket(AF_INET,SOCK_STREAM,0);
- setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on));
-
- ret = connect(sockfd,(struct sockaddr*)&saddr,size);
- if(ret ==0)
- {
- printf("connect sucess\n");
- inet_ntop(AF_INET,(void*)&saddr.sin_addr.s_addr,ipbuf,50);
- port = ntohs(saddr.sin_port);
- printf("ip:%s,port:%d\n",ipbuf,port);
- }
- else if(ret == -1)
- {
- printf("failed to connect\n");
- return -1;
- }
-
- while(1)
- {
- FD_ZERO(&fdset);
- FD_SET(sockfd,&fdset);
- FD_SET(STDIN_FILENO,&fdset);
- max_sd = sockfd>STDIN_FILENO?sockfd:STDIN_FILENO;
- select(max_sd+1,&fdset,NULL,NULL,NULL);
-
- if(FD_ISSET(sockfd,&fdset))
- {
- bzero(rbuf,50);
- read(sockfd,rbuf,50);
- printf("%s\n",rbuf);
- }
- if(FD_ISSET(STDIN_FILENO,&fdset))
- {
- bzero(wbuf,50);
- scanf("%s",wbuf);
- write(sockfd,wbuf,50);
- if(strcmp(wbuf,"quit")==0)
- {
- printf("quit!\n");
- return 0;
- }
- }
- }
- return 0;
- }