Linux操作系统select非阻塞操作socket分析

socket在很多应用常见下,单进程单线程,调用socket(),connect(),read(),write()等一套,对单个socket服务。

但在多socket场景下,这样的方法行不通了。server会在accept()时阻塞,等待client连入,那么到底是监听端口8080先accept(),还是监听端口8086先accept()。

这就需要引入非阻塞式轮询各个socket的状态,当他们连入状态改变时,在进入accept()。这样就可以在单个进程中,为多个socket服务。代码框架可以是

sock_A = socket(port_A);
sock_B = socket(port_B);
while(1){
    if(is_ready){
        accept(sock_A);
    }
    if(is_ready){
        accept(sock_B);
    }
}

 Linux C中一切皆是文件,设置文件初始状态。当状态改变时,某些比特自动置位,就可以读取这些比特位,得知是否有事件触发了文件状态改变。这样的机制就是select,它的函数原型为

int select(int n,fd_set * readfds,fd_set * writefds,fd_set * exceptfds,struct timeval * timeout);

select()函数用来等待文件描述符状态的改变(可设定为阻塞与非阻塞)。n代表需要等待的文件描述符+1;参数readfds、writefds和exceptfds为描述符字,用来回传该描述词的读,写或异常的状况。

在使用select前,需要使用相关宏设定文件描述符等待的状态。宏函数包括

FD_CLR(int fd, fd_set* set);      // 把fd在set中删除
FD_ISSET(int fd, fd_set* set);    // 测试set中fd是否存在
FD_SET(int fd, fd_set* set);      // 在set中设置fd
FD_ZERO(fd_set* set);             // 将set清零

fd_set时select提供的一种数据结构,实际上时一个long型数组,每一个比特位与一个打开文件句柄建立联系。Linux下设置fd_set的描述字大小位1024 bits。这与用户级打开文件限制数对应(FD_SETSIZE,可以使用ulimit -n查看)。

fd_set set;         // set为1024 bits,即128 bytes,以下例子仅展示低8位
FD_ZERO(&set);      // 清除所有set位,此时set为0000 0000
FD_SET(0, &set);    // 设置set中第0位,此时set为0000 0001
FD_CLR(1, &set);    // 清楚set中第1位,如果此时set为0000 0010,则设置后为0000 0000
FD_ISSET(2, &set);  // 测试第2位是否为1,如果此时set为0000 0100,则返回true

在来说下,为什么select需要设置n为需要等待文件描述符+1呢???

其实,select在说明文件解释,参数n需要设置为三个fd_set中最大文件描述符+1

nfds is the highest-numbered file descriptor in any of the three  sets, plus 1.
言下之意,是内核为了提高select的查询效率,仅查询fd_set中的低ndfs位。

接下来说,最后一个参数timeout。struct timeval结构体定义为

struct timeval {
               long    tv_sec;         /* seconds */
               long    tv_usec;        /* microseconds */
};

select可以阻塞式等待,也可以非阻塞返回。设置timeval后,select会占用CPU等待。达到以下条件之一返回。

(1)文件描述符就绪

(2)被信号中断

(3)时间溢出

最后说下,select的返回值。成功时,返回三个文件描述符字中包含的文件描述符的数量(非0);如果超时,返回0;遇到错误返回-1。

---------------------------------------------------分割线---------------------------------------------------------

言归正传,回到使用select操作socket上来。

有了上面的基础,实现多个socket,简直是小小case了。

直接上代码。

client.cpp

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

using namespace std;

int main()
{
    int sock = socket(AF_INET, SOCK_STREAM, 0);
    if(sock == -1){
        cerr << "socket failed" << endl;
        exit(1);
    }

    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(8080);
    inet_aton("127.0.0.1", &addr.sin_addr);

    struct sockaddr_in self_addr;
    self_addr.sin_family = AF_INET;
    self_addr.sin_port = htons(10000);
    inet_aton("127.0.0.1", &self_addr.sin_addr);

    /* 客户端也可以绑定端口 */
    int res = bind(sock, (struct sockaddr*)&self_addr, sizeof(self_addr));
    if(res == -1){
        cerr << "bind self failed" << endl;
        exit(1);
    }

    int res = connect(sock, (struct sockaddr *)&addr,
    sizeof(addr));
    if(res == -1){
        cerr << "connect failed" << endl;
        exit(1);
    }

    char buf[100] = "呵呵哒,我是萌萌哒";

    write(sock, buf, strlen(buf)+1);
    read(sock, buf, sizeof(buf));
    cout << buf << endl;

    return 0;
}

server.cpp

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

using namespace std;
const static int client_port = 8080;
const static int monitor_port = 8090;

int main()
{
    int client_sock = socket(AF_INET, SOCK_STREAM, 0);
    if(client_sock == -1){
        cerr << "socket failed" << endl;
        exit(1);
    }
    struct sockaddr_in client_addr;
    client_addr.sin_family = AF_INET;
    client_addr.sin_port = htons(client_port);
    inet_aton("127.0.0.1", &client_addr.sin_addr);

    int res = bind(client_sock, (struct sockaddr *)&client_addr,
    sizeof(client_addr));
    if(res == -1){
        cerr << "bind failed" << endl;
        exit(1);
    }

    listen(client_sock, 10);

    int monitor_sock = socket(AF_INET, SOCK_STREAM, 0);
    if(monitor_sock == -1){
        cerr << "socket failed" << endl;
        exit(1);
    }
    struct sockaddr_in monitor_addr;
    monitor_addr.sin_family = AF_INET;
    monitor_addr.sin_port = htons(monitor_port);
    inet_aton("127.0.0.1", &monitor_addr.sin_addr);

    res = bind(monitor_sock, (struct sockaddr *)&monitor_addr,
    sizeof(monitor_addr));
    if(res == -1){
        cerr << "bind failed" << endl;
        exit(1);
    }

    listen(monitor_sock, 10);
    fd_set allset;
    struct timeval tv = {0, 10000};		//set timeval(10ms)
    while(1){
        FD_ZERO(&allset);
        FD_SET(client_sock, &allset);
        FD_SET(monitor_sock, &allset);
        select(client_sock+1, &allset, NULL, NULL, &tv);
        select(monitor_sock+1, &allset, NULL, NULL, &tv);

        if(FD_ISSET(client_sock, &allset)){
            int clientfd = accept(client_sock, NULL, NULL);
            pthread_t thread;
            pthread_create(&thread, NULL, serv_client, (void *)&clientfd);
        }
        if(FD_ISSET(monitor_sock, &allset)){
            int monitorfd = accept(monitor_sock, NULL, NULL);
            pthread_t thread;
            pthread_create(&thread, NULL, serv_monitor, (void *)&monitorfd);
        }

    }

    return 0;
}

void *serv_client(void *arg)
{
    int socketfd = *(int *)arg;
    char buf[] = "萌萌哒,我是呵呵哒";
    write(socketfd, buf, sizeof(buf));

    return nullptr;
}


void *serv_monitor(void *arg)
{
    cout << "一百块也不给我" << endl;
    cout << "悄悄记下来" << endl;

    int socketfd = *(int *)arg;
    static struct sockaddr_in remote_addr;
    socklen_t len = sizeof(remote_addr);
    int ret = getsockname(socketfd, (struct sockaddr *)&remote_addr, &len);	//get the monitorfd msg
    if(ret != 0){
        cerr << "getsockname failed" << endl;
    }

    cout << "IP: " << remote_addr.sin_addr << endl;
    cout << "port: " <

 

你可能感兴趣的:(Linux,C/C++)