您的位置:首页 > 其它

I/O复用——聊天室程序

2015-03-20 19:32 141 查看
本文主要是为熟悉Linux下网络编程,而实现一个简单的网络聊天室程序。 以Poll实现I/O复用技术来同时处理网络连接和用户输入,实现多个用户同时在线群聊。

其中客户端实现两个功能:一:从标准输入读入用户数据,并将用户数据发送到服务器;二:接收服务器发送的数据,并在标准输出打印。

服务端功能为:接收客户端数据,并将客户数据发送到登录到该服务端的所有客户端(除数据发送的客户端外)。

服务端程序 chat_server:

#define _GNU_SOURCE 1
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<assert.h>
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<errno.h>
#include<fcntl.h>
#include<poll.h>

//最大用户数
#define USER_LIMIT 5
#define BUFFER_SIZE 64
#define FD_LIMIT 65535

/*客户端数据:客户端地址 写缓冲区  读缓冲区*/
struct client_data
{
struct sockaddr_in address;
char* write_buf;
char buf[BUFFER_SIZE];
};

int setnonblocking(int fd)
{
int old_opton=fcntl(fd,F_GETFL);
int new_option=old_opton | O_NONBLOCK;
fcntl(fd,F_SETFL,new_option);
return old_opton;
}

int main(int argc,char* argv[])
{
if(argc<=2)
{
printf("usage: %s ip_address port_number\n",basename(argv[0]));
return 1;
}
const char* ip=argv[1];
int port=atoi(argv[2]);

int ret=0;
struct sockaddr_in address;
bzero(&address,sizeof(address));
address.sin_family=AF_INET;
inet_pton(AF_INET,ip,&address.sin_addr);
address.sin_port=htons(port);

int listenfd=socket(PF_INET,SOCK_STREAM,0);
assert(listenfd>=0);

ret=bind(listenfd,(struct sockaddr*)&address,sizeof(address));
assert(ret!=-1);

ret=listen(listenfd,5);
printf("listen!\n");
assert(ret!=-1);

struct client_data* users=new client_data[FD_LIMIT];
struct pollfd fds[USER_LIMIT+1];

//连接的用户数
int user_counter=0;

//注册连接套结字事件
for(int i=1;i<=USER_LIMIT;++i)
{
fds[i].fd=-1;
fds[i].events=0;

}

//注册监听套结字
fds[0].fd=listenfd;
fds[0].events=POLLIN | POLLERR;
fds[0].revents=0;

//printf("while_1!\n");
while(1)
{

// printf("while_2!\n");
ret=poll(fds,user_counter+1,-1);

// printf("poll_1!\n");
if(ret<0)
{
printf("poll failed!\n");
break;
}

//printf("poll_2!\n");
for(int i=0;i<user_counter+1;++i)
{
if((fds[i].fd==listenfd) && (fds[i].revents & POLLIN))//为监听套结字,有新连接到来
{
struct sockaddr_in client_address;
socklen_t client_addrlength=sizeof(client_address);
int connfd=accept(listenfd,(struct sockaddr*)&client_address,&client_addrlength);
if(connfd<0)
{
printf("errno is: %d",errno);
continue;
}
//如果请求过多,则关闭信到来的联接
if(user_counter>=USER_LIMIT)
{
const char* info="too many users\n";
printf("%s",info);
send(connfd,info,strlen(info),0);//向连接客户端发送关闭信息i
close(connfd);
continue;
}
//对于新到来的来连接,修改fds和users数组
user_counter++;
users[connfd].address=client_address;
setnonblocking(connfd);
fds[user_counter].fd=connfd;
fds[user_counter].events=POLLIN | POLLRDHUP | POLLERR;
fds[user_counter].revents=0;
printf("comes a new user ,now have %d users\n",user_counter);
}
else if(fds[i].revents & POLLERR)//客户端错误信息
{
printf("get a error from %d\n",fds[i].fd);
//   char errors[100];
continue;

}
else if(fds[i].revents & POLLRDHUP)//客户端关闭连接
{
users[fds[i].fd]=users[fds[user_counter].fd];
close(fds[i].fd);
i--;
user_counter--;
printf("a client left \n");
}
else if(fds[i].revents & POLLIN)//连接套结字可读
{
int connfd=fds[i].fd;
memset(users[connfd].buf,'\0',BUFFER_SIZE);
ret=recv(connfd,users[connfd].buf,BUFFER_SIZE-1,0);
printf("get %d bytes of client's data %s form %d \n",ret,users[connfd].buf,connfd);

if(ret<0)
{
if(errno!=EAGAIN)
{
close(connfd);
users[fds[i].fd]=users[fds[user_counter].fd];
fds[i]=fds[user_counter];
i--;
user_counter--;
}

}
else if(ret==0)
{

}
else//成功读取数据,则同知其他客户端准备接受数据
{
for(int j =0;j<=user_counter;++j)
{
if(fds[j].fd==connfd)
{
continue;
}
fds[j].events |= ~POLLIN;
fds[j].events |= POLLOUT;
users[fds[j].fd].write_buf=users[connfd].buf;
}
}
}
else if(fds[i].revents & POLLOUT)//连接套结字可写
{
int connfd=fds[i].fd;
if(!users[connfd].write_buf)
{
continue;
}
ret=send(connfd,users[connfd].write_buf,strlen(users[connfd].write_buf),0);
users[connfd].write_buf=NULL;

fds[i].events |= ~POLLOUT;
fds[i].events |= POLLIN;
}
}

}

delete[] users;
close(listenfd);
return 0;
}<strong style="color: rgb(0, 0, 153);">
</strong>


客户端程序 chat_client:

#define _GNU_SOURCE 1
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <poll.h>
#include <fcntl.h>

#define BUFFER_SIZE 64

int main(int argc,char* argv[])
{
if(argc<=2)
{
printf("usage:%s ip_address port_number\n",basename(argv[0]));
return 1;
}
const char* ip=argv[1];
int port=atoi(argv[2]);

struct sockaddr_in server_address;
bzero(&server_address,sizeof(server_address));
server_address.sin_family=AF_INET;
inet_pton(AF_INET,ip,&server_address.sin_addr);
server_address.sin_port=htons(port);

int sockfd=socket(PF_INET,SOCK_STREAM,0);
assert(sockfd>=0);
printf("will connect!\n");
if(connect(sockfd,(struct sockaddr*)&server_address,sizeof(server_address))<0)
{
printf("connect failed!\n");
close(sockfd);
return 1;
}

struct pollfd fds[2];
fds[0].fd=0;//标准输入
fds[0].events=POLLIN;
fds[0].revents=0;
fds[1].fd=sockfd;
fds[1].events=POLLIN | POLLRDHUP;//挂起:服务器关闭连接
fds[1].revents=0;

char read_buf [BUFFER_SIZE];
int pipefd[2]; //管道用于将标准输入数据传入到套结字fd
int ret=pipe(pipefd);
assert(ret!=-1);

char getbuf[1024];
while(1)
{
ret=poll(fds,2,-1);//注册事件
if(ret<0)
{
printf("poll failed!\n");
break;
}
if(fds[1].revents & POLLRDHUP)//挂起
{
printf("server close the connect\n");
break;
}
else if(fds[1].revents & POLLIN)//服务器传来数据
{
memset(read_buf,'\0',BUFFER_SIZE);
recv(fds[1].fd,read_buf,BUFFER_SIZE-1,0);
printf("%s\n",read_buf);
}

if(fds[0].revents & POLLIN)//用户输入数据
{
// printf("stdfile_in_1 \n");
/* ret=splice(0,NULL,pipefd[1],NULL,32768,SPLICE_F_MORE | SPLICE_F_MOVE);//将标准输入导到管道的写端
assert(ret!=-1);
printf("stdfile_in_2 \n");
ret=splice(pipefd[0],NULL,sockfd,NULL,32768,SPLICE_F_MORE | SPLICE_F_MOVE); //将管道数据读到sockfd
assert(ret!=-1);
printf("stdfile_in_3 \n");*/

fgets(getbuf,1024,stdin);
send(sockfd,getbuf,strlen(getbuf),0);
}
}

close(sockfd);
return(0);
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: