一步异步Socket---Linux服务器编程入门之非阻塞IO简介

写惯客户端的人总觉得服务器开发高深而又神秘。纵观服务器编程,大概可分为异步I/O线程模式(如几个线程处理SOCKET,几个线程处理计算,线程个数限制等),锁及数据共享,内存池等。

本文尝试着客户端程序员的角度为起点,介绍服务端编程的基础---异步I/O。

对于客户端程序来讲,Socket是个很简单的东西。阻塞的API,操作完成后才会返回。完全可以像编写本地程序一样的编写网络程序。尤其是VC的CSocket封装,用起来更加的方便。随手写一个,没有什么难度。

CString cstrIP = "192.168.0.1";

int nPort = 2981;

CSocket s ;

s.Create ();

s.Connect (cstrIP, nPort);

 

char *szText = "Hello, I am client A, Who are you?";

s.Send (szText, strlen(szText ) + 1);

 

char szBuf [1024];

s.Receive (szBuf,1024);

 

 

想来服务端写起来也没有什么难度吧?随手写一个

CSocket s ;

s.Create (2981);

s.Listen ();

while(1)

{

                 CSocket c ;

                 if(m_socket .Accept( c))

                {

                                 char szbuf [1024];

                                 c.Receive (szbuf,1024);

                                 sprintf(szbuf ,"I am server A.");

                                 c.Send (szbuf, strlen(szbuf ));

                }

}

简单,干净,明了。运行起来也没什么问题。如果只有一个服务器,一个客户端。这种写法没什么问题,也不需要引入复杂的异步I/O。

但如果服务器需要支持两个以上的客户端呢?怎么办?难道要这么写?

for(int i = 0; i < nClients;i ++)

{

                 char szbuf [1024];

                 c[i ].Receive( szbuf,1024);   //???????

                 sprintf(szbuf ,"I am server A.");

                 c[i ].Send( szbuf,strlen (szbuf));

}

一旦c[2]有数据而c[1]没有,服务器永远也没法获取c[2]的数据。

解决的办法可以用多线程。每一个客户端连接到来的时候都开一个线程来处理它。等等,为了读写分离可能要为每个客户端开两个线程来处理,一个负责读,一个负责写。。。

如果有一千个客户端怎么办?如果有一万个客户端怎么办?于是曾经有一道面试题,Linux/Windows最多可以开多少个线程?

在游戏服务器中,为每个连接开线程是件很恐怖的事。服务器不用干别的,单单执行线程切换都可以把它类死。

 

windows就到此为止,切到Linux吧,因为大多数的游戏服务器都是在Linux下开发的。

 

这时我们的异步I/O就隆重出场了,Linux下可以用:

fcntl(fd, F_SETFL, O_NONBLOCK);

fd是socket的句柄。调用以上函数后,socket句柄就变成了非阻塞式的。之后不管对它调用什么操作(send,receive, accept.....)都会立刻返回。如果没有数据要发送/接受,函数返回值<0,并且(errno == EINTR || errno ==EWOULDBLOCK || errno == EAGAIN)。

/* If you only have a couple dozen fds, this version won't be awful */

fd_set readset;

int i, n;

char buf[1024];

 

while (i_still_want_to_read()) {

    int maxfd = -1;

    FD_ZERO(&readset);

 

    /* Add all of the interesting fds to readset */

    for (i=0; i < n_sockets; ++i) {

         if (fd[i]>maxfd) maxfd = fd[i];

         FD_SET(fd[i], &readset);

    }

 

    /* Wait until one or more fds are ready to read */

    select(maxfd+1, &readset, NULL, NULL, NULL);//线程会卡在这里,一直到有某个句柄可用为止

 

    /* Process all of the fds that are still set in readset */

    for (i=0; i < n_sockets; ++i) {

        if (FD_ISSET(fd[i], &readset)) {//检查究竟是哪个句柄变为有效了。

            n = recv(fd[i], buf, sizeof(buf), 0);//开始接收数据

            if (n == 0) {

                handle_close(fd[i]);

            } elseif (n < 0) {

                if (errno == EAGAIN)

                     ; /* The kernel didn't have any data for us to read. */

                else

                     handle_error(fd[i], errno);

             } else {

                handle_input(fd[i], buf, n);

             }

        }

    }

}

 

以下是一个完整的服务器代码实现

/* For sockaddr_in */

#include <netinet/in.h>

/* For socket functions */

#include <sys/socket.h>

/* For fcntl */

#include <fcntl.h>

/* for select */

#include <sys/select.h>

 

#include <assert.h>

#include <unistd.h>

#include <string.h>

#include <stdlib.h>

#include <stdio.h>

#include <errno.h>

 

#define MAX_LINE 16384

 

char

rot13_char(char c)

{

    /* We don't want to use isalpha here; setting the locale would change     * which characters are considered alphabetical. */

    if ((c >= 'a' && c <= 'm') || (c >= 'A' && c <= 'M'))

        return c + 13;

    elseif ((c >= 'n' && c <= 'z') || (c >= 'N' && c <= 'Z'))

        return c - 13;

    else

        return c;

}

 

struct fd_state {

    char buffer[MAX_LINE];

    size_t buffer_used;

 

    int writing;

    size_t n_written;

    size_t write_upto;

};

 

struct fd_state *

alloc_fd_state(void)

{

    struct fd_state *state = malloc(sizeof(struct fd_state));

    if (!state)

        return NULL;

    state->buffer_used = state->n_written = state->writing =

        state->write_upto = 0;

    return state;

}

 

void

free_fd_state(struct fd_state *state)

{

    free(state);

}

 

void

make_nonblocking(int fd)

{

    fcntl(fd, F_SETFL, O_NONBLOCK);

}

 

int

do_read(int fd, struct fd_state *state)

{

    char buf[1024];

    int i;

    ssize_t result;

    while (1) {

        result = recv(fd, buf, sizeof(buf), 0);

        if (result <= 0)

            break;

 

        for (i=0; i < result; ++i)  {

            if (state->buffer_used < sizeof(state->buffer))

                state->buffer[state->buffer_used++] = rot13_char(buf[i]);

            if (buf[i] == '\n') {

                state->writing = 1;

                state->write_upto = state->buffer_used;

            }

        }

    }

 

    if (result == 0) {

        return 1;

    } elseif (result < 0) {

        if (errno == EAGAIN)

            return 0;

        return -1;

    }

 

    return 0;

}

 

int

do_write(int fd, struct fd_state *state)

{

    while (state->n_written < state->write_upto) {

        ssize_t result = send(fd, state->buffer + state->n_written,

                              state->write_upto - state->n_written, 0);

        if (result < 0) {

            if (errno == EAGAIN)

                return 0;

            return -1;

        }

        assert(result != 0);

 

        state->n_written += result;

    }

 

    if (state->n_written == state->buffer_used)

        state->n_written = state->write_upto = state->buffer_used = 0;

 

    state->writing = 0;

 

    return 0;

}

 

void

run(void)

{

    int listener;

    struct fd_state *state[FD_SETSIZE];

    struct sockaddr_in sin;

    int i, maxfd;

    fd_set readset, writeset, exset;

 

    sin.sin_family = AF_INET;

    sin.sin_addr.s_addr = 0;

    sin.sin_port = htons(40713);

 

    for (i = 0; i < FD_SETSIZE; ++i)

        state[i] = NULL;

 

    listener = socket(AF_INET, SOCK_STREAM, 0);

    make_nonblocking(listener);

 

#ifndef WIN32

    {

        int one = 1;

        setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));

    }

#endif

 

    if (bind(listener, (struct sockaddr*)&sin, sizeof(sin)) < 0) {

        perror("bind");

        return;

    }

 

    if (listen(listener, 16)<0) {

        perror("listen");

        return;

    }

 

    FD_ZERO(&readset);

    FD_ZERO(&writeset);

    FD_ZERO(&exset);

 

    while (1) {

        maxfd = listener;

 

        FD_ZERO(&readset);

        FD_ZERO(&writeset);

        FD_ZERO(&exset);

 

        FD_SET(listener, &readset);

 

        for (i=0; i < FD_SETSIZE; ++i) {

            if (state[i]) {

                if (i > maxfd)

                    maxfd = i;

                FD_SET(i, &readset);

                if (state[i]->writing) {

                    FD_SET(i, &writeset);

                }

            }

        }

 

        if (select(maxfd+1, &readset, &writeset, &exset, NULL) < 0) {

            perror("select");

            return;

        }

 

        if (FD_ISSET(listener, &readset)) {

            struct sockaddr_storage ss;

            socklen_t slen = sizeof(ss);

            int fd = accept(listener, (struct sockaddr*)&ss, &slen);

            if (fd < 0) {

                perror("accept");

            } elseif (fd > FD_SETSIZE) {

                close(fd);

            } else {

                make_nonblocking(fd);

                state[fd] = alloc_fd_state();

                assert(state[fd]);/*XXX*/

            }

        }

 

        for (i=0; i < maxfd+1; ++i) {

            int r = 0;

            if (i == listener)

                continue;

 

            if (FD_ISSET(i, &readset)) {

                r = do_read(i, state[i]);

            }

            if (r == 0 && FD_ISSET(i, &writeset)) {

                r = do_write(i, state[i]);

            }

            if (r) {

                free_fd_state(state[i]);

                state[i] = NULL;

                close(i);

            }

        }

    }

}

 

int

main(int c, char **v)

{

    setvbuf(stdout, NULL, _IONBF, 0);

 

    run();

    return 0;

}

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值