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

本文主要是为熟悉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);
}
时间: 2024-10-06 02:54:10

I/O复用——聊天室程序的相关文章

ASP.NET 使用application和session对象写的简单聊天室程序

ASP.Net中有两个重要的对象,一个是application对象,一个是session对象. Application:记录应用程序参数的对象,该对象用于共享应用程序级信息. Session:记录浏览器端的变量对象,用来存储跨网页程序程序的变量或者对象. 说实话,写了快一年的asp.net,application对象还真没怎么用过.看了看书,根据这两个对象的特性写了一个简单的聊天室程序.真的是非常的简陋. 我的思路是,有两个页面Default页和ChatRoom页,页面布局如图: Default

java socket控制台版本聊天室程序源码下载

原文:java socket控制台版本聊天室程序源码下载 代码下载地址:http://www.zuidaima.com/share/1550463257578496.htm java socket控制台版本聊天室程序源码下载,学习的时候写的,适合学习java基础 java网络编程基础用 标签: java socket 控制台 聊天室 源码话题: 网络编程 java socket控制台版本聊天室程序源码下载,布布扣,bubuko.com

15.基于UDP协议的聊天室程序

使用UDP协议完成一个聊天室程序的小项目,大部分代码都有注释,一看就能看到的. 实现的功能:               (1)查看/显示已经登陆的用户信息               (2)向已登陆的用户发送消息               (3)输出错误消息,给予提示               (4)退出 共有三个文件: chat_public.h #ifndef _CHAT_PUB_H_ #define _CHAT_PUB_H_ //chat_public.h #include <lis

Java网络编程 - 基于UDP协议 实现简单的聊天室程序

最近比较闲,一直在抽空回顾一些Java方面的技术应用. 今天没什么事做,基于UDP协议,写了一个非常简单的聊天室程序. 现在的工作,很少用到socket,也算是对Java网络编程方面的一个简单回忆. 先看一下效果: 实现的效果可以说是非常非常简单,但还是可以简单的看到一个实现原理. "聊天室001"的用户,小红和小绿相互聊了两句,"聊天室002"的小黑无人理会,在一旁寂寞着. 看一下代码实现: 1.首先是消息服务器的实现,功能很简单: 将客户端的信息(进入了哪一个聊

Erlang 聊天室程序

Erlang 聊天室程序( 一) Erlang 聊天室程序(二) 客户端的退出 Erlang 聊天室程序(三) 数据交换格式---json的decode Erlang 聊天室程序(四) 数据交换格式---json的encode Erlang 聊天室程序(五) 设置客户端信息 Erlang 聊天室程序(七) 获取在线用户 Erlang 聊天室程序(八) 主题房间---supervisor 的使用 Erlang 聊天室程序(九) 主题房间2 ---房间信息管理 Erlang 聊天室程序(十) 主题房

构建有多个房间的聊天室程序

1. 程序概览 用户可以在一个简单的表单中输入消息,相互聊天.消息输入后会发送给同一个聊天室内的其他所有用户. 进入聊天室后,程序会自动给用户分配一个昵称,但他们可以用聊天命令修改自己的昵称,如图2-2所示.聊天命令以斜杠(/)开头. 同样,用户也可以输入命令创建新的聊天室(或加入已有的聊天室) ,如图2-3所示.在加入或创建聊天室时,新聊天室的名称会出现在聊天程序顶端的水平条上,也会出现在聊天消息区域右侧的可用房间列表中. 在用户换到新房间后,系统会确认这一变化,如图2-4所示. 2. 程序需

&#8203;下面为大家介绍一个运用自己电脑当成服务器,然后开发一个简单的php聊天室程序的方法:

上一期我们说过B/S技术开发聊天有什么优点,这一期我们就来简单的说说用C/S技术开发又有什么特点? 一.稳定性和灵活性:用C/S技术可以将应用和服务进行分离.二.安全性:C/S对应是的是结构模式,一般只适用于局域网,所以安全性比较好.三.速度快:客户端与服务器端是直接连接的,中间没有经过别的环节,所以响应速度非常快.四.升级维护复杂:如果软件需要升级维护,那么每一台客户的机子都要进行相应的升级维护服务,那么这个过程肯定是比较繁琐的.综上所述,对于不同的聊天室需要采用不同的开发技术,但是国内目前很

基于AJAX的长轮询(long-polling)方式实现简单的聊天室程序

原理: 可以看:http://yiminghe.javaeye.com/blog/294781 AJAX 的出现使得 JavaScript 可以调用 XMLHttpRequest 对象发出 HTTP 请求,JavaScript 响应处理函数根据服务器返回的信息对 HTML 页面的显示进行更新.使用 AJAX 实现“服务器推”与传统的 AJAX 应用不同之处在于: 服务器端会阻塞请求直到有数据传递或超时才返回. 客户端 JavaScript 响应处理函数会在处理完服务器返回的信息后,再次发出请求,

基于epoll的聊天室程序

epoll相对于poll和select这两个多路复用的I/O模型更加的高效.epoll的函数很简单,麻烦的地方在于水平触发和边沿触发. 用张图来说明下 ET(边沿)只是在状态反转时触发,比如从不可读到可读.而LT(水平)就是如果可读,就会一直触发.所以在使用ET的时候要做一些额外的处理,比如可读的,一直把缓冲区读完,进入不可读状态,下次来数据才会触发. 下面贴出代码,只是一个简单的练习的例子socketheads.h C++ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15