php网络socket编程实现并发服务器——IO多路复用

IO多路复用

一、五种网络I/O模型

在Linux下进行网络编程时,服务器端编程经常需要构造高性能的IO模型,常见的IO模型有五种:

    (1)同步阻塞IO(Blocking IO)

    (2)同步非阻塞IO(Non-blocking IO)

    (3)IO多路复用(IO Multiplexing)

    (4)信号驱动IO(signal driven IO)

    (5)异步IO(Asynchronous IO)


        目前操作系统对异步IO的支持并非特别完善,更多的是采用IO多路复用模型模拟异步IO的方式。这里我们主要介绍IO多路复用。


        IO多路复用模型是建立在内核提供的多路分离函数select()基础之上的,使用select()函数可以避免同步非阻塞IO模型中轮询等待的问题,此外poll()、epoll()都是这种模型。


IO多路复用模型:



        在该种模式下,用户首先将需要进行IO操作的socket添加到select()中,然后阻塞等待select()系统调用返回。当数据到达时,socket被激活,select()函数返回。用户线程正式发起read请求,读取数据并继续执行。从流程上来看,使用select函数进行IO请求和同步阻塞模型没有太大的区别,甚至还多了添加监视socket,以及调用select函数的额外操作,效率更差。但是,使用select()最大的优势是用户可以在一个线程内同时处理多个socket的IO请求。用户可以注册多个socket,然后不断地调用select读取被激活的socket,即可达到在同一个线程内同时处理多个IO请求的目标。而在同步阻塞模型中,必须通过多线程编程才能达到。


二、select多路复用

        select()函数允许进程指示内核等待多个事件(文件描述符)中的任何一个发生,并只在有一个或多个事件发生或经历一段指定时间后才唤醒它,然后判断是哪个文件描述符发生了事件并进行相应的处理。


#include <sys/select.h>

#include <sys/time.h>


struct timeval

{

long tv_sec; //seconds

long tv_usec; //microseconds

};


FD_ZERO(fd_set* fds) //清空集合

FD_SET(int fd, fd_set* fds) //将给定的描述符加入集合

FD_ISSET(int fd, fd_set* fds) //判断指定描述符是否在集合中

FD_CLR(int fd, fd_set* fds) //将给定的描述符从文件中删除


int select(int max_fd, fd_set *readset, fd_set *writeset, fd_set *exceptset, struct timeval *timeout);


说明:select监视并等待多个文件描述符的属性发生变化,它监视的属性分3类,分别是:


readfds(文件描述符有数据到来可读)

writefds(文件描述符可写)

exceptfds(文件描述符异常)

        调用后select()函数会阻塞,直到有描述符就绪(有数据可读、可写、或者有错误异常),或者超时(timeout 指定等待时间)发生函数才返回。当select()函数返回后,可以通过遍历 fdset,来找到究竟是哪些文件描述符就绪。


select函数的返回值是就绪描述符的数目,超时时返回0,出错返回-1;

第一个参数max_fd:待测试的fd的总个数。因为Linux内核是从0开始到max_fd-1扫描文件描述符,所以第一个参数的值是所监听的文件描述符中最大的一个+1。

中间三个参数readset、writeset和exceptset:指定要让内核测试读、写和异常条件的fd集合。如果不需要测试,则可以设置为NULL;

第五个参数timeout:设置select的超时时间。如果设置为NULL,则永不超时;

        注:Linux内核参数__FD_SETSIZE定义了每个FD_SET的句柄个数,这也意味着select所用到的FD_SET是有限的,也正是这个限制,select()函数只能同时处理1024个客户端的连接请求:


/linux/posix_types.h:

#define __FD_SETSIZE 1024

1

2

使用select()多路复用实现网络socket服务器多路并发流程图:

                 

使用select()多路复用实现并发服务器示例代码:


#include <stdio.h>

#include <stdlib.h>

#include <unistd.h>

#include <string.h>

#include <errno.h>

#include <ctype.h>

#include <time.h>

#include <pthread.h>

#include <getopt.h>

#include <libgen.h>

#include <sys/types.h>

#include <sys/socket.h>

#include <arpa/inet.h>

#include <netinet/in.h>


#define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0]))


static inline void msleep(unsigned long ms);

static inline void print_usage(char *progname);

int socket_server_init(char *listen_ip, int listen_port);


int main(int argc, char **argv)

{

int listenfd;

int connfd;

int serv_port = 0;

int daemon_run = 0;

int opt;

int rv;

int i, j;

int found;

int maxfd = 0;

int fds_array[1024];

fd_set rdset;

char    *progname = NULL;

char buf[1024];

struct option long_options[] ={

{"daemon", no_argument, NULL, 'b'},

{"port", required_argument, NULL, 'p'},

{"help", no_argument, NULL, 'h'},

{NULL, 0, NULL, 0}

};

progname = basename(argv[0]);

/* Parser the command line parameters */

while ((opt = getopt_long(argc, argv, "bp:h", long_options, NULL)) != -1)

{

switch (opt)

{

case 'b':

daemon_run = 1;

break;

case 'p':

serv_port = atoi(optarg);

break;

case 'h': /* Get help information */

print_usage(progname);

return EXIT_SUCCESS;

default:

break;

}

}

if (!serv_port)

{

print_usage(progname);

return -1;

}

if ((listenfd = socket_server_init(NULL, serv_port)) < 0)

{

printf("ERROR: %s server listen on port %d failure\n", argv[0],serv_port);

return -2;

}

printf("%s server start to listen on port %d\n", argv[0],serv_port);

/* set program running on background */

if (daemon_run)

{

daemon(0, 0);

}

for (i = 0; i < ARRAY_SIZE(fds_array); i++)

{

fds_array[i] = -1;

}

fds_array[0] = listenfd;

while (1)

{

FD_ZERO(&rdset);

for (i = 0; i < ARRAY_SIZE(fds_array); i++)

{

if (fds_array[i] < 0)

{

continue;

}

maxfd = (fds_array[i] > maxfd)? fds_array[i] : maxfd;

FD_SET(fds_array[i], &rdset);

}

/* program will blocked here */

rv = select(maxfd+1, &rdset, NULL, NULL, NULL);

if (rv < 0)

{

printf("select failure: %s\n", strerror(errno));

break;

}

else if (rv == 0)

{

printf("select get timeout\n");

continue;

}

/* listen socket get event means new client start connect now */

if (FD_ISSET(listenfd, &rdset))

{

if ((connfd=accept(listenfd, (struct sockaddr *)NULL, NULL)) < 0)

{

printf("accept new client failure: %s\n", strerror(errno));

continue;

}

found = 0;

for (i = 0; i < ARRAY_SIZE(fds_array); i++)

{

if (fds_array[i] < 0)

{

printf("accept new client[%d] and add it into array\n", connfd );

fds_array[i] = connfd;

found = 1;

break;

}

}

if (!found)

{

printf("accept new client[%d] but full, so refuse it\n", connfd);

close(connfd);

}

}

else /* data arrive from already connected client */

{

for (i = 0; i < ARRAY_SIZE(fds_array); i++)

{

if (fds_array[i]<0 || !FD_ISSET(fds_array[i], &rdset))

{

continue;

}

if ((rv=read(fds_array[i], buf, sizeof(buf))) <= 0)

{

printf("socket[%d] read failure or get disconncet.\n", fds_array[i]);

close(fds_array[i]);

fds_array[i] = -1;

}

else

{

printf("socket[%d] read get %d bytes data\n", fds_array[i], rv);

/* convert letter from lowercase to uppercase */

for (j = 0; j < rv; j++)

{

buf[j] = toupper(buf[j]);

}

if (write(fds_array[i], buf, rv) < 0)

{

printf("socket[%d] write failure: %s\n", fds_array[i], strerror(errno));

close(fds_array[i]);

fds_array[i] = -1;

}

}

}

}

}

close(listenfd);

return 0;

}


static inline void msleep(unsigned long ms)

{

struct timeval tv;

tv.tv_sec = ms/1000;

tv.tv_usec = (ms%1000)*1000;

select(0, NULL, NULL, NULL, &tv);

}


static inline void print_usage(char *progname)

{

printf("Usage: %s [OPTION]...\n", progname);

printf(" %s is a socket server program, which used to verify client and echo back string from it\n",progname);

printf("\nMandatory arguments to long options are mandatory for short options too:\n");

printf(" -b[daemon ] set program running on background\n");

printf(" -p[port ] Socket server port address\n");

printf(" -h[help ] Display this help information\n");

printf("\nExample: %s -b -p 8900\n", progname);

return ;

}


       基于select的I/O复用模型的是单进程执行可以为多个客户端服务,这样可以减少创建线程或进程所需要的CPU时间片或内存资源的开销;此外几乎所有的平台上都支持select(),其良好跨平台支持是它的另一个优点。当然它也有两个主要的缺点:


每次调用 select()都需要把fd集合从用户态拷贝到内核态,之后内核需要遍历所有传递进来的fd,这时如果客户端fd很多时会导致系统开销很大;

单个进程能够监视的文件描述符的数量存在最大限制,在Linux上一般为1024,可以通过setrlimit()、修改宏定义甚至重新编译内核等方式来提升这一限制,但是这样也会造成效率的降低;

三、poll多路复用

1、poll()简介

       select()和poll()系统调用的本质一样。poll()的机制与select()类似,与select()在本质上没有多大差别,管理多个描述符也是进行轮询,根据描述符的状态进行处理,但是poll()没有最大文件描述符数量的限制(但是数量过大后性能也是会下降)。poll()和select() 同样存在一个缺点就是,包含大量文件描述符的数组被整体复制于用户态和内核的地址空间之间,而不论这些文件描述符是否就绪,它的开销随着文件描述符数量的增加而线性增大!


poll函数的原型说明如下:


#include <poll.h>


struct pollfd

{

int fd; /* 文件描述符 */

short events; /* 等待的事件 */

short revents; /* 实际发生了的事件 */

} ;


int poll(struct pollfd *fds, nfds_t nfds, int timeout);


第一个参数 fds 用来指向一个struct pollfd类型的结构体,每个结构体指定了一个被监视的文件描述符,指示poll()监视多个文件描述符。events域是监视该文件描述符的事件掩码,由用户来设置这个域。revents域是文件描述符的操作结果事件掩码,内核在调用返回时设置这个域,events域中请求的任何事件都可能在revents域中返回。下表列出指定 events 标志以及测试 revents 标志的一些常值:


POLLIN | POLLPRI 等价于 select()的读事件;

POLLOUT |POLLWRBAND 等价于 select()的写事件;

POLLIN 等价于 POLLRDNORM | POLLRDBAND;

POLLOUT 等价于 POLLWRNORM;

注:这些标志并不是互斥的:它们可能被同时设置,表示这个文件描述符的读取和写入操作都会正常返回而不阻塞。


第二个参数 nfds :指定数组中监听的元素个数;


第三个参数 timeout :指定等待的毫秒数。

       1、timeout为负数值,表示无限超时,这时poll()会一直挂起,直到一个指定事件发生;

       2、timeout为0,指示poll调用立即返回,并列出准备好I/O的文件描述符,但并不等待其它的事件。


返回值:该函数成功调用时,poll()返回结构体中revents域不为0的文件描述符个数;如果在超时前没有任何事件发生,poll()返回0;失败时,poll()返回-1,并设置errno为下列值之一:


       * EBADF         一个或多个结构体中指定的文件描述符无效。

       * EFAULTfds   指针指向的地址超出进程的地址空间。

       * EINTR     请求的事件之前产生一个信号,调用可以重新发起。

       * EINVALnfds  参数超出PLIMIT_NOFILE值。

       * ENOMEM     可用内存不足,无法完成请求。


2、使用poll()多路复用实现并发服务器

模块代码如下:

注:完整代码可参考select()实现


for (i = 0; i < ARRAY_SIZE(fds_array); i++)

{

fds_array[i].fd = -1;

}

fds_array[0].fd = listenfd;

fds_array[0].events = POLLIN;

max = 0;

while (1)

{

/* program will blocked here */

rv = poll(fds_array, max+1, -1);

if (rv < 0)

{

printf("select failure: %s\n", strerror(errno));

break;

}

else if (rv == 0)

{

printf("select get timeout\n");

continue;

}

/* listen socket get event means new client start connect now */

if (fds_array[0].revents & POLLIN)

{

if ((connfd=accept(listenfd, (struct sockaddr *)NULL, NULL)) < 0)

{

printf("accept new client failure: %s\n", strerror(errno));

continue;

}

found = 0;

for (i = 1; i < ARRAY_SIZE(fds_array); i++)

{

if (fds_array[i].fd < 0)

{

printf("accept new client[%d] and add it into array\n", connfd );

fds_array[i].fd = connfd;

fds_array[i].events = POLLIN;

found = 1;

break;

}

}

if (!found)

{

printf("accept new client[%d] but full, so refuse it\n", connfd);

close(connfd);

continue;

}

max = i > max ? i : max;

if (--rv <= 0)

{

continue;

}

}

else /* data arrive from already connected client */

{

for (i = 1; i < ARRAY_SIZE(fds_array); i++)

{

if (fds_array[i].fd < 0)

{

continue;

}

if ((rv = read(fds_array[i].fd, buf, sizeof(buf))) <= 0)

{

printf("socket[%d] read failure or get disconncet.\n", fds_array[i].fd);

close(fds_array[i].fd);

fds_array[i].fd = -1;

}

else

{

printf("socket[%d] read get %d bytes data\n", fds_array[i].fd, rv);

/* convert letter from lowercase to uppercase */

for (j = 0; j <r v; j++)

{

buf[j] = toupper(buf[j]);

}

if (write(fds_array[i].fd, buf, rv) < 0)

{

printf("socket[%d] write failure: %s\n", fds_array[i].fd, strerror(errno));

close(fds_array[i].fd);

fds_array[i].fd = -1;

}

}

}

}

}


四、epoll多路复用

1、epoll()简介

       epoll是Linux内核为处理大批量文件描述符而作了改进的poll,是Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。另外在获取事件时,它无须遍历整个被监听的描述符集,只需遍历那些被内核IO事件异步唤醒而加入Ready队列的描述符集合即可。epoll除了提供select/poll那种IO事件的水平触发(Level Triggered)外,还提供了边缘触发(Edge Triggered),这就使得用户空间程序有可能缓存IO状态,减少epoll_wait/epoll_pwait的调用,提高应用程序效率。


LT(level triggered)是缺省的工作方式,并且同时支持block和no-block socket.在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行IO操作。如果你不作任何操作,内核还是会继续通知你的,所以,这种模式编程出错误可能性要小一点。传统的select/poll都是这种模型的代表。

ET (edge-triggered)是高速工作方式,只支持non-block socket。在这种模式下,当描述符从未就绪变为就绪时,内核通过epoll告诉你。然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知,直到你做了某些操作导致那个文件描述符不再为就绪状态了(比如,你在发送,接收或者接收请求,或者发送接收的数据少于一定量时导致了一个EWOULDBLOCK 错误)。但是请注意,如果一直不对这个fd作IO操作(从而导致它再次变成未就绪),内核不会发送更多的通知(only once),不过在TCP协议中,ET模式的加速效用仍需要更多的benchmark确认。

       ET和LT的区别就在这里体现,LT事件不会丢弃,而是只要读buffer里面有数据可以让用户读,则不断的通知你。而ET则只在事件发生之时通知。可以简单理解为LT是水平触发,而ET是边缘触发。LT模式只要有事件未处理就会触发,而ET则只在高低电平变换时(即状态从1到0或者0到1)触发。


2、创建epoll实例:epoll_create()

#include <sys/epoll.h>


int epoll_create(int size);

1

2

3

       系统调用epoll_create()创建了一个新的epoll实例,其对应的兴趣列表初始化为空。若成功返回文件描述符,若出错返回-1。参数size指定了我们想要通过epoll实例来检查的文件描述符个数。该参数并不是一个上限,而是告诉内核应该如何为内部数据结构划分初始大小。从Linux2.6.8版以来,size参数被忽略不用。


       作为函数返回值,epoll_create()返回了代表新创建的epoll实例的文件描述符。这个文件描述符在其他几个epoll系统调用中用来表示epoll实例。当这个文件描述符不再需要时,应该通过close()来关闭。当所有与epoll实例相关的文件描述符都被关闭时,实例被销毁,相关的资源都返还给系统。


3、修改epoll的兴趣列表:epoll_ctl()

#include <sys/epoll.h>


int epoll_ctl(int epfd, int op, int fd, struct epoll_event *ev);

1

2

3

       系统调用epoll_ctl()能够修改由文件描述符epfd所代表的epoll实例中的兴趣列表。若成功返回0,若出错返回-1。


1、 第一个参数epfd是epoll_create()的返回值;

2、 第二个参数op用来指定需要执行的操作,它可以是如下几种值:


EPOLL_CTL_ADD:将描述符fd添加到epoll实例中的兴趣列表中去。对于fd上我们感兴趣的事件,都指定在ev所指向的结构体中。如果我们试图向兴趣列表中添加一个已存在的文件描述符,epoll_ctl()将出现EEXIST错误;

EPOLL_CTL_MOD:修改描述符上设定的事件,需要用到由ev所指向的结构体中的信息。如果我们试图修改不在兴趣列表中的文件描述符,epoll_ctl()将出现ENOENT错误;

EPOLL_CTL_DEL:将文件描述符fd从epfd的兴趣列表中移除,该操作忽略参数ev。如果我们试图移除一个不在epfd的兴趣列表中的文件描述符,epoll_ctl()将出现ENOENT错误。关闭一个文件描述符会自动将其从所有的epoll实例的兴趣列表移除;

3、 第三个参数fd指明了要修改兴趣列表中的哪一个文件描述符的设定。该参数可以是代表管道、FIFO、套接字、POSIX消息队列、inotify实例、终端、设备,甚至是另一个epoll实例的文件描述符。但是,这里fd不能作为普通文件或目录的文件描述符;

4、 第四个参数ev是指向结构体epoll_event的指针,结构体的定义如下:


typedef union epoll_data

{

void *ptr; /* Pointer to user-defind data */

int fd; /* File descriptor */

uint32_t u32; /* 32-bit integer */

uint64_t u64; /* 64-bit integer */

} epoll_data_t;


struct epoll_event

{

uint32_t events; /* epoll events(bit mask) */

epoll_data_t data; /* User data */

};


参数ev为文件描述符fd所做的设置(epoll_event)如下:


events字段是一个位掩码,它指定了我们为待检查的描述符fd上所感兴趣的事件集合;

data字段是一个联合体,当描述符fd稍后称为就绪态时,联合的成员可用来指定传回给调用进程的信息;

4、事件等待:epoll_wait()

#include <sys/epoll.h>


int epoll_wait(int epfd, struct epoll_event *evlist, int maxevents, int timeout);

1

2

3

       epoll_wait()返回epoll实例中处于就绪态的文件描述符信息。调用成功后,epoll_wait()返回数组evlist中的元素个数。如果在timeout超时间隔内没有任何文件描述符处于就绪态的话就返回0,出错时返回-1,并在errno中设定错误码以表示错误原因。

1、 第一个参数epfd是epoll_create()的返回值;

2、 第二个参数evlist所指向的结构体数组中返回的是有关就绪态文件描述符的信息,数组evlist的空间由调用者负责申请;

3、 第三个参数maxevents指定所evlist数组里包含的元素个数;

4、 第四个参数timeout用来确定epoll_wait()的阻塞行为,有如下几种:


如果timeout等于-1,调用将一直阻塞,直到兴趣列表中的文件描述符上有事件产生或者直到捕获到一个信号为止。

如果timeout等于0,执行一次非阻塞式地检查,看兴趣列表中的描述符上产生了哪个事件。

如果timeout大于0,调用将阻塞至多timeout毫秒,直到文件描述符上有事件发生,或者直到捕获到一个信号为止。

       数组evlist中,每个元素返回的都是单个就绪态文件描述符的信息。events字段返回了在该描述符上已经发生的事件掩码。data字段返回的是我们在描述符上使用epoll_ctl()注册感兴趣的事件时在ev.data中所指定的值。注意,data字段是唯一可获知同这个事件相关的文件描述符的途径。因此,当我们调用epoll_ctl()将文件描述符添加到感兴趣列表中时,应该要么将ev.date.fd设为文件描述符号,要么将ev.date.ptr指向包含文件描述符的结构体。


       当我们调用epoll_ctl()时可以在ev.events中指定的位掩码以及由epoll_wait()返回的evlist[].events中的值如下所示:


       默认情况下,一旦通过epoll_ctl()的EPOLL_CTL_ADD操作将文件描述符添加到epoll实例的兴趣列表中后,它会保持激活状态(即之后调用epoll_wait()时,会在描述符处于就绪态时通知我们)直到我们通过epoll_ctl()的EPOLL_CTL_DEL操作将其从列表中移除。


5、使用epoll()多路复用实现并发服务器

模块代码如下:

注:完整代码可参考select()实现


#define MAX_EVENTS 512

#define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0]))


if ((epollfd=epoll_create(MAX_EVENTS)) < 0)

{

printf("epoll_create() failure: %s\n", strerror(errno));

return -3;

}


//event.events = EPOLLIN|EPOLLET;

event.events = EPOLLIN;

event.data.fd = listenfd;

if (epoll_ctl(epollfd, EPOLL_CTL_ADD, listenfd, &event) < 0)

{

printf("epoll add listen socket failure: %s\n", strerror(errno));

return -4;

}

while (1)

{

/* program will blocked here */

events = epoll_wait(epollfd, event_array, MAX_EVENTS, -1);

if (events < 0)

{

printf("epoll failure: %s\n", strerror(errno));

break;

}

else if (events == 0)

{

printf("epoll get timeout\n");

continue;

}

/* rv>0 is the active events count */

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

{

if ((event_array[i].events&EPOLLERR) || (event_array[i].events&EPOLLHUP))

{

printf("epoll_wait get error on fd[%d]: %s\n", event_array[i].data.fd, strerror(errno));

epoll_ctl(epollfd, EPOLL_CTL_DEL, event_array[i].data.fd, NULL);

close(event_array[i].data.fd);

}

/* listen socket get event means new client start connect now */

if (event_array[i].data.fd == listenfd)

{

if ((connfd=accept(listenfd, (struct sockaddr *)NULL, NULL)) < 0)

{

printf("accept new client failure: %s\n", strerror(errno));

continue;

}

event.data.fd = connfd;

//event.events = EPOLLIN|EPOLLET;

event.events = EPOLLIN;

if (epoll_ctl(epollfd, EPOLL_CTL_ADD, connfd, &event) < 0)

{

printf("epoll add client socket failure: %s\n", strerror(errno));

close(event_array[i].data.fd);

continue;

}

printf("epoll add new client socket[%d] ok.\n", connfd);

}

else /* already connected client socket get data incoming */

{

if ((rv=read(event_array[i].data.fd, buf, sizeof(buf))) <= 0)

{

printf("socket[%d] read failure or get disconncet and will be removed.\n",

event_array[i].data.fd);

epoll_ctl(epollfd, EPOLL_CTL_DEL, event_array[i].data.fd, NULL);

close(event_array[i].data.fd);

continue;

}

    else

{

printf("socket[%d] read get %d bytes data\n", event_array[i].data.fd, rv);

/* convert letter from lowercase to uppercase */

for (j = 0; j < rv; j++)

{

buf[j] = toupper(buf[j]);

}

if (write(event_array[i].data.fd, buf, rv) < 0)

{

printf("socket[%d] write failure: %s\n", event_array[i].data.fd, strerror(errno));

epoll_ctl(epollfd, EPOLL_CTL_DEL, event_array[i].data.fd, NULL);

close(event_array[i].data.fd);

}

}

}

} /* for(i=0; i<rv; i++) */

} /* while(1) */



socket_server_init.c:(select、poll、epoll均使用到)


#include <stdio.h>

#include <sys/types.h> 

#include <sys/socket.h>

#include <arpa/inet.h>

#include <string.h>

#include <errno.h>


int socket_server_init(char *listen_ip, int listen_port)

{

int rv = 0;

int on = 1;

int listenfd;

struct sockaddr_in servaddr;

if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)

{

printf("Use socket() to create a TCP socket failure: %s\n", strerror(errno));

return -1;

}

/* Set socket port reuseable, fix 'Address already in use' bug when socket server restart */

setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));

memset(&servaddr, 0, sizeof(servaddr));

servaddr.sin_family = AF_INET;

servaddr.sin_port = htons(listen_port);

if (!listen_ip) /* Listen all the local IP address */

{

servaddr.sin_addr.s_addr = htonl(INADDR_ANY);

}

else /* listen the specified IP address */

{

if (inet_pton(AF_INET, listen_ip, &servaddr.sin_addr) <= 0)

{

printf("inet_pton() set listen IP address failure.\n");

rv = -2;

goto CleanUp;

}

}

if (bind(listenfd, (struct sockaddr *) &servaddr, sizeof(servaddr)) < 0)

{

printf("Use bind() to bind the TCP socket failure: %s\n", strerror(errno));

rv = -3;

goto CleanUp;

}

if (listen(listenfd, 13) < 0)

{

printf("Use bind() to bind the TCP socket failure: %s\n", strerror(errno));

rv = -4;

goto CleanUp;

}

CleanUp:

if (rv<0)

{

close(listenfd);

}

else

{

rv = listenfd;

}

return rv;

}