这两天在学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<sys/types.h>
#include<sys/socket.h>
#include<stdio.h>
#include<iostream>
#include<netinet/in.h>
#include<unistd.h>
#include<errno.h>
#include<arpa/inet.h>
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来确定错误原因。
在创建了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<sys/socket.h>
#include<sys/epoll.h>
#include<sys/types.h>
#include<iostream>
#include<string>
#include<errno.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<stdio.h>
#include<string.h>
#include<fcntl.h>
#include<unistd.h>
#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;
}