Linux下socket(select,epoll)

来源:互联网 发布:网络综艺节目优势 编辑:程序博客网 时间:2024/06/04 04:41

1. Linuxsocket的简介

在linux支持select模式,poll模式,在内核2.6版本以后支持epoll模式;

epoll模式的优点:
A:支持进程打开的最大socket数据
B:IO效率不随FD数目增加而线性下降
C:使用mmap加速内核与用户空间的消息传递。
D:内核微调


2. socket的属性(例如:设置为非阻塞模式)

   1: /*设置socket为非阻塞模式
   2:  * window: ioctlsocket()
   3:  * linux: fcntl(), (头文件fcnt.h)
   4:  */
   5: int set_nonblock(int fd)
   6: {
   7:     int opts;
   8:     opts=fcntl(fd, F_GETFL); // 获得socket的属性
   9:     if (opts < 0)
  10:         return -1;
  11:  
  12:     opts = opts | O_NONBLOCK;
  13:     if(fcntl(fd, F_SETFL, opts) < 0) //设置socket的属性
  14:         return -1;
  15:  
  16:     return 0;
  17: }

 

3.socket的缓冲区的扩充

int nRecvBufferLen=2*1024*1024; //设置为2M的缓冲区
setsockopt(s,SOL_SOCKET,SO_RCVBUF, (const char*)&nRecvBufferLen, sizeof(nRecvBufferLen));

 

4 sockaddr 和 sockaddr_in 的关系和对比

   1: //sockaddr结构体:缺陷是sa_data把目标地址和端口信息混在一起了
   2: struct sockaddr {
   3:     unsigned short sa_family; //通信类型: AF_INET 或 UNIX
   4:     char sa_data[14]; // 14字节,包含套接字中的目标地址和端口信息 
   5: };
   6:   
   7: //sockaddr_in 结构体:解决了sockaddr的缺陷,把port和addr 分开储存在两个变量中
   8: struct sockaddr_in {
   9:    short int sin_family;
  10:    unsigned short int sin_port; //端口(网络字节顺序)
  11:     struct in_addr  sin_addr;  //IP地址(网络字节顺序)
  12:      unsigned char sin_zero[8];
  13: } 
  14:  
  15: //注ip地址的结构
  16: struct in_addr { unsigned long s_addr; }
 

sockaddr 和 sockaddr_in的相互关系:
1.一般先把sockaddr_in变量赋值后,强制类型转换后传入用sockaddr做参数的函数
2.sockaddr_in用于socket定义和赋值
3.sockaddr用于函数参数

NBO网络字节顺序 和 HBO本机字节顺序 的转换
inet_addr()    将字符串转换为NBO网络地址
inet_ntoa ()   将NBO地址转化成字符串点数格式
htons()        将short类型转换为网络类型的端口号
ntohs()        将NBO类型的端口转换为short类型的变量

   1: //代码例子
   2: struct sockaddr_in cliaddr;
   3: bzero(&cliaddr,sizeof(cliaddr)); //网络地址的初始化
   4: ina.sin_family=AF_INET;  //INET通信类型
   5: ina.sin_port = htons(23);  //指定端口,将host的变量转换为NBO的端口号
   6: ina.sin_addr.s_addr = inet_addr("132.241.5.10"); //将字符串转换为NBO地址
   7:  
   8: char* szIp = inet_ntoa(ina.sin_addr);//inet_ntoa  将NBO地址转化成字符串点数格式
   9: short port = ntohs(ina.sin_port);//ntohs 将NBO端口转换为short变量

 

5.socket的创建和设置

socket创建和设置,及其设置(tcp、udp流程类似)

   1: //建立一个侦听socket
   2: int create_listener(int port)
   3: {
   4:     int fd;
   5:  
   6:     //创建一个inet类型的socket
   7:     if ((fd = socket(AF_INET, SOCK_STREAM, 0)) <= 0)
   8:         return -1;
   9:  
  10:  
  11:     int opt=1;
  12:     
  13:     /* SO_REUSEADDR,只定义一个套接字在一个端口上进行监听,
  14:      * 如果服务器出现意外而导致没有将这个端口释放,
  15:      * 那么服务器重新启动后,你还可以用这个端口,因为你已经规定可以重用了,
  16:      如果你没定义的话,你就会得到提示,ADDR已在使用中 
  17:      */
  18:     setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (const void*)&opt, sizeof(opt));
  19:     set_nonblock(fd);
  20:  
  21:  
  22:     /* sockaddr结构体的缺陷:sa_data把目标地址和端口信息混在一起了
  23:      * sockaddr_in 结构体: 把port和addr 分开储存在两个变量中
  24:      * sockaddr 和 sockaddr_in的相互关系
  25:      */
  26:     struct sockaddr_in sin;
  27:     memset(&sin, 0, sizeof(struct sockaddr_in));
  28:     sin.sin_family = AF_INET;
  29:     sin.sin_port = htons(port);       // 指定port端口
  30:     sin.sin_addr.s_addr = INADDR_ANY; // ip地址不做绑定使用机器默认的ip地址
  31:     if (bind(fd, (struct sockaddr *)&sin, sizeof(sin)) != 0)
  32:     {
  33:         close(fd);
  34:         return -1;
  35:     }
  36:  
  37:     
  38:  
  39:     // 开始侦听该端口
  40:     if (listen(fd, 32) != 0)
  41:     {
  42:         close(fd);
  43:         return -1;
  44:     }
  45:  
  46:     return fd;
  47: }

 

6. socket的接收数据

socket接收数据要注意:
1. 是否是阻塞模式;如果是阻塞模式,没有读到数据会持续等待;非阻塞则返回;如果要根据错误代码和接收模式及其收到数据的个数,来判断是客户端已经断开?网络错误?
2. udp如果有数据则就是一整包数据,而tcp是流会出现粘包和半包数据的情况

   1: //socket数据接收,可以Window可以linux
   2: while(rs)
   3: {
   4:     //接收数据,默认接收sizeof(buf)大小,实际接收buflen大小
   5:     buflen = recv(sock_fd, buf, sizeof(buf), 0);
   6:     if(buflen < 0)
   7:     {
   8:         // 由于是非阻塞的模式,所以当errno为EAGAIN时,表示当前缓冲区已无数据可读;在这里表示该事件已做处理
   9:         if(errno == EAGAIN)
  10:             break;
  11:         else
  12:             return;//表示有错误发生
  13:     }
  14:     else if(buflen == 0)
  15:     {
  16:         // 这里表示对端的socket已正常关闭.
  17:     }
  18:  
  19:  
  20:     if(buflen == sizeof(buf)
  21:         rs = 1;   // 需要再次读取
  22:     else
  23:         rs = 0;
  24: }

 

7. socket的发送数据

◇.send函数最后一个参数
window: 一般是0
linux: 最好设置为MSG_NOSIGNAL;表示出错后不向系统发信号,否则程序会退出!

   1: /* socket发送数据,可以在Window下,可以在linux下
   2:  * 
   3:  * 该代码是采用阻塞模式下,完整的发送一个缓冲中的数据
   4:  * 如果缓冲区满,则阻塞线程等待缓冲区有空间再发送
   5:  * 如果网络错误,或socket错误,会立即返回-1
   6:  */
   7: int socket_send(int sockfd, const char* buffer, size_t buflen)
   8: {
   9:     int tmp;
  10:     size_t total = buflen;
  11:     const char *p = buffer;
  12:  
  13:     while(1)
  14:     {
  15:         /* window: 一般是0
  16:          * linux: 最好设置为MSG_NOSIGNAL;表示出错后不向系统发信号,否则程序会退出!
  17:          */
  18:         tmp = send(sockfd, p, total, 0);
  19:         if(tmp < 0)
  20:         {
  21:             //当进程收到信号会中断正在进行的系统调用、区处理信号,处理完系统返回-1且errno==Eintr;
  22:             //所以可continue继续执行
  23:             if(errno == EINTR)
  24:                 continue;
  25:  
  26:  
  27:             // Eagain表示写缓冲队列已满, usleep后继续发送
  28:             if(errno == EAGAIN)
  29:             {
  30:                 usleep(1000);
  31:                 continue;
  32:             }
  33:  
  34:  
  35:             return -1;
  36:         }
  37:  
  38:  
  39:         if((size_t)tmp == total)
  40:             return buflen;
  41:  
  42:  
  43:         total -= tmp;
  44:         p += tmp;
  45:     }
  46:  
  47:  
  48:     return tmp;
  49: }

 

8. socket的select模式




9. socket的epoll模式

int epoll_create(int size);
int epoll_ctl(int epfd, int op, int fd, epoll_event *event);
        op:Add,Mod,Del
        event {events,epoll_data_t data}
        events:epollIN/out/pri/err/hup/et
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);

ET:高速模式,支持无阻塞;当fd从未就绪变成就绪epoll将通知你,然后它会假设你已经知道了该通知且不会再次发出通知、知道你做了操作将fd变为未就绪状态;所以如果你一直对某个fd没有操作,那么内核将一直不发出该fd的通知!
LT:同ET相反,它会一直发出fd的通知!
ET模式:在epollIN事件时,recv返回的大小等于请求大小,那么很有可能缓冲区还有数据未读完,也意味着该次事件没有处理完,还需要再次读取; 参考数据完整接收
ET模式:由于epoll是无阻塞那么send虽然返回但是数据并没有真正发出去,当缓冲区满后会产生Eagain错误,同时不会理此次待发送的数据,因此封装一个socket_send函数来处理该问题,如果返回eagain就阻塞在该函数中[EAGAIN 另外一个名字是 EWOULDBLOCK]  参考数据的完整发送

   1: /* 这是一个简单的epoll模式的程序,
   2:  *
   3:  */
   4: int main()
   5: {
   6:     //1. 创建epoll描述符, 可接受5000个连接
   7:     epoll_fd = epoll_create(5000);
   8:  
   9:     //2. 创建、绑定和侦听socket; 并且将它放到epoll上面开始侦听数据到达和connect连接
  10:     listen_fd = create_listener(port);
  11:   epoll_event ev;
  12:     ev.data.fd = cfd;
  13:     ev.events = EPOLLIN | EPOLLET;
  14:     epoll_ctl(epoll_fd, EPOLL_CTL_ADD, cfd, &ev);//检查cfd上面的数据到达
  15:  
  16:  
  17:     //3.创建线程、并脱离创建者;在线程中检索epoll上面的触发的socket
  18:     pthread_attr_init(&attr);
  19:     pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);
  20:     pthread_create(&tid, &attr, epoll_function, NULL);
  21: }
  22:  
  23:  
  24: // 专门进行侦听epoll的线程函数
  25: void *epoll_function(void *p) 
  26: {
  27:     while ( 1)
  28:     {
  29:         // 等待epoll上面的事件触发,然后将他们的fd检索出来
  30:         struct epoll_event events[EPOLL_EVENT_MAX];
  31:         int evn_cnt = epoll_wait(epfd, events, EVENTSIZE , -1);
  32:  
  33:         // 循环处理检索出来的events队列中的每个fd
  34:         for (int i=0; i<wait_cnt; i++)
  35:         {
  36:             if (events[i].data.fd == listen_fd)   // 有客户端connect过来
  37:             {
  38:                 struct sockaddr_in caddr;
  39:                 socklen_t clen = sizeof(caddr);
  40:  
  41:                 // 接收连接
  42:                 int connfd = accept(listen_, (sockaddr *)&caddr, &clen);
  43:                 if (connfd < 0)
  44:                     continue;
  45:  
  46:                 // 将新接收的连接的socket放到epoll上面
  47:                 struct epoll_event ev;
  48:                 ev.data.fd = connfd;
  49:                 ev.events = EPOLLIN|EPOLLET;
  50:                 epoll_ctl(epoll_, EPOLL_CTL_ADD, connfd, &ev);
  51:             }
  52:             else if (events[i].events & EPOLLIN)       // 有数据到达
  53:             {
  54:                 //接收数据
  55:                 recv(event[i].data.fd, buffer, sizeof(buffer), 0);
  56:             }
  57:         }
  58:     }
  59: }



10.Window和linux的socket的不同之处:

.头文件
Window:winsock.h或winsock2.h
linux: netinet/in.h(大部分都在这),unistd.h(close函数)

.初始化
window: 需要WSAStartup启动Ws2_32.lib;#program comment(lib, "Ws2_32")加载lib库
linux: 无需

.关闭socket
window: closesocket()
linux: close()

.socket的类型
window: SOCKET
linux: int

.错误代码的获取
window: getlasterror()/WSAGetLastError()
linux: 为成功返回-1;错误代码在errno中(头文件errno.h)

.设置非阻塞
window: ioctlsocket()
linux: fcntl(), (头文件fcnt.h)

.send函数最后一个参数
window: 一般是0
linux: 最好设置为MSG_NOSIGNAL;表示出错后不向系统发信号,否则程序会退出!

◇.多线程
window:包含process.h,用_beginthread/_endthread
linux:包含pthread.h,用pthread_create/pthread_exit

◇.使用IP地址
window:addr_var.sin_addr.S_un.S_addr
linux:addr_var.sin_addr.s_addr

11.创建线程

我们先看一个简单的创建线程的代码
   1: // 创建脱离创建者的线程
   2: pthread_t tid;
   3: pthread_attr_t attr;
   4: pthread_attr_init(&attr);
   5:  
   6: // 创建线程PTHREAD_CREATE_DETACHED(分离线程),PTHREAD _CREATE_JOINABLE(非分离线程)
   7: // 分离线程是不能join的,非分离线程可以join
   8: // 一般来说服务器大多采用分离线程
   9: pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
  10:  
  11: //创建线程;指定线程属性、指定线程函数
  12: if (pthread_create(&tid, &attr, serv_epoll, NULL) != 0)
  13: {
  14:     fprintf(stderr, "pthread_create failed\n");
  15:     return -1;
  16: }



//下面是一个小的线程
http://blog.chinaunix.net/u1/33885/showart_265060.html


http://blog.163.com/miky_sun/blog/static/3369405200992593735641/

原创粉丝点击