Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1417106
  • 博文数量: 1334
  • 博客积分: 645
  • 博客等级: 上士
  • 技术积分: 5762
  • 用 户 组: 普通用户
  • 注册时间: 2012-07-25 16:56
文章分类

全部博文(1334)

文章存档

2014年(108)

2013年(1059)

2012年(169)

分类: LINUX

2013-06-05 14:47:37

原文地址:socket非阻塞和复用 作者:LaoLiulaoliu

Socket的nonblock:

1.将socket设置为nonblock
    fcntl(sockfd, F_SETFL, O_NONBLOCK);
会使得send,recv变成非阻塞方式,通常在connect()之后设置。

2.已经设置为nonblock的socket变成阻塞的
    int flags;
    flags = fcntl(sockfd, F_GETFL, 0);
    flags &= ~O_NONBLOCK;
    if (fcntl(sockfd, F_SETFL, flags) == -1)
        perror("fcntl");

3.setsockopt()的SO_RCVTIMEEO,SO_SNDTIMEO参数的作用跟socket的nonblock相同



Socket的复用:
  复用通过select和poll。
  man select: Allow a program to monitor multiple file descriptors, waiting until one or more of the file descriptors become "ready" for some class of I/O operation.
 
  单线程实现多用户通信:
被多个client端链接的socket服务端,这里监测的是readfds

  示例1源自cnblog的博主,我把浪得虚名的意见加入并进行修改,示例2源自浪得虚名,我修改2处错误。
  示例2加入了一些注释和一个QT超时的判断,其实只看示例1就可以。

示例1

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define MYPORT 1234 // the port users will be connecting to
#define MAXCLIENT 5 // how many pending connections queue will hold
#define BUF_SIZE 200

int fd_A[MAXCLIENT] = {0}; // accepted connection fd
int conn_amount; // current connection amount


void showclient()
{
    int i;
    printf("client amount: %d\n", conn_amount);
    for (i = 0; i < MAXCLIENT; i++) {
        printf("[%d]:%d ", i, fd_A[i]);
    }
    printf("\n\n");
}

int main(void)
{
    int sock_fd, new_fd; // listen on sock_fd, new connection on new_fd

    struct sockaddr_in server_addr; // server address information
    struct sockaddr_in client_addr; // connector's address information

    socklen_t sin_size;
    int yes = 1;
    char buf[BUF_SIZE];
    int ret;
    int i;

    if ((sock_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
        perror("socket");
        exit(1);
    }

    if (setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1) {
        perror("setsockopt");
        exit(1);
    }
    
    server_addr.sin_family = AF_INET; // host byte order
    server_addr.sin_port = htons(MYPORT); // short, network byte order
    server_addr.sin_addr.s_addr = INADDR_ANY; // automatically fill with my IP

    memset(server_addr.sin_zero, '\0', sizeof(server_addr.sin_zero));

    if (bind(sock_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
        perror("bind");
        exit(1);
    }

    if (listen(sock_fd, MAXCLIENT) == -1) {
        perror("listen");
        exit(1);
    }

    printf("listen port %d\n", MYPORT);

    fd_set fdsr;
    int maxsock;
    struct timeval tv;

    conn_amount = 0;
    sin_size = sizeof(client_addr);
    maxsock = sock_fd;
    while (1) {
        // initialize file descriptor set

        FD_ZERO(&fdsr);
        FD_SET(sock_fd, &fdsr);

        // timeout setting

        tv.tv_sec = 30;
        tv.tv_usec = 0;

        // add active connection to fd set

        for (i = 0; i < MAXCLIENT; i++) {
            if (fd_A[i] != 0) {
                FD_SET(fd_A[i], &fdsr);
            }
        }

        ret = select(maxsock + 1, &fdsr, NULL, NULL, &tv);
        if (ret < 0) {
            perror("select");
            break;
        } else if (ret == 0) {
            printf("timeout\n");
            continue;
        }

        // check every fd in the set

        for (i = 0; i < MAXCLIENT; i++) {
            if (FD_ISSET(fd_A[i], &fdsr)) {
                ret = recv(fd_A[i], buf, sizeof(buf), 0);
                if (ret <= 0) { // client close

                    printf("client[%d] close\n", i);
                    close(fd_A[i]);
                    FD_CLR(fd_A[i], &fdsr);
                    fd_A[i] = 0;
                    --conn_amount;
                } else { // receive data

                    if (ret < BUF_SIZE)
                        memset(&buf[ret], '\0', 1);
                    printf("client[%d] send:%s\n", i, buf);
                }
            }
        }

// after select, sock_fd still "SET" means there is a new connection comes
        if (FD_ISSET(sock_fd, &fdsr))
        {
            new_fd = accept(sock_fd, (struct sockaddr *)&client_addr, &sin_size);
            if (new_fd <= 0) {
                perror("accept");
                continue;
            }

            // add to fd queue

            for (i = 0; i < MAXCLIENT; ++i)
            {
                if (fd_A[i] == 0)
                {
                    fd_A[i] = new_fd;
                    break;
                }
            }
            ++conn_amount;
            if (conn_amount < MAXCLIENT) {
                printf("new connection client[%d] %s:%d\n", i,
                        inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
                if (new_fd > maxsock)
                    maxsock = new_fd;
            }
            else {
                printf("max connections arrive, exit\n");
                send(new_fd, "bye", 4, 0);
                close(new_fd);
                continue; //break;

            }
        }
        showclient();
    }
    // close other connections

    for (i = 0; i < MAXCLIENT; i++) {
        if (fd_A[i] != 0) {
            close(fd_A[i]);
        }
    }

    exit(0);
}


示例2

#define BUF_SIZE 0x100
#define PORT 9999
#define LISTENQ 8
#define MAXCLIENT 8
#define MAX_IDLECONNCTIME 5

int fd[MAXCLIENT] = {0};
int con_time[MAXCLIENT];

void run()
{
    char msg[BUF_SIZE];
    int Listen_socket,ret,on;
    struct sockaddr_in local_addr;
    struct sockaddr_in client_addr;
    int i;
    fd_set fdsr; //文件描述符集的定义

    socklen_t addr_size;
    addr_size = sizeof(struct sockaddr_in);

    int conn_amount = 0; //当前最大活跃连接数

    int new_fd;
    struct timeval tv;

    //建立socket套接字

    if( (Listen_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1)
    {
        //emit err_msg_signal("failed create socket");
        perror("failed create socket");
    }
    
    //bind API 函数将允许地址的立即重用

    on = 1;
    ret = setsockopt( Listen_socket, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on) );
    
    int nNetTimeout=2000;//2秒

    //设置发送时限

    setsockopt(Listen_socket,SOL_SOCKET,SO_SNDTIMEO,(char *)&nNetTimeout,sizeof(int) );
    //设置接收时限

    setsockopt(Listen_socket,SOL_SOCKET,SO_RCVTIMEO,(char *)&nNetTimeout,sizeof(int));
    
    //设置本机服务类型

    local_addr.sin_family = AF_INET;
    local_addr.sin_port = htons(PORT);
    local_addr.sin_addr.s_addr = INADDR_ANY;
    
    //while(flag_port == 0)

    //绑定本机IP和端口号

    if(bind(Listen_socket, (struct sockaddr*)&local_addr, sizeof(struct sockaddr)) == -1)
    {
        //emit err_msg_signal("failed bind");
        perror("failed bind");
    }
    
    //监听客户端连接

    if(listen(Listen_socket, LISTENQ) == -1)
    {
        //emit err_msg_signal("failed listen");
        perror("failed listen");
    }
    
    
    QTime current_time;
    current_time = QTime::currentTime();
    int flag_minutechange = 0;
    int lastminute = current_time.currentTime().minute();
    int maxsock = Listen_socket;

    /***************************************
     * 以下为并发连接处理,系统关键部分
     * ***************************************/


    while (1)
    {

        if( current_time.currentTime().minute() != lastminute) //每次循环开始都读取系统时间,与上次分钟数比较,为以下超时判断提供依据

        {
            lastminute = current_time.currentTime().minute();
            flag_minutechange = 1;
        }

        FD_ZERO(&fdsr); //每次进入循环都重建描述符集

        FD_SET(Listen_socket, &fdsr);
        for (i = 0; i < MAXCLIENT; i++) //将存在的套接字加入描述符集

        {
            if (fd[i] != 0)
            {
                FD_SET(fd[i], &fdsr);
                if(flag_minutechange == 1)
                {
                    con_time[i]--;
                    if(con_time[i] <= 0)
                    {
                        close(fd[i]);
                        FD_CLR(fd[i], &fdsr);
                        fd[i] = 0;
                        conn_amount--;
                    }
                }

            }
        }
        flag_minutechange = 0;
        tv.tv_sec = 1;
        tv.tv_usec = 0;
        ret = select(maxsock + 1, &fdsr, NULL, NULL,&tv); //关键的select()函数,用来探测各套接字的异常
        //如果在文件描述符集中有连接请求或发送请求,会作相应处理,
        //从而成功的解决了单线程情况下阻塞进程的情况,实现多用户连接与通信


        if (ret < 0) //<0表示探测失败
        {
            perror("failed select");
            break;
        }
        else if (ret == 0) //=0表示超时,下一轮循环
        {
            //printf("timeout\n");

            continue;
        }

        // 如果select发现有异常,循环判断各活跃连接是否有数据到来

        for (i = 0; i < MAXCLIENT; i++)
        {
            if (FD_ISSET(fd[i], &fdsr))
            {
                ret = recv(fd[i], msg, BUF_SIZE, 0);
                if (ret <= 0) // recv<=0,表明客户端关闭连接,服务器也关闭相应连接,并把连接套接子从文件描述符集中清除
                {
                    printf("client[%d] close\n", i);
                    close(fd[i]);
                    FD_CLR(fd[i], &fdsr);
                    fd[i] = 0;
                    conn_amount--;
                }
                else //否则表明客户端有数据发送过来,作相应接受处理
                {
                    con_time[i] = MAX_IDLECONNCTIME; //重新写入fd[i]的超时数,再此之后如果MAX_IDLECONNCTIME分钟内此连接无反应,服务器会关闭该连接

                    if (ret < BUF_SIZE)
                        //emit err_msg_signal("client ip: " + QString::fromLatin1(inet_ntoa(client_addr.sin_addr)) +

                        //    " port: " + QString::number(ntohs(client_addr.sin_port)) + " coming data");

                    printf("client[%d] send:%s\n", i, msg);
                    msg[ret] = '\0';
                    //emit recv_msg_signal(QString::fromLatin1(msg),fd[i]);

                    //send(fd[i],msg,ret,0);

                }
            }
        }


        // 以下说明异常有来自客户端的连接请求

        if (FD_ISSET(Listen_socket, &fdsr))
        {
            new_fd = accept(Listen_socket, (struct sockaddr *)&client_addr, &addr_size);
            if (new_fd <= 0)
            {
                perror("failed accept");
                continue;
            }

            // 判断活跃连接数时候是否小于最大连接数,如果是,添加新连接到文件描述符集中
            if (conn_amount < MAXCLIENT)
            {
                for(i = 0; i < MAXCLIENT; i++)
                {
                    if(fd[i] == 0)
                    {
                        fd[i] = new_fd;
                        con_time[i] = MAX_IDLECONNCTIME; //每次新建立连接,设置该连接的超时数,如果此连接此后MAX_IDLECONNCTIME分钟内无反应,关闭该连接

                        break;
                    }
                }
                conn_amount++;
                printf("new connection client[%d] %s:%d\n", i,
                        inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
                //emit err_msg_signal("client ip: " + QString::fromLatin1(inet_ntoa(client_addr.sin_addr)) +

                //    " port: " + QString::number(ntohs(client_addr.sin_port)));

                if (new_fd > maxsock)
                    maxsock = new_fd;
            }
            else
            {
                printf("MAXCLIENT arrive, exit\n");
                send(new_fd, "over MAXCLIENT\n", 25, 0);
                close(new_fd);
                continue;
            }
        }

    }
}


阅读(243) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~