写惯客户端的人总觉得服务器开发高深而又神秘。纵观服务器编程,大概可分为异步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; } |