c
复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/select.h>
#define LOG(s) printf("[%s] {%s:%d} %s \n", __DATE__, __FILE__, __LINE__, s);
/*
函数功能:创建一个服务端与客户端通信的套接字
函数参数:下一个空余的文件描述符
函数返回值:成功:用于与新客户端通信的套接字 失败:-1
*/
int server_handler(int server, struct sockaddr_in saveCaddr[])
{
struct sockaddr_in addr = {0};
socklen_t asize = sizeof(addr);
int ret = -1;
if((ret = accept(server, (struct sockaddr*)&addr, &asize)) == -1)
{
LOG("accept error");
perror("accept error");
return -1;
}
printf("[%s/%d] client%d 已连接\n", inet_ntoa(addr.sin_addr), ntohs(addr.sin_port), ret);
saveCaddr[ret] = addr;
return ret;
}
/*
函数功能:接收客户端发送的数据
函数参数:客户端的文件描述符
函数返回值:成功:返回成功读取到的字节数 失败:-1
*/
int client_handler(int client, struct sockaddr_in* saveCaddr)
{
char buf[128] = {0};
bzero(buf, sizeof(buf));
//sizeof(buf)-1为了给字符串补'\0'需要留一个位置
int ret = read(client, buf, sizeof(buf)-1);
if(ret == 0)
{
printf("[%s/%d] client%d 已下线\n", inet_ntoa(saveCaddr[client].sin_addr), ntohs(saveCaddr[client].sin_port), client);
ret = -1;
}
else if(ret > 0)
{
//read不会自动补0,需要在结尾处给字符串补'\0'
buf[ret] = 0;
printf("[%s/%d] client%d:%s\n", inet_ntoa(saveCaddr[client].sin_addr), ntohs(saveCaddr[client].sin_port), client, buf);
//客户端输入quit则断开与服务器连接
if(strcmp(buf, "quit") != 0)
{
//输出读取到的数据
ret = write(client, buf, ret);
}
else
{
printf("[%s/%d] client%d 已下线\n", inet_ntoa(saveCaddr[client].sin_addr), ntohs(saveCaddr[client].sin_port), client);
ret = -1;
}
}
return ret;
}
int main(char argc, char* argv[])
{
//申请服务器资源
int server = 0;
struct sockaddr_in saddr = {0};
//记录文件描述符的范围
int max = 0;
//接收select函数返回值
int num = 0;
//标记感兴趣的文件描述符
fd_set reads = {0};
fd_set temps = {0};
//申请服务端套接字
server = socket(PF_INET, SOCK_STREAM, 0);
if(server == -1)
{
puts("server socket error");
return -1;
}
int reuse = 1;
if(setsockopt(server, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) < 0)
{
LOG("setsockopt");
return -1;
}
//使用IPv4连接,需要与套接字的第一个参数对应
saddr.sin_family = AF_INET;
//INADDR_ANY->"0.0.0.0"接受所有IP地址连接
saddr.sin_addr.s_addr = htonl(INADDR_ANY);
//监听端口号
saddr.sin_port = htons(8899);
//绑定端口
if(bind(server, (struct sockaddr*)&saddr, sizeof(saddr)) == -1)
{
puts("server bind error");
return -1;
}
//监听端口,队列为5
if(listen(server, 5) == -1)
{
puts("server listen error");
return -1;
}
puts("server start success");
//将reads的所有位置置0
FD_ZERO(&reads);
//监听reads[0,max]号位置的事件
FD_SET(server, &reads);
FD_SET(0, &reads);
//更新max,将最新一个空余的文件符记录
max = server;
//备份连接成功的客户端地址信息
struct sockaddr_in saveCaddr[1024];
while(1)
{
//selet会修改参数内容,所以需要拷贝reads的内容到temps去使用
temps = reads;
//对[0,max]个文件描述符的读事件进行轮询
num = select(max+1, &temps, NULL, NULL, NULL);
if(num > 0)
{
for(int i = 0; i <= max; i++)
{
if(FD_ISSET(i, &temps) == 0)
continue;
//判断是否有新的客户端连接
if(0 == i)
{
puts("键盘事件");
char buf[128] = "";
bzero(buf, sizeof(buf));
fgets(buf, sizeof(buf), stdin);
buf[strlen(buf) - 1] = 0;
printf("input: %s\n",buf);
}
else if(server == i)
{
puts("连接事件");
//服务端创建一个用于与客户端通信的套接字
int client = server_handler(server, saveCaddr);
if(client > -1)
{
//将该套接字放入监听队列
FD_SET(client, &reads);
//判断最大文件描述符是否更新
max = (client > max)? client : max;
}
}
//判断是否存在文件描述符
else
{
puts("通信事件");
int r = client_handler(i, saveCaddr);
if(r == -1)
{
FD_CLR(i, &reads);
close(i);
while(FD_ISSET(max, &reads) == 0 && max-- >= 0);
}
}
}
}
}
close(server);
return 0;
}