#include<iostream>
#include<cstring>
#include<cstdlib>
#include<unistd.h>
#include<arpa/inet.h>
#include<cctype>
#include<sys/select.h>
using namespace std;
const int PORT = 7777;
int main()
{
int server_socket,client_socket;
//用来存储需要监听的文件描述符
int client_fd[FD_SETSIZE];
//最大的文件描述符,select的返回结果
int max_fd,select_num;
//每次监听的集合,总的集合(因为select会改变参数,所以需要备份)
fd_set res_set,all_set;
sockaddr_in server_addr,client_addr;
socklen_t server_addrlen;
//maxi为监听集合中最后一个描述符的下标
int i,maxi=-1;
char client_ip[BUFSIZ],str[BUFSIZ];
server_socket = socket(AF_INET, SOCK_STREAM, 0);
if(-1 == server_socket)
{
perror("create server socket error:");
exit(1);
}
bzero(&server_addr,sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(PORT);
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
if(-1 == bind(server_socket, (struct sockaddr *)&server_addr, sizeof(server_addr)))
{
perror("bind error:");
exit(1);
}
if(-1 == listen(server_socket, 32))
{
perror("listen error:");
exit(1);
}
max_fd = server_socket;
for(int k=0;k<FD_SETSIZE;k++)
{
client_fd[k] = -1;
}
FD_ZERO(&all_set);
FD_SET(server_socket, &all_set);
cout<<"waiting for clients\n";
while(1)
{
res_set = all_set;
select_num = select(max_fd+1, &res_set, NULL, NULL, NULL);
if(select_num < 0)
{
perror("select error:");
exit(1);
}
/*
当server_socket出现在了res_set中表示,有新的客户端请求连接
把新的客户端的文件描述符放到监听集合中
*/
if(FD_ISSET(server_socket, &res_set))
{
client_socket = accept(server_socket,(struct sockaddr *)&client_addr,&server_addrlen);
cout<<"received from:"<<inet_ntop(AF_INET, &client_addr.sin_family, client_ip, BUFSIZ)<<" port:"
<<ntohs(client_addr.sin_port)<<endl;
for(i=0;i<FD_SETSIZE;i++)
{
if(client_fd[i] < 0)
{
client_fd[i] = client_socket;
break;
}
}
if(FD_SETSIZE == i)
{
cout<<"too mant clients";
exit(1);
}
FD_SET(client_socket, &all_set);
if(client_socket > max_fd)
max_fd = client_socket;
if(i > maxi)
maxi = i;
if(--select_num == 0)
continue;
}
/*
下面这个循环用来查看在client_fd中保存的文件描述符,是否有读事件发生
如果有的话,这里简单处理,转为大写再发回去
*/
for(i=0;i<=maxi;i++)
{
if(client_fd[i] < 0)
continue;
if(FD_ISSET(client_fd[i], &res_set))
{
int read_len;
if((read_len = read(client_fd[i], str, sizeof(str))) == 0)
{
cout<<"client close\n";
close(client_fd[i]);
FD_CLR(client_fd[i], &all_set);
client_fd[i] = -1;
}else if(read_len > 0)
{
cout<<"have read sth from client\n";
for(int j=0;j<read_len;j++)
{
str[j] = toupper(str[j]);
}
int wr_ret = write(client_fd[i], str, read_len);
if(-1 == wr_ret)
{
perror("write error:");
exit(1);
}
}
if(--select_num == 0)
break;
}
}
}
close(server_socket);
return 0;
}
这里引出一些问题
1.如果客户端发送的数据长度,大于read函数中的第三个参数怎么办?
经过测试,假设用户发的数据长度为4,read的第三个参数为2,所以会每次从缓冲区中读取2个字节,然后由于缓冲区中的数据没有被完全消化掉,所以这个客户端对应的文件描述符还是会有读事件产生,整个操作执行三次,知道清空读缓冲区,才不会有读事件发生
2.总的思想就是,把服务器的socket和客户端链接进来的socket放在res_set中,看是否有读事件发生,如果服务器端的socket有读事件发生,则表示有新的客户端链接进来了,加入到all_set中,然后看链接进来的客户端socket是否有读事件发生,这些socket存储在client_fd中,如果有的话,进行一下简单操作。