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

来源:互联网 发布:gis是什么软件 编辑:程序博客网 时间:2024/06/08 03:13

本文主要是为熟悉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 64int 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]; //管道用于将标准输入数据传入到套结字fdint 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);}


0 0
原创粉丝点击