Socket网络编程--聊天程序(4)
2014-07-27 11:25
639 查看
上一小节讲到可以实现多客户端与服务器进行通讯,对于每一个客户端的连接请求,服务器都要分配一个进程进行处理。对于多用户连接时,服务器会受不了的,而且还很消耗资源。据说有个select函数可以用,好像还很NB的样子。
使用select多路转换处理聊天程序
下面摘取APUE 14.5小结 I/O多路转接
当从一个描述符读,然后又写到另一个描述符时,可以在下列形式的循环中循环中使用阻塞I/O:
while((n = read(STDIN_FILENO, buf, BUFFSIZ))>0)
if(write(STDOUT_FILENO, buf, n)!=n)
err_sys("write error");
这种形式的阻塞I/O到处可见。但是如果必须从两个描述符读,又将如何呢?如果仍旧使用阻塞I/O,那么就可能长时间阻塞在一个描述符上,而另一个描述符虽有很多数据却不能得到及时处理。所以为了处理这种情况显然需要另一种不同的技术。
方法一:也就是上一小节使用的方法,使用多进程。每一个进程处理一个描述符
方法二:和上面相似的,使用多线程,不同的线程处理不同的描述符
方法三:仍然使用一个进程执行该程序,但使用非阻塞I/O读取数据。然后对所有的描述符进行遍历一遍,判断对应的描述符是否有数据,如果有就读取,如果没有就立即返回。这种办法就是轮询(polling)
方法四:异步I/O。其基本的思想是进程告诉内核,当一个描述符已经准备好可以进行I/O时,用一个信号通知它。
方法五:这是一种比较好的办法。叫做I/O多路转换(I/O multiplexing)。先构造一张有关描述符的列表,然后调用一个函数,直到这些描述符中的一个已经准备好进行I/O时,该函数才会返回。在返回时,它高数进程哪些描述符已经准备好可以进行I/O。
poll,pselect和select这三个函数使我们能够执行I/O多路转换。本程序只使用select函数。
#include <sys/select.h>
int select (int maxfdp1, fd_set *restrict readfds, fd_set *restrict writefds, fd_set *restrict exceptfds, struct time val *restrict tvptr); //返回值:准备就绪的描述符数,若超时则返回0,否则出错返回-1
select 函数讲解
FD_ISSET判断描述符fd是否在给定的描述符集fdset中,通常配合select函数使用,由于select函数成功返回时会将未准备好的描述符位清零。通常我们使用FD_ISSET是为了检查在select函数返回后,某个描述符是否准备好,以便进行接下来的处理操作。
fd_set数据类型的操作
#include <sys/select.h>
int FD_ISSET(int fd, fd_set *fdset); //判断fd是否在fdset中
void FD_CLR(int fd, fd_set *fdset); //进fd从fdset中取出
void FD_SET(int fd, fd_set *fdset); //将fd放入fdset
void FD_ZERO(fd_set *fdset); //将fdset清空
timeval结构分析
struct timeval{
long tv_sec; //seconds
long tv_usec; //and microseconds
};
client.c的代码没有改
server.c的代码如下
运行后的截图结果
![](http://images.cnitblog.com/i/485067/201407/262135407443011.jpg)
可以看出三个客户端都可以随时连接到服务器,并且发送数据给服务器。实现的效果跟上一节的多进程实现是一样的。毕竟没有大量客户端进行连接,所以就看不出效果,从书中和网上介绍说,这样可以提高某些方面的性能。
下一节将介绍服务器端向各个还在线的客户端进行发送数据,实现交互。然后再实现聊天室功能,大概的思路就是对接收到的数据进行转发。
参考资料: /article/6093974.html
本文出处: /article/6193120.html
使用select多路转换处理聊天程序
下面摘取APUE 14.5小结 I/O多路转接
当从一个描述符读,然后又写到另一个描述符时,可以在下列形式的循环中循环中使用阻塞I/O:
while((n = read(STDIN_FILENO, buf, BUFFSIZ))>0)
if(write(STDOUT_FILENO, buf, n)!=n)
err_sys("write error");
这种形式的阻塞I/O到处可见。但是如果必须从两个描述符读,又将如何呢?如果仍旧使用阻塞I/O,那么就可能长时间阻塞在一个描述符上,而另一个描述符虽有很多数据却不能得到及时处理。所以为了处理这种情况显然需要另一种不同的技术。
方法一:也就是上一小节使用的方法,使用多进程。每一个进程处理一个描述符
方法二:和上面相似的,使用多线程,不同的线程处理不同的描述符
方法三:仍然使用一个进程执行该程序,但使用非阻塞I/O读取数据。然后对所有的描述符进行遍历一遍,判断对应的描述符是否有数据,如果有就读取,如果没有就立即返回。这种办法就是轮询(polling)
方法四:异步I/O。其基本的思想是进程告诉内核,当一个描述符已经准备好可以进行I/O时,用一个信号通知它。
方法五:这是一种比较好的办法。叫做I/O多路转换(I/O multiplexing)。先构造一张有关描述符的列表,然后调用一个函数,直到这些描述符中的一个已经准备好进行I/O时,该函数才会返回。在返回时,它高数进程哪些描述符已经准备好可以进行I/O。
poll,pselect和select这三个函数使我们能够执行I/O多路转换。本程序只使用select函数。
#include <sys/select.h>
int select (int maxfdp1, fd_set *restrict readfds, fd_set *restrict writefds, fd_set *restrict exceptfds, struct time val *restrict tvptr); //返回值:准备就绪的描述符数,若超时则返回0,否则出错返回-1
select 函数讲解
FD_ISSET判断描述符fd是否在给定的描述符集fdset中,通常配合select函数使用,由于select函数成功返回时会将未准备好的描述符位清零。通常我们使用FD_ISSET是为了检查在select函数返回后,某个描述符是否准备好,以便进行接下来的处理操作。
fd_set数据类型的操作
#include <sys/select.h>
int FD_ISSET(int fd, fd_set *fdset); //判断fd是否在fdset中
void FD_CLR(int fd, fd_set *fdset); //进fd从fdset中取出
void FD_SET(int fd, fd_set *fdset); //将fd放入fdset
void FD_ZERO(fd_set *fdset); //将fdset清空
timeval结构分析
struct timeval{
long tv_sec; //seconds
long tv_usec; //and microseconds
};
client.c的代码没有改
server.c的代码如下
#include <stdio.h> #include <stdlib.h> #include <errno.h> #include <string.h> #include <netdb.h> #include <sys/types.h> #include <sys/socket.h> #include <sys/time.h> #include <sys/un.h> #include <sys/ioctl.h> #include <sys/wait.h> #include <sys/select.h> #include <netinet/in.h> #include <arpa/inet.h> #define SERVER_PORT 12138 #define BACKLOG 20 #define MAX_CON_NO 10 #define MAX_DATA_SIZE 4096 int MAX(int a,int b) { if(a>b) return a; return b; } int main(int argc,char *argv[]) { struct sockaddr_in serverSockaddr,clientSockaddr; char sendBuf[MAX_DATA_SIZE],recvBuf[MAX_DATA_SIZE]; int sendSize,recvSize; int sockfd,clientfd; fd_set servfd,recvfd;//用于select处理用的 int fd_A[BACKLOG+1];//保存客户端的socket描述符 int conn_amount;//用于计算客户端的个数 int max_servfd,max_recvfd; int on=1; socklen_t sinSize=0; char username[32]; int pid; int i; struct timeval timeout; if(argc != 2) { printf("usage: ./server [username]\n"); exit(1); } strcpy(username,argv[1]); printf("username:%s\n",username); /*establish a socket*/ if((sockfd = socket(AF_INET,SOCK_STREAM,0))==-1) { perror("fail to establish a socket"); exit(1); } printf("Success to establish a socket...\n"); /*init sockaddr_in*/ serverSockaddr.sin_family=AF_INET; serverSockaddr.sin_port=htons(SERVER_PORT); serverSockaddr.sin_addr.s_addr=htonl(INADDR_ANY); bzero(&(serverSockaddr.sin_zero),8); /* * SOL_SOCKET.SO_REUSEADDR 允许重用本地地址 * */ setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on)); /*bind socket*/ if(bind(sockfd,(struct sockaddr *)&serverSockaddr,sizeof(struct sockaddr))==-1) { perror("fail to bind"); exit(1); } printf("Success to bind the socket...\n"); /*listen on the socket*/ if(listen(sockfd,BACKLOG)==-1) { perror("fail to listen"); exit(1); } timeout.tv_sec=1;//1秒遍历一遍 timeout.tv_usec=0; sinSize=sizeof(clientSockaddr);//注意要写上,否则获取不了IP和端口 FD_ZERO(&servfd);//清空所有server的fd FD_ZERO(&recvfd);//清空所有client的fd FD_SET(sockfd,&servfd); conn_amount=0; max_servfd=sockfd;//记录最大的server端描述符 max_recvfd=0;//记录最大的client端的socket描述符 while(1) { FD_ZERO(&servfd);//清空所有server的fd FD_ZERO(&recvfd);//清空所有client的fd FD_SET(sockfd,&servfd); //timeout.tv_sec=30;//可以减少判断的次数 switch(select(max_servfd+1,&servfd,NULL,NULL,&timeout))//为什么要+1,是因为第一个参数是所有描述符中最大的描述符fd号加一,原因的话在APUE中有讲,因为内部是一个数组,第一个参数是要生成一个这样大小的数组 { case -1: perror("select error"); break; case 0: //在timeout时间内,如果没有一个描述符有数据,那么就会返回0 break; default: //返回准备就绪的描述符数目 if(FD_ISSET(sockfd,&servfd))//sockfd 有数据表示可以进行accept { /*accept a client's request*/ if((clientfd=accept(sockfd,(struct sockaddr *)&clientSockaddr, &sinSize))==-1) { perror("fail to accept"); exit(1); } printf("Success to accpet a connection request...\n"); printf(">>>>>> %s:%d join in!\n",inet_ntoa(clientSockaddr.sin_addr),ntohs(clientSockaddr.sin_port)); //每加入一个客户端都向fd_A写入 fd_A[conn_amount++]=clientfd; max_recvfd=MAX(max_recvfd,clientfd); } break; } //FD_COPY(recvfd,servfd); for(i=0;i<MAX_CON_NO;i++)//最大队列进行判断,优化的话,可以使用链表 { if(fd_A[i]!=0) { FD_SET(fd_A[i],&recvfd);//对所有还连着服务器的客户端都放到fd_set中用于下面select的判断 } } switch(select(max_recvfd+1,&recvfd,NULL,NULL,&timeout)) { case -1: //select error break; case 0: //timeout break; default: for(i=0;i<conn_amount;i++) { if(FD_ISSET(fd_A[i],&recvfd)) { /*receive datas from client*/ if((recvSize=recv(fd_A[i],recvBuf,MAX_DATA_SIZE,0))==-1) { //perror("fail to receive datas"); //表示该client是关闭的 printf("close\n"); FD_CLR(fd_A[i],&recvfd); fd_A[i]=0;//表示该描述符已经关闭 } else { printf("Client:%s\n",recvBuf); //可以判断recvBuf是否为bye来判断是否可以close memset(recvBuf,0,MAX_DATA_SIZE); } } } break; } } return 0; }
运行后的截图结果
![](http://images.cnitblog.com/i/485067/201407/262135407443011.jpg)
可以看出三个客户端都可以随时连接到服务器,并且发送数据给服务器。实现的效果跟上一节的多进程实现是一样的。毕竟没有大量客户端进行连接,所以就看不出效果,从书中和网上介绍说,这样可以提高某些方面的性能。
下一节将介绍服务器端向各个还在线的客户端进行发送数据,实现交互。然后再实现聊天室功能,大概的思路就是对接收到的数据进行转发。
参考资料: /article/6093974.html
本文出处: /article/6193120.html
相关文章推荐
- 基于Udp的Socket网络编程聊天程序
- Socket网络编程--聊天程序(9)
- java编程_socket_套接字_网络编程_简易的GUI多线程聊天程序
- Socket网络编程--聊天程序(1)
- 基于Udp的Socket网络编程聊天程序
- java 聊天程序 socket网络编程
- Socket网络编程--聊天程序(2)
- Socket网络编程--聊天程序(3)
- 网络编程与多线程的应用--基于socket udp编写一个简单聊天程序
- Socket网络编程--聊天程序(7)
- Socket网络编程--聊天程序(6)
- Delphi-网络编程-第一个网络方面作品(UDP聊天程序)
- 基于socket的Linux网络聊天程序--单线程非阻塞客户端
- c++ 网络编程 socket 聊天客户端/服务器
- java 网络编程【6】 用UDP编写网络聊天程序 以及 TCP编程实例
- iPhone之网络编程初体验-简单的聊天程序
- 基于socket的Linux上的网络聊天程序--多线程的服务器
- Java 网络编程之 (UDP网络聊天程序)
- socket编程-- 基于TCP协议的网络程序