注意问题:
(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 8080
int 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 8080
int 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;
}