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