注意问题:
(1):输入聊天信息,用fgets()而不是scanf(),scanf()遇到空格结束录入;
(2):用send()发送信息时应用strlen(),这样可避免接收并打印时乱码出现;
(3):当客户端退出时,在server()端应使用FD_CLR()清除对应的sockfd;
缺少清除的后果可能是在client端退出时,server端无限循环(出错),并显示select()的返回值为1或其它大于0的数;
(4):client端的地址结构中填充的是server端的IP,即seraddr.sin_addr.s_addr=192.... ,这可以保证server端收到client端的信息,转发后可保证client端也能收到。若写成seraddr.sin_addr.s_addr=INADDR_ANY,则server端能收到,但client端接收不到server端的转发信息。
(5):套接字描述符集的存储方式为大端序,开一个客服端,描述符集对应为11000,注意是从右向左,最右面三位分别对应标准输入,标准输出,标准错误。右面第一个1为server端创建的第一个套接字描述符3对应的位,右面第二个1为开第一个client端时server端创建的新的套接字的描述符,以此类推。
那么对应位到底是0还是1呢,分两种情况:[1]:若有新客户端打开,则右面第四位为1,反之为0;[2]:若对应的client端有数据写入套接字,则该client的套接字(server端创建的)对应位为1,反之为0。
(6)在server端和client端,while(1){}中,select()中的描述符集均为原始描述符集的复制体。因为select()之后,不活跃的文件描述符将被置空,若使用原始描述符集,则会破坏原始描述符集。造成各种问题......
以下为server.c文件:
#include <stdio.h>#include <stdlib.h>#include <string.h>#include <unistd.h>#include <sys/types.h>#include <sys/socket.h>#include <netinet/in.h>#include <arpa/inet.h>#define BUFFER_SIZE 1024#define ECHO_PORT 8080int main(){ fd_set master; fd_set read_fds; char buffer[BUFFER_SIZE]; int sockfd; int fdmax; int addrlen; int newfd; int nbytes; int ret_select; int i; int j; struct sockaddr_in seraddr; struct sockaddr_in cliaddr; FD_ZERO(&master);//置空套接字描述符集 FD_ZERO(&read_fds); sockfd=socket(AF_INET,SOCK_STREAM,0);
//解决端口冲突问题 int opt; setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(int)); bzero(&seraddr,sizeof(seraddr)); seraddr.sin_family=AF_INET; seraddr.sin_port=htons(ECHO_PORT); seraddr.sin_addr.s_addr=INADDR_ANY; //绑定套接字和地址结构
bind(sockfd,(struct sockaddr *)&seraddr,sizeof(seraddr));
//监听 listen(sockfd,10); //将套接字描述符加到描述符集里
FD_SET(sockfd,&master); fdmax=sockfd; while(1) { read_fds = master;//复制描述符集,为了保存原有描述符集 ret_select = select(fdmax+1,&read_fds,NULL,NULL,NULL);//不活跃的描述符对应位置0 for(i=0;i<=fdmax;i++) { if(FD_ISSET(i,&read_fds))//经过select()后的read_fds中对应位为1则FD_ISSET()返回1 { if(i==sockfd)//判断是否为server中的第一个套接字 { addrlen=sizeof(cliaddr); newfd=accept(sockfd,(struct sockaddr *)&cliaddr,&addrlen);//创建新的套接字 FD_SET(newfd,&master);//将新的套接字写入描述符集中 fdmax=newfd; printf("new connection from %s on socket %d/n",inet_ntoa(cliaddr.sin_addr),newfd); } else { memset(buffer,0,BUFFER_SIZE);//置空buffer nbytes = recv(i,buffer,sizeof(buffer),0);//接收信息存于buffer中 printf("from client: %s/n",buffer); if(nbytes==0) //没收到信息,即client端退出 { FD_CLR(i,&master); //将该client对应的套接字从描述符集中清除 } for(j=0;j<=fdmax;j++) //循环群发收到的信息 { if(FD_ISSET(j,&master)) { if(j!=sockfd && j!=i)//群发收到的信息但排除本身和来源端 send(j,buffer,strlen(buffer),0); } } } } } }}
以下为client.c:
#include <stdio.h>#include <stdlib.h>#include <sys/types.h>#include <sys/socket.h>#include <unistd.h>#include <netinet/in.h>#include <error.h>#include <errno.h>#include <string.h>#include <sys/wait.h>#include <sys/times.h>#include <time.h>#include <arpa/inet.h>#define RET_OK 0#define RET_ERROR -1#define BUFFER_SIZE 1024#define ECHO_PORT 8080int main(int argc,char **argv){ int sockfd; int fdmax; int ret_select; int i; int len; char buffer[BUFFER_SIZE]; fd_set read_fds;
fd_set copy_fds; struct sockaddr_in seraddr; sockfd = socket(AF_INET,SOCK_STREAM,0); int opt; setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(int)); memset(&seraddr,0,sizeof(seraddr)); seraddr.sin_family = AF_INET; seraddr.sin_port = htons(ECHO_PORT); seraddr.sin_addr.s_addr = inet_addr("192.168.1.142"); connect(sockfd,(struct sockaddr *)&seraddr,sizeof(seraddr)); FD_ZERO(&read_fds); FD_SET(0,&read_fds); FD_SET(sockfd,&read_fds); fdmax = sockfd; while(1) { copy_fds = read_fds;
ret_select = select(fdmax+1,©_fds,NULL,NULL,NULL);//返回活跃的字符数 if(ret_select == 0) { continue; } else { if(FD_ISSET(sockfd,©_fds)) //套接字中有信息,接收信息 { memset(buffer,0,BUFFER_SIZE); len = recv(sockfd,buffer,sizeof(buffer),0); if(len == 0)//收到信息为空,即server端退出 { FD_CLR(sockfd,©_fds); fdmax = 0; } else { printf("Receive message: %s/n",buffer); } } if(FD_ISSET(0,©_fds)) //从标准输入输入信息,发送到server端 { fgets(buffer,sizeof(buffer),stdin); len=send(sockfd,buffer,strlen(buffer),0); } } } close(sockfd); return 0;}