I/O多路复用之select:多用户聊天室学习与开发

前言:
开发windows多用户聊天室的前提:
1、了解网络编程的基本步骤
2、了解阻塞非阻塞,同步异步概念
3、了解select模型的原理与使用

实现功能:
1、新用户上线,将提醒所有在线用户;
2、用户下线,提醒在线用户
3、实现群聊,一个用户发送的消息要转发给所有用户
4、跨平台功能,能够同时在windows与linux下运行

select模型
select的功能原理:在一段指定时间内,监听用户感兴趣的文件描述符上的可读、可写和异常事件三个事件。poll和select应该被归类为这样的系统 调用,它们可以阻塞地同时探测一组支持非阻塞的IO设备,直至某一个设备触发了事件或者超过了指定的等待时间——也就是说它们的职责不是做IO,而是帮助 调用者寻找当前就绪的设备。
函数原型

/* Check the first NFDS descriptors each in READFDS (if not NULL) for read
   readiness, in WRITEFDS (if not NULL) for write readiness, and in EXCEPTFDS
   (if not NULL) for exceptional conditions.  If TIMEOUT is not NULL, time out
   after waiting the interval specified therein.  Returns the number of ready
   descriptors, or -1 for errors.

   This function is a cancellation point and therefore not marked with
   __THROW.  */
extern int select (int __nfds, fd_set *__restrict __readfds,
		   fd_set *__restrict __writefds,
		   fd_set *__restrict __exceptfds,
		   struct timeval *__restrict __timeout);

参数描述:
int nfds: 表示监听的套接字的数量,一般为select监听的所有文件描述符中最大值加1
fd_set *readfds //可读性检查(有数据可读入,连接关闭,重设,终止)
fd_set *writefds //可写性检查(有数据可发出)
fd_set *exceptfds //带外数据检查(带外数据)
struct timeval *timeout // 超时管理
返回:
返回准备就绪的套接字数量
-1 表示错误

fd_set集合:用select函数对套接字进行监视之前,必须要将套接字分配给一个fd_set集合,设置好读、写以及带外数据的fd_set结构。将一个套接字分配给任何一个集合后,再来调用select进行监视,便可知道一个套接字上是否正在发生上述的I/O活动。Winsock提供了下列宏操作,对fd_set进行处理和检查:
FD_ZERO(*set):初始化set
FD_SET(s, *set):将套接字s加入集合set
FD_CLR(s, *set):从set中删除套接字s。
FD_ISSET(s,*set):检查s是否还在集合set上,在调用select函数之前必须对此进行判断。

select 模型的工作步骤
(1) 使用FD_ZERO,初始化自己感兴趣的每一个fd_set。
(2) 使用FD_SET宏,将套接字句柄分配给自己感兴趣的每个fd_set。
(3) 调用select函数,等待I/O操作的完成。
(4) 根据select的返回值,我们便可判断出哪些套接字存在着尚未完成(待决)的I/O操作,.具体的方法是使用FD_ISSET宏,对每个fd_set集合进行检查。
(5) 将发生变化的IO操作(如新连接,断开连接,收到消息等)解决之后,返回第一步重新进行select操作
(6) select返回后,它会修改每个fd_set结构,删除那些不存在待决I/O操作的套接字句柄。这正是我们在上述的步骤( 4 )中,为何要使用FD_ISSET宏来判断一个特定的套接字是否仍在集合中的原因,这一步很重要。

完整程序代码如下:
客户端
client.h

#ifndef _CLENT_H_
#define _CLENT_H_

#ifdef _WIN32
#include <WS2tcpip.h>
#include "windows.h"
#pragma comment ( lib,"ws2_32.lib") 
#else
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#endif // _WIN32
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <iostream>

using namespace std;
#ifdef _WIN32
typedef DWORD ( WINAPI *ThreadFunc )(LPVOID lpThreadParameter);
#else
typedef void*(*ThreadFunc)(void* param);
#endif

#define MAX_SIZE 512

int init( struct sockaddr_in addr)
{
	int sock;
	int ret;
#ifdef _WIN32
	WSADATA ws;
	if(WSAStartup(MAKEWORD(2,2), &ws)!=0)
		return -1;
#endif
	sock=socket(AF_INET,SOCK_STREAM,0);
	ret=connect(sock,(struct sockaddr*)&addr,sizeof(addr));
	if(ret==-1)printf("connect erro\n");
	return sock;
}
int Chat(int sock)
{
	//启动两个线程进行读写
	fd_set refd;
	int Maxfd;
	char talk[20];
	int ret;

	Maxfd=sock;
	while(1)
	{
		FD_ZERO(&refd);
		FD_SET(0,&refd);
		FD_SET(sock,&refd);
		ret=select(Maxfd+1,&refd,0,0,0);
		if (ret==-1)break;
		if(FD_ISSET(0,&refd))
		{
			//scanf("%s",talk);
			memset(talk,0,20);
			fgets(talk,20,stdin);
			send(sock,talk,strlen(talk)+1,0);
		}
		else if(FD_ISSET(sock,&refd))
		{
			memset(talk,0,20);
			recv(sock,talk,20,0);
			printf("%s",talk);
		}
	}
	return 0;
}
DWORD WINAPI ForRead(void *arg)
{
	char talk[MAX_SIZE];
	int* sock;
	sock=(int*)arg;
	while(1)
	{
		memset(talk, 0, MAX_SIZE);
		printf("\nSend Msg:");
		fgets(talk, MAX_SIZE, stdin);
		send(*sock, talk, strlen(talk)+1, 0);
	}
}
DWORD WINAPI ForWrite(void *arg)
{
	char talk[MAX_SIZE];
	int *sock=(int *)arg;
	while(1)
	{
		memset(talk, 0, MAX_SIZE);
		recv(*sock, talk, MAX_SIZE, 0);
		cout<<endl<<talk<<endl;
		cout<<"Send Msg:";
	}
}

#endif

client.cpp

#include "client.h"

int main()
{
#ifdef _WIN32
	HANDLE hRead, hWrite;
#else
	pthread_t hRead,hWrite;
#endif // _WIN32
	
	int sock,ret;
	struct sockaddr_in client_addr;
	client_addr.sin_family  = AF_INET;
	client_addr.sin_port    = htons(8888);
	//client_addr1.sin_addr.s_addr = inet_addr("127.0.0,1");
	inet_pton(AF_INET, "127.0.0.1", &client_addr.sin_addr);

	sock=init(client_addr);
	if (sock >= 0)
	{
		printf("连接成功\n");
		printf("usage:(user1:user2:user3:...:hello)\n");
	}
	// Chat(sock);
#ifdef _WIN32
	hRead = CreateThread(NULL, 0, ForRead, &sock, CREATE_SUSPENDED, 0);

	hWrite = CreateThread(NULL, 0, ForWrite, &sock, CREATE_SUSPENDED, 0);
// 	ResumeThread(hRead);
// 	::WaitForSingleObject(hRead, -1);
	ResumeThread(hWrite);


// 	ResumeThread(hRead);
// 	::WaitForSingleObject(hRead, -1);

	while (true)
	{
		ForRead(&sock);
	}
#else
	pthread_create(&hRead,0,ForRead,&sock);

	pthread_create(&hWrite,0,ForWrite,&sock);
	pthread_join(hRead,0);
	pthread_join(hWrite,0);
#endif // _WIN32
	

	return 0;
}

服务端
server.h

#ifndef _SERVER_H_
#define _SERVER_H_

#ifdef _WIN32
#include <WS2tcpip.h>
#include "windows.h"
#pragma comment ( lib,"ws2_32.lib") 
#else
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#endif // _WIN32
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <iostream>
#include "map"

using namespace std;
#ifdef _WIN32
typedef DWORD ( WINAPI *ThreadFunc )(LPVOID lpThreadParameter);
#else
typedef void*(*ThreadFunc)(void* param);
#endif

#define MAX_SIZE 512
int init(struct sockaddr_in addr)
{
	int sock;
	int ret;// 记录错误马
#ifdef _WIN32
	WSADATA ws;
	if(WSAStartup(MAKEWORD(2,2), &ws)!=0)
		return -1;
#endif
	sock=socket(AF_INET,SOCK_STREAM,0);
	if(sock==-1)return -1;
	ret=bind(sock,(struct sockaddr*)&addr,sizeof(addr));
	if(ret==-1)return -1;
	listen(sock,10);
	return sock;
}
int Link(int sock)
{
	int ret;//返回值
	int Maxfd;//最大描述符号
	int acpsock;//accpet函数返回值
	struct sockaddr_in acpaddr;//accpet用到的地址
	socklen_t len;//地址大小
	char buf[MAX_SIZE];//接受到的消息内容
	int resock;//recv中用到的文件么描述符号、
	std::map<int, std::string> mapClient;//存储连接的文件描述符
	int i;//for循环变量
	fd_set refd,allfd;

	FD_ZERO(&refd);
	FD_ZERO(&allfd);
	Maxfd=sock;
	FD_SET(sock,&allfd);
	while(1)
	{
		refd=allfd;
		ret=select(Maxfd+1, &refd, 0, 0, 0);
		if(ret==-1)return -1;
		if(FD_ISSET(sock,&refd))
		{
			len=sizeof(acpaddr);
			acpsock=accept(sock,(struct sockaddr*) &acpaddr, &len);
			// 新有人上线,则让所有人知道:
			char szLoadInfo[MAX_SIZE] = {0};
			char szLoadedUser[MAX_SIZE] = {0};
			sprintf(szLoadInfo, "From system: %s load...\n", inet_ntoa(acpaddr.sin_addr));
			sprintf(szLoadedUser, "on load user:");
			printf(szLoadInfo);
			std::map<int, std::string>::iterator it = mapClient.begin();
			while(it != mapClient.end())
			{
				resock = it->first;
				send(resock, szLoadInfo, strlen(szLoadInfo), 0);
				strcat(szLoadedUser, it->second.c_str());
				it++;
			}
			send(acpsock, szLoadedUser, strlen(szLoadedUser), 0);
			mapClient[acpsock] = inet_ntoa(acpaddr.sin_addr);
			Maxfd=Maxfd>acpsock?Maxfd:acpsock;
			FD_SET(acpsock,&allfd);
		}

		std::map<int, std::string>::iterator it = mapClient.begin();
		while(it != mapClient.end())
		{
			resock = it->first;
			if(FD_ISSET(resock,&refd))
			{
				int nLen = 0;
				memset(buf, 0, sizeof(buf));
				// 客户端直接退出时为-1
				if((nLen =recv(resock, buf, MAX_SIZE, 0))<=0)
				{

					char szUnload[32] = {0};
					sprintf(szUnload, "%s exit\n", mapClient[resock].c_str());
					printf(szUnload);
					it = mapClient.erase(it);
					// 提醒所有人,某人下线
					for(std::map<int, std::string>::iterator it2 = mapClient.begin(); it2 != mapClient.end() ; it2++)
					{
						send(it2->first, szUnload, strlen(szUnload), 0);
					}
					closesocket(resock);
					FD_CLR(resock,&allfd);
				}
				else
				{
					char szMsg[MAX_SIZE] = {0};
					sprintf(szMsg, "From user:%s %s\n", mapClient[resock].c_str(), buf);
					printf("%s\n",szMsg);

					for(std::map<int, std::string>::iterator it2 = mapClient.begin(); it2 != mapClient.end() ; it2++)
					{
						if(it2->first != resock)
							send(it2->first, szMsg, strlen(szMsg), 0);
					}
					it++;
				}
			}
			else
				it++;
		}

	}

	return 0;
}

#endif

server.cpp

#include "server.h"
#define SERVER_PORT 8888
int main()
{
	int Sock;
	struct sockaddr_in servaddr;

	servaddr.sin_family      = AF_INET;
	//servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");//htonl(INADDR_ANY);
	servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
	servaddr.sin_port        = htons(SERVER_PORT); 

	Sock=init(servaddr);
	if(-1==Sock)
	{
		printf("init error\n");
		return -1;
	}
	printf("初始化成功:IP:127.0.0.1 PORT:8888\n");
	Link(Sock);   

	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值