照着书上写了一个C/S模式的聊天室,通过查看函数和宏源码注释加了点自己的注释
客户端:
#define _GNU_SOURCE 1 #include#include #include #include #include #include #include #include #include #include #include #define BUFFER_SIZE 64 int main(int argc,char* argv[]){ if(argc<=2){ printf("usage: %s ip_address port_numbern",basename(argv[0])); return 1; } const char* ip=argv[1]; int port=atoi(argv[2]); //创建服务地址并初始化 struct sockaddr_in server_address; // 置字节字符串前n个字节为零且包括‘ ’。 bzero(&server_address,sizeof(server_address)); //指定ipv4协议 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); if(connect(sockfd,(struct sockaddr*)&server_address,sizeof(server_address))<0){ printf("connection failedn"); close(sockfd); return 1; } pollfd fds[2]; // 注册文件描述符0(标准输入)和文件描述符sockfd上的可读事件 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]; int ret=pipe(pipefd); assert(ret != -1); while (1) { ret=poll(fds,2,-1); if(ret<0){ printf("poll failure"); break; } if(fds[1].revents & POLLRDHUP){ printf("server close the connectionn"); break; } else if(fds[1].revents & POLLIN){ memset(read_buf, ' ',BUFFER_SIZE); recv(fds[1].fd,read_buf,BUFFER_SIZE-1,0); printf("%sn",read_buf); } if(fds[0].revents & POLLIN){ // 使用splice将用户输入的数据直接写到sockfd上(零拷贝) ret=splice(0,NULL,pipefd[1],NULL,32768,SPLICE_F_MORE | SPLICE_F_MOVE); ret=splice(pipefd[0],NULL,sockfd,NULL,32768,SPLICE_F_MORE | SPLICE_F_MOVE); } } close(sockfd); return 0; }
服务器:
#define _GNU_SOURCE 1 #include#include #include #include #include #include #include #include #include #include #include #include #define USER_LIMIT 5 //最大用户数量 #define BUFFER_SIZE 64 //读缓冲区的大小 #define FD_LIMIT 65535 //文件描述符数量限制 // 客户数据:客户端socket地址、待写到客户端的数据的位置、从客户端读入的数据 struct client_data { sockaddr_in address; char* write_buf; char buf[BUFFER_SIZE]; }; int setnonblocking(int fd){ // 获取文件状态标志 int old_option=fcntl(fd,F_GETFL); int new_option=old_option | O_NONBLOCK; fcntl(fd,F_SETFL,new_option); return old_option; } int main(int argc,char* argv[]){ if(argc <= 2){ printf("usage: %s ip_address port_numbern",basename(argv[0])); return 1; } const char* ip=argv[1]; int port=atoi(argv[2]); int ret=0; struct sockaddr_in address; // 将指定内存块的前n个字节全部设置为零。 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); assert(ret!=-1); // 创建一个user数组,分配FD_LIMIE个client_data对象。可以预期:每个可能的socket连接都可以获得一个这样的对象 // 并且socket的值可以直接用来索引(作为数组的下标)socket连接对应的client_data对象, // 这是将socket和客户数据关联的简单而高效的方式 client_data* users=new client_data[FD_LIMIT]; // 尽管我们分配了足够多的client_data对象,但为了提高poll性能,仍然有必要限制用户的数量 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; while (1) { ret=poll(fds,user_counter+1,-1); if(ret<0){ printf("poll failuren"); break; } for (int i = 0; i < user_counter+1; ++i) { if((fds[i].fd==listenfd) && (fds[i].revents & POLLIN)){ // 描述Internet套接字地址的结构 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:%dn",errno); continue; } // 如果请求太多,则关闭新到的连接 if(user_counter >= USER_LIMIT){ const char* info ="too many usersn"; printf("%s",info); send(connfd,info,strlen(info),0); close(connfd); continue; } // 对于新的连接,同时修改fds和users数组。前文已经提到,users[connfd] // 对应于新连接文件描述符connnfd的客户数据 user_counter++; users[connfd].address=client_address; setnonblocking(connfd); fds[user_counter].events=POLLIN | POLLRDHUP | POLLERR; fds[user_counter].revents=0; printf("comes a new user,now have %d usersn",user_counter); } else if (fds[i].revents & POLLERR) { printf("get an error from %dn",fds[i].fd); char errors[100]; memset(errors,' ',100); socklen_t length=sizeof(errors); if (getsockopt(fds[i].fd,SOL_SOCKET,SO_ERROR,&errors,&length)<0) { printf("get socket option failedn"); } continue; } else if(fds[i].revents & POLLHUP) { //如果客户端关闭连接,则服务器也关闭对应的链接,并将用户总数减1 users[fds[i].fd]=users[fds[user_counter].fd]; close(fds[i].fd); fds[i]=fds[user_counter]; i--; user_counter--; printf("a client leftn"); } else if(fds[i].revents & POLLIN) { int connfd=fds[i].fd; memset(users[connfd].buf,' ',BUFFER_SIZE); // (1)第一个参数指定接收端套接字描述符; // (2)第二个参数指明一个缓冲区,该缓冲区用来存放recv函数接收到的数据; // (3)第三个参数指明buf的长度;(4)第四个参数一般置0。 ret=recv(connfd,users[connfd].buf,BUFFER_SIZE-1,0); printf("get %d bytes of client data %s from %dn",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 { // 如果接收到客户数据,则通知其他socket连接准备写数据 for (int j = 1; i < user_counter; ++j) { if (fds[j].fd==connfd) { continue; } fds[j].events |= ~POLLIN; fds[j].events |= POLLIN; 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]上的可读事件 fds[i].events |= ~POLLOUT; fds[i].events |= POLLIN; } } } delete[]users; close(listenfd); return 0; }
编译运行之后,感觉没啥功能,就能检测有多少客户链接,哈哈哈



