您的位置:首页 > 理论基础 > 计算机网络

Linux网络编程--单进程服务器处理多客户请求(I/O复用)

2014-03-28 19:04 253 查看
采用I/O复用可以使用单进程的服务器去处理多个客户的连接请求,而不需要为每个客户分配单独的进程或线程去专门的处理客户请求;

I/O复用的模型:



进程阻塞于select(),而select检测相应的描述符集,如果有就绪的描述符,则退出阻塞,然后进程判断就绪的描述符并作相应的处理;

下面的例子根据前面的文章:http://blog.csdn.net/hjj414/article/details/21642183中的程序修改而来;

功能:

服务器仅仅读取客服发送的数据,并显示到屏幕上;

客户端仅仅发送数据到服务器;

服务器代码:

int main(int argc, char **argv)
{
int listenfd;
struct sockaddr_in servaddr;

/*socket*/
listenfd = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == listenfd) {
perror("socket error.");
return -1;
}
/*init IP address*/
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(1234);
/*set prot reuse*/
const int onflg = 1;
if (-1 == setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &onflg, sizeof(onflg))) {
perror("set socket prot reuse error.");
return -1;
}
/*bind*/
if (-1 == bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr))) {
perror("bind error.");
return -1;
}
/*listen*/
if (-1 == listen(listenfd, 10)) {
perror("listen error.");
return -1;
}//调用listen后建立了一个监听socket,如果没有客户连接,此时监听socket的‘已完成连接队列’为空;
//accept函数将读取监听socket的已完成连接队列,如果队列为空accept将阻塞,直到队列不为空,即当
//监听socket可读的时候,accept才从退出阻塞状态;所以使用I/O复用模型,使用select函数来测试
//监听socket,当其可读时,调用accept;
serv_io_multiplex(listenfd);

return 0;
}
serv_io_multiplex:

struct cli_array_t {//记录已连接的soket描述符
int clifd[FD_SETSIZE];	//已连接的socket
struct sockaddr_in *cliad[FD_SETSIZE];//客户IP地址
int maxi; //记录clifd数组中有记录的最大下标
int maxfd; //最大的描述符
};
#define BSIZE 100
int serv_io_multiplex(int listenfd)
{
int connfd;
struct sockaddr_in cliaddr;
socklen_t clilen;

fd_set allset, rset; //allset为所有待检测的读描述符集,rset为当前检测的读描述符集
int nready, i; //nready记录当前描述符集中有多少个准备就绪
char buf[BSIZE];
ssize_t n;
struct cli_array_t cliarr;

for (i = 0; i < FD_SETSIZE; i ++) {
cliarr.clifd[i] = -1;
cliarr.cliad[i] = NULL;
}
cliarr.maxi = -1;
cliarr.maxfd = listenfd;

FD_ZERO(&allset);
FD_SET(listenfd, &allset); //将监听socket加入allset中

while (1) {
rset = allset; //更新当前检测的读描述符集
nready = select(cliarr.maxfd+1, &rset, NULL, NULL, NULL);
if (-1 == nready) {
perror("select error.");
return -1;
}
/*client connected*/
if (FD_ISSET(listenfd, &rset)) {//监听socket准备就绪
clilen = sizeof(cliaddr);
connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &clilen);
if (-1 == connfd) {
perror("accept error.");
return -1;
}
for (i = 0; i < FD_SETSIZE; i ++) { //记录已连接socket,并记录客户IP
if (cliarr.clifd[i] < 0) {
cliarr.clifd[i] = connfd;
cliarr.cliad[i] = malloc(sizeof(cliaddr));
if (NULL == cliarr.cliad[i]) {
printf("malloc error.\n");
return -1;
}
cliarr.cliad[i]->sin_addr = cliaddr.sin_addr;
cliarr.cliad[i]->sin_port = cliaddr.sin_port;
break;
}
}
FD_SET(connfd, &allset); //将已连接的socket描述符加入allset描述符集中
if (i > cliarr.maxi)
cliarr.maxi = i;
if (connfd > cliarr.maxfd)
cliarr.maxfd = connfd;

if (--nready <= 0) {	//如果没有其他的可读就绪,进入下一次检测
continue;
}
}
/*read clients*/
for (i = 0; i <= cliarr.maxi; i ++) {
if (cliarr.clifd[i] < 0){
continue;
}
if (FD_ISSET(cliarr.clifd[i], &rset)) {// 已连接的socket读准备就绪
if (0 == (n=read(cliarr.clifd[i], buf, BSIZE))) { //客户输入ctrl+D或者ctrl+C,即客户关闭
/*client close the socket*/
close(cliarr.clifd[i]);
FD_CLR(cliarr.clifd[i], &allset);
cliarr.clifd[i] = -1;
printf("<IP:%s @PORT:%d exit>\n", inet_ntoa(cliarr.cliad[i]->sin_addr), \
ntohs(cliarr.cliad[i]->sin_port), buf);
free(cliarr.cliad[i]);
cliarr.cliad[i] = NULL;
} else { //显示读取到的信息
buf
= '\0';
printf("IP:%s @PORT:%d# %s", inet_ntoa(cliarr.cliad[i]->sin_addr), \
ntohs(cliarr.cliad[i]->sin_port), buf);
}
if (--nready <= 0) //如果没有已连接的socket可读,则进入下一次检测
break;
}
}
while (cliarr.clifd[cliarr.maxi]<0 && cliarr.maxi>=0)
cliarr.maxi --;
}
}


客户端的代码:

int main(int argc, char **argv)
{
int sockfd;
struct sockaddr_in servaddr;

if (2 != argc) {
printf("usage: ./client 127.0.0.1");
return -1;
}
/*socket*/
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == sockfd) {
perror("socket error.");
return -1;
}
/*connect*/
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(1234);
inet_pton(AF_INET, argv[1], &servaddr.sin_addr);
if (-1 == connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr))) {
perror("connect error.");
return -1;
}

str_cli(stdin, sockfd);

return 0;
}

#define LSIZE 100
void str_cli(FILE *fp, int sockfd) //仅仅发送数据到服务器
{
char sendline[LSIZE], recvline[LSIZE];

while (NULL != fgets(sendline, LSIZE, fp)) {
n_write(sockfd, sendline, strlen(sendline));
}
}

运行结果:



……
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  socket 网络编程 linux io
相关文章推荐