linux下socket編程IO多路複用select模型

今天學習了一下linux環境的socket編程。先學習一下簡單的select模型。

一、select模型

是一種IO多路複用模型。一般網絡程序,使用accept、recv等接口。則服務端,在調用accept時,如果此時沒有客戶端連接時,則服務端需要等待。recv也是一樣。這種情況下,則服務端只能阻塞等待。select模塊,則是使用select函數去查詢你使用到的socket的狀態。比如,是否有客戶端連接,是否有客戶端發送數據。這樣可以,你可以針對有數據的socket進行操作。避免盲目等待。

二、接口介紹

2.1select函數

int select(int maxfdp,fd_set *readfds,fd_set *writefds,fd_set *errorfds,struct timeval *timeout);
最主要的一個函數,就是select函數。該函數有5個參數。

maxfdp:是一個整數值,是指集合中所有文件描述符(socket)的範圍,即所有文件描述符的最大值加1。

readfds,writefds,errorfds都是fd_set類型的指針,是個socket的數組指針。

readfds:進行可讀操作查詢的文件描述符的集合,不關心讀操作,則可傳入NULL

writefds:進行可寫操作查詢的文件描述符的集合,不關心寫操作,則可傳入NULL。

errorfds:進行文件異常錯誤的查詢的文件描述符,也可以傳入NULL

timeout:第一,若將NULL以形參傳入,即不傳入時間結構,就是將select置於阻塞狀態,一定等到監視文件描述符集合中某個文件描述符發生變化爲止;第二,若將時間值設爲0秒0毫秒,就變成一個純粹的非阻塞函數,不管文件描述符是否有變化,都立刻返回繼續執行;第三,timeout的值大於0,這就是等待的超時時間,即select在timeout時間內阻塞,超時時間之內有事件到來就返回了,否則在超時後不管怎樣一定返回

返回值:如果有錯誤,則返回值爲-1,如果超時返回,則返回值爲0,如果,文件描述符裏面有變化,則返回值大於0

2.2 fd_set結構體操作 

清空fd_set結構體

FD_ZERO(fd_set*);

向fd_set結構體添加一個文件描述符

FD_SET(int ,fd_set*);
判斷socket是否在某個集合內

FD_ISSET(fd, fdsetp)

三、示例代碼

1.服務端, 啓動一個端口,進行偵聽。循環查詢客戶端是否有數據發過來。如果有則讀取客戶端數據。

selectService.h文件代碼

#ifndef SELECT_SERVICE_H_
#define SELECT_SERVICE_H_
#include <list>
using namespace std;

class SelectService{
public:
	explicit SelectService(unsigned short port)
	: port_(port), listenSocket(0){}
	bool beginRun();
private:
    void runImpl();
    void acceptOpe();

private:
	list<int> socketList;
	unsigned short port_;
	int listenSocket;
};

#endif

selectService.cpp文件代碼

#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include "selectService.h"
#include <iostream>
#include <memory.h>
#include <netinet/in.h>
#include <string.h>
#include <fcntl.h> 
#include <errno.h>
using namespace std;

const size_t MAX_BUF_LEN = 1024;
const int MaxListenNum = 5;
int beginListen(unsigned short port){
    struct  sockaddr_in s_add,c_add;
    int sfp = socket(AF_INET, SOCK_STREAM, 0);
    if (-1 == sfp){
        cout << "socket failed.error:" << strerror(errno) << endl;
        return -1;
    }
    bzero(&s_add,sizeof(struct sockaddr_in));
    s_add.sin_family=AF_INET;
    s_add.sin_addr.s_addr=htonl(INADDR_ANY);
    s_add.sin_port=htons(port);

    if(-1 == bind(sfp,(struct sockaddr *)(&s_add), sizeof(struct sockaddr)))
    {
        cout << "bind failed. error:" << strerror(errno) << endl;
        return -1;
    }
    if(-1 == listen(sfp, MaxListenNum))
    {
        cout << "listen failed. error:" << strerror(errno) << endl;
        return -1;
    }

    return sfp; 
}

void SelectService::runImpl()
{
    fd_set  rdfds;
    struct timeval tv;
    tv.tv_sec = 2;
    tv.tv_usec = 500;
    int socketSize = listenSocket;
    FD_ZERO(&rdfds);

    FD_SET(listenSocket, &rdfds);

    for (list<int>::iterator iter=socketList.begin(); iter!=socketList.end(); ++iter)
    {
        FD_SET(*iter, &rdfds);
        socketSize = *iter> socketSize ? *iter : socketSize;
    }

    int ret = select(socketSize+1, &rdfds, NULL, NULL, &tv);
    if (ret < 0)
    {
        cout << "select error" << endl;
    } else if(ret == 0 ){
        cout << "select time out. socket size:" << socketSize << endl;
    } else {
        //查看是否有客戶端連接
        if (FD_ISSET(listenSocket, &rdfds)){
            //創建與客戶端的連接,加入到list裏
            acceptOpe();
        }

        //判斷客戶端是否發請求過來
        char buf[MAX_BUF_LEN] = {0};
        for (list<int>::iterator iter=socketList.begin(); iter!=socketList.end(); )
        {
            memset(buf, 0, MAX_BUF_LEN);
            //判斷socket是否可讀
            if (FD_ISSET(*iter, &rdfds)){
                ret = recv(*iter, buf, MAX_BUF_LEN, 0);
                if (ret > 0)
                {
                    cout << socketSize << ".socket:" << *iter << ". data:" << buf << endl;

                    strcpy(buf, "hello, this is select server.");
                    send(*iter, buf, strlen(buf), 0);
                } else{
                    //客戶端斷開,關閉socket
                    close(*iter);
                    iter = socketList.erase(iter);
                }
            } else {
                ++iter;
            }
        }
    }
}

void SelectService::acceptOpe()
{
    ///客戶端套接字
    struct sockaddr client_addr;
    socklen_t length = sizeof(client_addr);
    ///成功返回非負描述字,出錯返回-1
    int clientConn = accept(listenSocket, (struct sockaddr*)&client_addr, &length);
    if (clientConn < 0)
    {
        cout << "socket error:" << endl;
        return ;
    }

    socketList.push_back(clientConn);
    cout << "client is connect." << clientConn << endl;
}

bool SelectService::beginRun()
{
    //創建一個socket,用於listen
    listenSocket=beginListen(port_);
    while (true){
        runImpl();
    }

    return true;
}
main.cpp文件代碼
#include "selectService.h"

int main(){
        SelectService selectService(10241);
        selectService.beginRun();
        
        return 0;
}

2.客戶端代碼

用python寫了一個客戶端

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import socket, struct
import sys
import traceback
import json
import time

reload(sys)
sys.setdefaultencoding('utf-8')

ip = '192.168.86.39'
port = 10241

def init():   
    client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    client_socket.settimeout(2) # set time out
    client_socket.connect((ip, port))
    return client_socket

def cleanup(client_socket):
    if client_socket:
        try:
            client_socket.close()
        except:
            print "catch an exception %s" % (traceback.format_exc())

if __name__ == '__main__':    
    req_str = "this is test."
    
    client_socket = init()
    for j in range(1000):
        try:
            ret_val = ""
            client_socket.send('%s %d' % (req_str, j))
            buf_ret = client_socket.recv(1024)
            print buf_ret
            time.sleep(1)
            
        except socket.timeout, e:
            print "socket timeout error: %s" %(traceback.format_exc())
        except Exception, e:
            print "socket send and recieve Error: %s" %(traceback.format_exc())
        else:
            pass








發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章