近期学习了并发套接字编程,通过查找资料发现,并发套接字编程可以通过三种方式来实现:
1.多线程
2.多进程
3.多路复用
在上一篇博客中,我介绍了简单的socket模型,并且利用多进程实现了并发套接字编程。但是,我发现利用多进程实现的并发套接字编程需要很多的资源并且效率也不是特别的高,所以,下面来介绍一下通过多路复用实现的并发套接字,采取这种方式系统内核缓冲I/O数据,当某个I/O准备好后,系统通知应用程序该I/O可读或可写,这样应用程序可以马上完成相应的I/O操作,而不需要等待系统完成相应I/O操作,从而应用程序不必因等待I/O操作而阻塞。与多线程和多进程的方式相比较,这种方式最大的优势就是系统开销小,不必创建进程和线程并且维护这些进程和线程。
I/O复用通过调用select()或poll()函数,并在该函数上阻塞,等待数据报套接口可读;当select()返回可读条件时,调用recvfrom()将数据报拷贝到应用程序缓冲区中,select()函数允许进程指示内核等待多个事件中的任意一个发生,并仅在一个或多个事件发生或经过指定的时间时才唤醒进程。这个函数的形式如下:
#include<sys/select.h>
#include<sys/time.h>
intselect(intmaxfdp1,fd_set*readset,fd_set*writeset,fd_set*execepset,conststructtimeval*timeout);
返回值:
1.返回值表示所有描述字集中已准备好的描述字个数
2.如果定时器时间到,则返回0
3.若出错,则返回-1。例如:内存不足,文件标识符关闭等。
有关select()、poll()和epoll()函数的详细区别可以查看这篇博客:http://www.cnblogs.com/Anker/p/3265058.html
多路复用的socket模型如图:
此图来自:http://www.cnblogs.com/Anker/archive/2013/08/14/3258674.html
服务器端的代码如下
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<memory.h>
#include<errno.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<sys/select.h>
#include<sys/time.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#define DEFAULT_PORT 1234
#define MAXLINE 4096
#define CONNECTION_MAX 10
int main(int argc, char** argv)
{
int socket_fd, connect_fd;
struct sockaddr_in servaddr, clientaddr;
struct timeval timeout = { 3, 0 };
fd_set file_des_set;//无struct
char buff[4096];
int n, state, connection_mount = 0, fd_active[ CONNECTION_MAX ];
int clientaddr_size = sizeof( clientaddr );
memset( fd_active, 0, CONNECTION_MAX );
//初始化Socket
if( (socket_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1 ){
printf("create socket error: %s(errno: %d)\n",strerror(errno),errno);
exit(0);
}
//初始化
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);//IP地址设置成INADDR_ANY,让系统自动获取本机的IP地址。
servaddr.sin_port = htons(DEFAULT_PORT);//设置的端口为DEFAULT_PORT
//将本地地址绑定到所创建的套接字上
if( bind(socket_fd, (struct sockaddr*)&servaddr, sizeof(servaddr)) == -1){
printf("bind socket error: %s(errno: %d)\n",strerror(errno),errno);
exit(0);
}
//开始监听是否有客户端连接
if( listen(socket_fd, CONNECTION_MAX ) == -1){
printf("listen socket error: %s(errno: %d)\n",strerror(errno),errno);
exit(0);
}
printf("======waiting for client's request======\n");
int maxsock = socket_fd;
while(1){
//每一次循环都清空集合,否则不能够检查到文件标识符是否变化
FD_ZERO( &file_des_set );
//添加文件描述符
FD_SET( socket_fd, &file_des_set );
timeout.tv_sec = 30;
timeout.tv_usec = 0;
//向文件标识符集合中添加活跃的连接
int index;
for( index = 0; index < CONNECTION_MAX; index++ )
{
if( fd_active[ index] != 0)
FD_SET( fd_active[ index ], &file_des_set );
}
state = select( maxsock + 1, &file_des_set, NULL, NULL, &timeout );
if( state < 0 )
{
printf("select file descripter set is null! \n");
break;//break;
}
else if( state == 0 )
{
printf( "select timeout!\n" );
continue;
}
//检查文件字符集合中的文件标识符
for( index = 0; index < connection_mount; index++ )
{
if( FD_ISSET( fd_active[ index ], &file_des_set ) )
{
//接受客户端传过来的数据
if( ( n = recv( fd_active[ index ], buff, MAXLINE, 0 ) ) <= 0)
{ //文件描述符集合中的套接字没有收到客户端传送的数据,则关闭连接
printf("client %d closed!\n", index );
close( fd_active[ index ] );
FD_CLR( fd_active[ index ], &file_des_set );
fd_active[ index ] = 0;
connection_mount--;
}
else
{
if( n < MAXLINE)
buff[n] = '\0';
printf("recv msg from client[%d]: %s\n", index+1, buff);
//向客户端发送回应数据
sprintf( buff, "server have receive %d bytes!\n", n);
if( send( fd_active[ index ], buff, strlen( buff ), 0 ) == -1)
perror("send error");
}
}
}
//检查是否产生一个新的连接
if( FD_ISSET( socket_fd, &file_des_set ) )
{
//阻塞直到有客户端连接,不然多浪费CPU资源
if( ( connect_fd = accept( socket_fd, (struct sockaddr*)&clientaddr, &clientaddr_size ) ) == -1)
{
printf("accept socket error: %s(errno: %d)\n",strerror(errno),errno);
continue;
}
//将新建立的套接字添加到文件标识符集合中
if( connection_mount < CONNECTION_MAX )
{
fd_active[ connection_mount++ ] = connect_fd;
printf("new connection client[%d] %s:%d \n ", connection_mount, inet_ntoa( clientaddr.sin_addr), ntohs( clientaddr.sin_port ) );
if( connect_fd > maxsock )
maxsock = connect_fd;
}
else
{
printf("max connection arrive, exit \n" );
send( connect_fd, "bye", 4, 0 );
close( connect_fd );
break;
}
}
// for( index = 0; index < connection_mount; index++ )
// {
// if( fd_active[ index ] != 0 )
// close( fd_active[ index ] );
// }
}
close(socket_fd);
}
参考资料:
http://www.cnblogs.com/xiehy/archive/2010/11/01/1866402.html
http://www.cnblogs.com/dyllove98/archive/2013/06/11/3132186.html
http://blog.hehehehehe.cn/a/9146.htm