linux下socket编程和epoll的使用

  这两天在学Linux下的网络编程,于是便看了些关于socket和epoll的资料。
  首先介绍socket,socket编程我之前也接触过,不过是在windows下接触的。和windows不同的是,windows下关于socket编程,是直接给你一个socket的类,在上面建立自己的实例。而在linux中,你在建立socket时,它会给你一个文件描述符(其实就是一个整数),这个整数和内核为你建立的socket相联系,这个整数其实就代表着建立的socket(在网上查到的是说,linux下一切皆文件,socket其实也就是一种特殊的文件,而文件用文件描述符来标记)。接着就是将这个文件描述符(以下以sockfd代替)用bind函数与地址绑定(之后详细解释),如果是监听socket就开始listen,如果是连接socket就与server连接。其实感觉无论在windows上还是在linux上,socket都是这么使用的,下面讲解下在这个过程中使用的函数。
  首先是使用socket函数,原型如下:

int socket(int domain, int type, int protocol);

  函数返回一个整型值,就是所建立的socket的文件描述符。当返回值为-1是,说明建立socket失败。第一个参数domain指定,它用于确定所建立的socket的通信域,例如AF_INET就是ipv4,第二个参数type,指定建立的socket的类型,它定义了通信的语义,例如SOCK_STREAM提供顺序,可靠,双向,基于连接的字节流。最后一个参数protocol指定与套接字相匹配的协议,通常,只有单个协议存在以支持给定协议族内的特定套接字类型,在这种情况下协议可以被指定为0。但是可能存在多个协议和套接字匹配,此时就要手动指定协议。(三个参数的具体取值查询man)。
在建立了socket后,就是使用bind函数将其与地址绑定在一起。

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

  当绑定成功后,函数返回0,失败时返回-1,此时可查询errno来确定错误原因。此函数有三个参数,第一个参数就是你需要绑定的socket的文件描述符。第二个参数是需要绑定的地址,第三个参数是地址的长度(字节数)。对于第二个参数,对于ip协议,常使用sockaddr_in来代替,在绑定的时候强制转换成sockaddr,((struct sockaddr*)&addr,addr为一个sockaddr_in类型的参数)。

struct sockaddr_in
{
	sa_family_t    sin_family; /* address family: AF_INET */
    in_port_t      sin_port;   /* port in network byte order */
    struct in_addr sin_addr;   /* internet address */
};

/* Internet address. */
struct in_addr 
{
	uint32_t       s_addr;     /* address in network byte order */
};

  对于ip协议,sin_family永远被设为AF_INET,sin_port为端口号,sin_addr为具体的IP地址,可以将其设为INADDR_ANY来指定为任意ip地址(也可以近似于认为是本机地址),使用“127.0.0.1”来指定本机地址,或者自定义ip地址。使用inet_aton来将字符串形式的ip地址转换为标准的IP地址形式,int inet_aton(const char *cp, struct in_addr *inp),第一个参数是字符串形式ip地址,第二个参数是需要得到的地址,还有一些其他转换的方式,具体见man。最后,注意主机字节序和网络字节序的差别。
绑定成功后,便可以开始监听socket了,使用listen函数:

int listen(int sockfd, int backlog);

  和bind函数一样,若是函数成功,返回0,若是失败,返回-1。第一个参数是监听socket的文件描述符,第二个参数指定sockfd监听队列的最大长度,当sockfd的监听队列已满时,若还有新的连接,便会出现错误。
  对于客户端的连接socket,使用connect函数连接到服务器socket上。(貌似对于客户端socket不需要绑定地址,系统会自动为其指派地址和端口,这个具体还不太清楚,之后若是确定了会写出来)。

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

  函数的返回值规则和上述函数一样,0为成功,-1为失败。第一个参数为client的socket的文件描述符,后面两个参数则分别为所要连接到的server的ip地址和地址长度。地址使用和bind函数一样。
  这样,便完成了client和server的连接。
  连接成功后便可以使用send和recv函数来收发数据了。具体的例子如下(先只放出client的例子,server的代码之后和epoll代码一起放出。):
  client:

#include
#include
#include
#include
#include
#include
#include
#include

using namespace std;

int main(void)
{
	int ClientFd;
	sockaddr_in ClientAddr;

	ClientFd=socket(AF_INET,SOCK_STREAM,0);
	if(ClientFd==-1)
	{
		cout<<"Client socket created falied!!"<<errno<<endl;
		return 0;
	}
	
	int ServerFd;
	sockaddr_in ServerAddr;
	ServerAddr.sin_family=AF_INET;
	if(inet_aton("127.0.0.1",&ServerAddr.sin_addr)==0)
	{
		cout<<"server IPAddress error!!"<<endl;
		return 0;
	}

	string ipAddress=inet_ntoa(ServerAddr.sin_addr);
	cout<<ipAddress<<endl;
	ServerAddr.sin_port=htons(8000);
	socklen_t ServerLen=sizeof(ServerAddr);

	if(connect(ClientFd,(struct sockaddr*)&ServerAddr,ServerLen)==-1)
	{
		cout<<"can't connect to server!!"<<endl;
		cout<<errno<<endl;
		return 0;
	}
	
	const char *buffer="Hello, My Server!!";
	send(ClientFd,buffer,18,0);
	shutdown(ClientFd,SHUT_RDWR);
	if(close(ClientFd)==-1)
		cout<<"close Client failed"<<endl;
	return 0;
}

-------------------------------------------------------------------华丽的分界线---------------------------------------------------------------------------

	以上就是关于我关于socket的一些理解。下面介绍下epoll。
	在学习关于epoll之前,我曾经使用过完成端口,感觉和完成端口相比,epoll的使用就简单很多了,只需要三个函数,epoll_create,epoll_vtl,和epoll_wait.当然完成epoll的第一步就是先建立一个监听socket,将其作为第一个socket加进epoll的文件描述符中,之后每有一个客户端连接到这个sockfd时,就将这个客户端加进epoll中。首先介绍epoll_create函数:
int epoll_create(int size);

  在过去的版本中,size参数用来指定能在epoll中添加的sockfd的数量,但从Linux 2.6.8开始,size参数被忽略,但必须为一个大于0的数。此函数返回一个新的epoll的实例的文件描述符,此文件描述符用于之后对epoll的操作。当不再需要此文教描述符时,应该使用close函数关闭此文件描述符,当所有引用此epoll实例的文件描述符关闭时,系统内核会销毁此epoll实例以释放资源(这句话应该也说明了能够以不同的文件描述符调用一个epoll实例)。当函数返回-1时,说明函数失败,可以查看errno来确定错误原因。
v在创建了epoll实例,得到引用其的文件描述符之后,就可以调用epoll_ctl函数将已经建立好的监听socket加入epoll队列中了。

 int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

  epoll_ctl有四个参数,第一个参数epfd为epoll的文件描述符,也就是刚刚使用epoll_create建立的epoll文件描述符。第二个参数op(operation),它指定需要对目标文件描述符fd进行的动作,op参数有效值为以下三个:EPOLL_CTL_ADD,它将目标文件描述符fd注册到epoll的队列之中,并且使fd文件描述符所引用的文件和event(第四个参数)相关联;EPOLL_CTL_MOD,用来改变目标文件描述符fd相关联的事件event;EPOLL_CTL_DEL,用来将目标文件描述符fd,从epoll队列中移除,在这个操作下,event参数被忽略,可以被设置为NULL。第三个参数fd就是需要被操作的目标文件描述符fd。第四个参数event描述和fd连接到一起的操作(请原谅我的语文水平,不过看到对参数值的讲解时应该都能够理解event的含义)。epoll_event的结构如下:

		   typedef union epoll_data {
               void        *ptr;
               int          fd;
               uint32_t     u32;
               uint64_t     u64;
           } epoll_data_t;

           struct epoll_event {
               uint32_t     events;      /* Epoll events */
               epoll_data_t data;        /* User data variable */
           };

  其中events变量是一组位掩码,可以使用|来同时选中几个不同的events参数。可选择的参数如下:EPOLLIN,相关文件可用于read操作;EPOLLOUT,相关文件可用于write操作;对于stream socket来说,可以检测出对面客户端关闭(close)或者半关闭连接(shutdown)(但我在代码中测试过,没有能够成功检测出来,可能是我的代码写错了,但是可以用recv来检测对面是否关闭连接,如果recv的返回值为0的话说明对面关闭了连接,这点我测试过);还有一些其他的events参数,如EPOLLPRI,EPOLLERR等,我也没有进行测试,可以查询man来了解具体含义。
  在将监听socket注册到epoll中后,便可以调用epoll_wait来开始进行epoll的监听工作。

int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);

  epoll_wait函数等待发生在epoll队列中的文件描述符的事件,如果没有事件发生,它会阻塞住程序。第一个参数epfd还是epoll实例的文件描述符。第二个参数events指向记录发生的事件的内存区域,可用events[i]来调用发生的事件。第三个参数maxevents指定epoll所能返回的最大的事件数量,此参数必须大于0,第四个参数timeout指定epoll_wait阻塞住程序的超时时间(epoll_wait将会阻塞住程序直到以下三种情况之一发生:epoll队列中的一个文件描述符上发生了事件,epoll-wait被信号中断,超时时间到)。当timeout为-1时会导致如果没有事件到达,程序将会被无限期阻塞住,而如果timeout为0,epoll-wait将会立即返回,即使任何事件都没有发生。epoll-wait函数返回发生的事件数目,如果为0,说明超时,没有时间发生,如果返回-1,说明函数错误,可查询errno来确定错误原因。
  在调用完epoll_wait后,如果有客户端连接到监听socket,便可以接收到,接收到后便新建一个sockfd来专门和这个客户端进行通信,再调用epoll_ctl函数将这个sockfd注册到epoll中,如此循环便可。程序如下:
epoll.h:

#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include

#define MAX_SIZE 500
#define BUFF_SIZE 1000
#define MAX_EVENTS 100

using namespace std;
class myEpollServer
{
public:
	myEpollServer(){};
	~myEpollServer(){};
	int setnonblocking(int socketFd);

	void start();

	bool initializeEpoll(int maxSize);
	bool initializeServerSocket();
private:
	//listen socket info
	int ServerFd=0;
	sockaddr_in ServerAddr;
	int ServerPort;	
};

  epoll.cpp

#include"MyEpollPort.h"

bool myEpollServer::initializeServerSocket()
{
	using namespace std;
				
	if((ServerFd=socket(AF_INET,SOCK_STREAM,0))==-1)
	{
										   
		cout<<"create server socket fail!!,error:"<<strerror(errno)<<endl;
		return false;
	}   
    
	memset(&ServerAddr,0,sizeof(ServerAddr));
	ServerAddr.sin_family=AF_INET;
	ServerAddr.sin_addr.s_addr=htons(INADDR_ANY);
	ServerAddr.sin_port=htons(8000);
									
	if(bind(ServerFd,(struct sockaddr*)&ServerAddr,sizeof(ServerAddr))==-1)
	{
		cout<<"bind server addr fail,error:"<<strerror(errno)<<endl;
		return false;
	}   
										
	if(listen(ServerFd,10)==-1)
	{
		cout<<"server listen fail,error:"<<strerror(errno)<<endl;
		return false;
	}  
	return true;
}

bool myEpollServer::initializeEpoll(int maxSize)
{
	struct epoll_event ev,events[100];
	int connectFd,nfds,epollFd;

	epollFd=epoll_create(maxSize);
	if(epollFd==-1)
	{
		perror("epoll_create");
		return false;
	}
	ev.events=EPOLLIN|EPOLLRDHUP;
	ev.data.fd=ServerFd;
	if(epoll_ctl(epollFd,EPOLL_CTL_ADD,ServerFd,&ev)==-1)
	{
		perror("epoll_ctl:ServerFd");
		return false;
	}

	for(;;)
	{
		nfds=epoll_wait(epollFd,events,MAX_EVENTS,-1);
		if(nfds==-1)
		{
			perror("epoll_wait");
			close(ServerFd);
			return false;
		}

		for(int i=0;i<nfds;++i)
		{
			cout<<i<<endl;
			if(events[i].data.fd==ServerFd)
			{
				cout<<1<<endl;
				sockaddr_in clientAddr;
				socklen_t len;
				connectFd=accept(ServerFd,(struct sockaddr*)&clientAddr,&len);
				if(connectFd==-1)
				{
					perror("accept");
					close(ServerFd);
					return false;
				}
				cout<<"client addr is: "<<inet_ntoa(clientAddr.sin_addr)<<endl;
				setnonblocking(connectFd);
				ev.events=EPOLLIN|EPOLLET;
				ev.data.fd=connectFd;
				if(epoll_ctl(epollFd,EPOLL_CTL_ADD,connectFd,&ev)==-1)
				{
					perror("epoll_ctl:connectFd");
					close(ServerFd);
					return false;
				}
			}
			else
			{
				cout<<2<<endl;
				if(events[i].events&EPOLLRDHUP||events[i].events&EPOLLERR)
				{
					sockaddr_in addr;
					socklen_t len=sizeof(addr);
					if(getpeername(events[i].data.fd,(struct sockaddr*)&addr,&len)==-1)
					{
						cout<<"get client address fail!1"<<endl;
					}
					cout<<"client:"<<inet_ntoa(addr.sin_addr)<<"out of link!!"<<endl;

				}
				char buff[BUFF_SIZE]; 
				int n=recv(events[i].data.fd,buff,1000,0);
				if(n==0)
				{
					sockaddr_in addr;
					socklen_t len=sizeof(addr);
					if(getpeername(events[i].data.fd,(struct sockaddr*)&addr,&len)==-1)
					{
						cout<<"get client address fail!1"<<endl;
					}
					cout<<"client:"<<inet_ntoa(addr.sin_addr)<<"out of link!!"<<endl;


				}
				buff[n]='\0';
				cout<<buff<<endl;
			}
		}
	}

	return true;

}


void myEpollServer::start()
{
	initializeServerSocket();
	initializeEpoll(100);
}

int myEpollServer::setnonblocking(int sockFd)
{
	if(fcntl(sockFd,F_SETFL,fcntl(sockFd,F_GETFD,0)|O_NONBLOCK)==-1)
	{
		return -1;
	}
	return 0;
}

你可能感兴趣的:(linux,c++)