c++ socket 多路复用IO之select

1.select()

select函数用于在非阻塞中,当一个套接字或一组套接字有信号时通知你,系统提供select函数来实现多路复用输入/输出模型

#include <sys/time.h> 
#include <unistd.h>   // 所在的头文件

int select(int maxfd,fd_set *rdset,fd_set *wrset,fd_set *exset,struct timeval *timeout);

1、maxfd:是需要监视的最大的文件描述符值+1

2、rdset:需要检测的可读文件描述符的集合

3、wrset:需要检测的可写文件描述符的集合

4、exset:需要检测的异常文件描述符的集合

5、timeout:指向timeval结构体的指针,通过传入的这个timeout参数来决定select()函数的三种执行方式

      1.传入的timeout为NULL,则表示将select()函数置为阻塞状态,直到我们所监视的文件描述符集合中某个文件描述符发生变化是,才会返回结果。

      2.传入的timeout为0秒0毫秒,则表示将select()函数置为非阻塞状态,不管文件描述符是否发生变化均立刻返回继续执行。

      3.传入的timeout为一个大于0的值,则表示这个值为select()函数的超时时间,在timeout时间内一直阻塞,超过时间即返回结果 。

2.fd_set类型四个宏操作:

1、FD_ZERO(fd_set *fdset) 将指定的文件描述符集清空,在对文件描述符集合进行设置前,必须对其进行初始化,如果不清空,由于在系统分配内存空间后,通常并不作清空处理,所以结果是不可知的。

2、FD_SET(fd_set *fdset) 用于在文件描述符集合中增加一个新的文件描述符。

3、FD_CLR(fd_set *fdset) 用于在文件描述符集合中删除一个文件描述符。

4、FD_ISSET(int fd,fd_set *fdset) 用于测试指定的文件描述符是否在该集合中

3.服务端

流程:

1、创建套接字,即调用socket(),根据需要选择参数类型

2、根据地址和端口号,绑定服务端,即调用bind()

3、将套接字设为监听套接字,并设置监听数,即调用listen()

4、开始检测监听套接字与客户端套接字信号,即调用select()

4、阻塞等待等待客户端连接请求,即调用accept()

5、接受发送消息,即调用recv(),send()函数

6、关闭套接字,即调用close()

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <iostream>
using namespace std;

class Server
{
	private:
		int port;	// 设置端口号
		int serverfd;	// 定义服务端套接字(这个最后作为了监听套接字)
		int clientfd;	// 定义客户端套接字
		int backlog;	// 设置最大客户端连接数,监听数
		struct sockaddr_in serveraddr;	// 定义服务端套接字地址结构体
		struct sockaddr_in clientaddr;	// 定义客户端套接字地址结构体
		struct timeval tv;		// 定义时间结构体
		socklen_t clientlen;	// 定义客户端套接字长度
	public:
		Server(int port = 10000):port(port),serverfd(-1),clientfd(-1),backlog(10) // 初始化相关参数
		{
			serverfd = socket(AF_INET, SOCK_STREAM, 0);		// 初始化服务端套接字对象
			//cout << serverfd << "\n";
			if (serverfd == -1)
			{
				throw("Create socket error ");
			}
			serveraddr.sin_family = AF_INET;	// 定义服务端地址为AF_INET协议族
			serveraddr.sin_port = htons(port);	// 定义服务端地址的端口号
			serveraddr.sin_addr.s_addr = htonl(INADDR_ANY); // 定义服务端地址ip
			int bind_val = bind(serverfd, (struct sockaddr*)&serveraddr, sizeof(serveraddr));	// 绑定服务端
			if (bind_val<0)
			{
			    throw("Bind error...");
			}
			listen(serverfd, backlog);	// 开始监听
		}
		void run()
		{
			fd_set readfds;   // 可读套接字集合
			backlog = 10;	// 初始化客户端最大连接数
			int maxfd;		// 定义可读套接字中最大数
			int arr[];		// 定义一个数据用于轮询各个客户端连接
			for(int k = 0; k<backlog;k++)
			{
			    arr[k] = -1;
			}
			arr[serverfd] = serverfd;   // 先将监听套接字加入数组中
			tv.tv_sec = 5;
			tv.tv_usec = 0;
			int i;
			while(1)
			{
			    FD_ZERO(&readfds);		// 将可读套接字集合清空
			    FD_SET(serverfd,&readfds);	// 重新将监听套接字加入可读套接字中,已保证新客户可以连接进来
			    for(i=0;i<backlog;i++)
			    {
				if (arr[i]>0)
				{
				    FD_SET(arr[i],&readfds);	// 将其余连接进来的客户端套接字加入可读套接字中,已保证接收数据
				}
				if (arr[i]>maxfd)
				{
				    maxfd = arr[i];  // 确定套接字中的最大数
				}
			    }
			    int select_val = select(maxfd+1, &readfds, NULL,NULL, &tv); 	// 开始监控套接字信号变化
			    if(select_val==-1)
			    {
				cout << "Select error..." << "\n";
				break;
			    }else if(select_val==0)
			    {
				cout << "No response..." << "\n";
				sleep(3);
				continue;
			    }else
			    {
				cout << "Receive response..." << "\n";
			    }
			    for(i=0;i<backlog;i++)
			    {
				if(arr[i]==serverfd)
				{
				    if(FD_ISSET(serverfd,&readfds))
				    {
				    	clientfd = accept(serverfd,(struct sockaddr*)&clientaddr,&clientlen); // 接受客户端的连接请求
				    	if(clientfd==-1)
				    	{
					    cout << "Accept client error..."<< "\n";
				    	}else if(clientfd==0)
					{    
					    cout << "";
				    	}else
					{
					    cout << "Get client " << clientfd << "\n";
					    FD_SET(clientfd,&readfds);	// 连接成功的客户端加入到集合中
					    arr[clientfd] = clientfd;	// 连接成功的客户端加入到数组中
					}
				    }
				}else if(arr[i]<0)
				{
				    cout << "";
				}else
				{
				    char data[100];
				    int recv_val = recv(arr[i],data,sizeof(data),0);	// 接受来自客户端的数据
				    if(recv_val==-1)
				    {
					cout << "Recv data error..." << "\n";
				    }else if (recv_val==0)
				    {
					cout << "connect close..." << arr[i] << "\n";
					arr[i] = -1;
				    }else
					cout << data << "\n";
				}
			    }
			}
		}
		~Server()
		{
			cout << "close server...." << "\n";
			if(clientfd == -1)
			{
				close(clientfd);	//关闭程序时,关闭套接字
			}
			if(serverfd == -1)
			{
				close(serverfd);	// 同上
			}
		}
};

int test()
{
	Server *s = NULL;		// 定义客户端指针
	try
	{
		s = new Server;		// 为该指针分配内存
		s->run();			// 运行函数run()
	}catch(const char* e)
	{
		cout << e << "\n";
	}
	delete s;		// 删除指针
}

int main()
{
	test();
}

4.客户端

流程:

1、创建套接字,即调用socket()

2、根据地址,端口号绑定地址,即调用bind()

3、发起连接服务端请求,即调用connect()

4、接收发消息,即recv(),send()

5、关闭套接字,close()

#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <cstring>
#include <cstdio>
#include <signal.h>

using namespace std;

class Client
{
        private:
                int port;
                int serverfd;
                struct sockaddr_in serveraddr;
        public:
                Client(int port = 10000):port(port),serverfd(-1)
                {
                        serverfd = socket(AF_INET, SOCK_STREAM, 0);
                        if(serverfd == -1)
                        {
                                throw("create socket error");
                        }
                        serveraddr.sin_family = AF_INET;
                        serveraddr.sin_port = htons(port);
                        inet_pton(AF_INET, "127.0.0.1", &serveraddr.sin_addr.s_addr);
                        int ret1;
                        ret1 = connect(serverfd, (struct sockaddr*)&serveraddr, sizeof(serveraddr));
                }
                void run()
                {
                        char* data;
                        int ret2;
                        data = "hello";
                        ret2 = send(serverfd, data, strlen(data), 0);
                }
                ~Client()
                {
                        cout << "close client..." << "\n";
                        if (serverfd == -1)
                        {
                                close(serverfd);
                        }
                }
};
int test()
{
        try
        {
                Client *c = NULL;
                c = new Client;
                c->run();
                delete c;
        }catch(const char* e)
        {
                cout << e << "\n";
        }
}

int main(int argc, char *argv[])
{
        test();
}

以上代码亲测有效,可以多个客户端同时连接,并收发消息 

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值