码迷,mamicode.com
首页 > 其他好文 > 详细

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

时间:2015-03-20 22:08:22      阅读:186      评论:0      收藏:0      [点我收藏+]

标签:服务器   socket   linux   网络编程   

本文主要是为熟悉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);
}


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

标签:服务器   socket   linux   网络编程   

原文地址:http://blog.csdn.net/yuyixinye/article/details/44496203

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!