您的位置:首页 > 其它

I/O多路复用(一):用select实现的多客户聊天的服务器

2012-08-23 20:57 441 查看
以下例子为用select实现的多客户聊天的服务器,客户端成功连接服务器之后,可以和其他客户进行群聊。

服务器代码:

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <strings.h>
#include <arpa/inet.h>
#include <string.h>
#include <sys/ioctl.h>
#include <fcntl.h>

void main(void)
{
struct sockaddr_in ser;
struct sockaddr_in cli;
int ser_fd;
int cli_len = sizeof(cli);

bzero(&ser, sizeof(ser));
bzero(&cli, sizeof(cli));

ser.sin_family = AF_INET;
ser.sin_port = htons(9678);
ser.sin_addr.s_addr = htonl(INADDR_ANY);

ser_fd = socket(AF_INET, SOCK_STREAM, 0);

bind(ser_fd, (struct sockaddr*)&ser, sizeof(ser));

listen(ser_fd, 5);

int i;
int client[FD_SETSIZE];
for(i = 0; i < FD_SETSIZE; i++){  //初始化客户端数组,数组用于存放客户端socket描述符
client[i] = -1;
}

fd_set rd;
fd_set allset;

FD_ZERO(&allset);
FD_ZERO(&rd);
FD_SET(ser_fd, &allset);

int res;
int maxfd = ser_fd; //最大的描述符
int confd;   //连接后返回的socket描述符
int maxi = 0;
int cli_num = 0; //处于连接状态的客户端总数
int self = 0;
char buf[1024];

while(1){
rd = allset;  //见本文末尾红色提示
res = select(maxfd + 1, &rd, NULL, NULL, NULL);
if(res < 0)
perror("select failed");
else if(0 == res)
printf("select time out\n");
else{
if(FD_ISSET(ser_fd, &rd)){
confd = accept(ser_fd, (struct sockaddr*)&cli, &cli_len);
if(-1 == confd){
perror("accept failed");
continue;
}

for(i = 0; i < FD_SETSIZE; i++){
if(client[i] < 0){
client[i] = confd;
break;
}
}
if(i > maxi)
maxi = i;
if(maxfd < confd ) //获取最大的描述符
maxfd = confd;

FD_SET(confd, &allset);
cli_num++;

printf("%d have connected.\n", confd);
printf("Now,the client num is %d\n", cli_num);
}

for(i = 0; i <= maxi; i++){
if(client[i] < 0)
continue;

if(FD_ISSET(client[i], &rd)){
res = read(client[i], buf, sizeof(buf));
if(res <= 0){ //读取失败,去除该套接字
perror("read failed");
FD_CLR(client[i], &allset);
cli_num--;
printf("client %d is out of connect\n", client[i]);
close(client[i]);
client[i] = -1;
}else if(res > 0){
    self = client[i];
printf("from client %d : %s\n", client[i], buf);
for(i = 0; i <= maxi; i++){ //实现群发
if(client[i] < 0)
continue;
    if(self == client[i])
     continue;
if(0 >= send(client[i], buf, sizeof(buf), 0))
perror("send failed\n");
}
memset(buf, 0, sizeof(buf));
}
}
}
}
}
}

客户端代码:

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

void main(void)
{
struct sockaddr_in local;
struct hostent *he = gethostbyname("localhost");

bzero(&local, sizeof(local));

local.sin_family = AF_INET;
local.sin_port = htons(9678);
local.sin_addr = *(struct in_addr*)he->h_addr;

int fd = socket(AF_INET, SOCK_STREAM, 0);
int res = connect(fd, (struct sockaddr*)&local, sizeof(local));
if(res < 0){
perror("connect failed");
exit(1);
}
if(res == 0)
printf("connect\n");

fd_set rd;
fd_set test;
FD_ZERO(&rd);
FD_ZERO(&test);
FD_SET(0, &rd);
FD_SET(fd, &rd);
char buf[1024] = {0};

while(1){
test = rd;      //见本文末尾红色提示
res = select(fd + 1, &test, NULL, NULL, NULL);
if(res < 0)
perror("select failed");
else if(0 == res)
perror("select time out");
else{
memset(buf, 0, sizeof(buf));
if(FD_ISSET(0, &test)){
fgets(buf, sizeof(buf), stdin);
if(0 >= send(fd, buf, sizeof(buf), 0)){
perror("send failed\n");
break;
}

}
if(FD_ISSET(fd, &test)){
if(0 >= recv(fd, buf, sizeof(buf), 0)){
perror("recv failed\n");
break;
}
printf("recv : %s\n", buf);
}
}
}
FD_CLR(0, &rd);
FD_CLR(fd, &rd);
close(fd);
}

注意一点:调用select()时,由内核根据IO状态修改fd_set的内容,由此来通知执行了select()的进程哪一socket或文件可读。

select系列的FD函数同样会修改fd_set的内容。

所以循环调用select时需用一个中间变量来储存初始fd_set的值。

select的3个缺点:1 连接数受限 2 查找配对速度慢 3数据由内核拷贝到用户态。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: