通过select函数来编写socket通信。
原理:集合fd_set是用来存储文件描述符。每次调用select函数时,将fd集合从用户态复制拷贝到内核态。select函数运行完后,然后遍历fd集合,查看哪些描述符已经就绪,对已经就绪的fd进行相应的读写操作。
server端:
#include <iostream>
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/select.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <sys/shm.h>
using namespace std;
#define port 5875
#define IP "127.0.0.1"
#define MAX_FD_SIZE 1024
struct server_context_st
{
int cli_cnt;//fd size
int clifds[MAX_FD_SIZE];//client fd
int maxFd;
fd_set allfds;//all fd(include client and server)
};
static server_context_st * s_srv_ctx = NULL;
int main()
{
//init
s_srv_ctx = (server_context_st*)malloc(sizeof(server_context_st));
if (s_srv_ctx == NULL)
{
cout << "malloc error" << endl;
return 0;
}
memset(s_srv_ctx, 0, sizeof(s_srv_ctx));
for (int i = 0; i < MAX_FD_SIZE; i++)
{
s_srv_ctx->clifds[i] = -1;
}
//创建服务文件描述符
int server_fd;
server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd == -1)
{
perror("create server fd error");
return 0;
}
struct sockaddr_in sockAttr;
sockAttr.sin_family = AF_INET;
sockAttr.sin_port = htons(port);
sockAttr.sin_addr.s_addr = htonl(INADDR_ANY);
if (bind(server_fd, (struct sockaddr*)&sockAttr, sizeof(sockAttr)) == -1)
{
perror("bind error");
return 0;
}
if (listen(server_fd, 1024) == -1)
{
perror("bind errpr");
return 0;
}
struct timeval tv;
fd_set *readfds = &s_srv_ctx->allfds;
while (1)
{
/*
void FD_ZERO(fd_set *fdset);//清空集合
*/
FD_ZERO(readfds);
/*
void FD_SET(int fd, fd_set *fdset); //将一个给定的文件描述符加入集合之中
*/
FD_SET(server_fd, readfds);
s_srv_ctx->maxFd = server_fd;
tv.tv_sec = 20;
tv.tv_usec = 0;
/*
拷贝集合到内核态中
*/
for (int i = 0; i < MAX_FD_SIZE; i++)
{
int clifd = s_srv_ctx->clifds[i];
FD_SET(clifd, readfds);
s_srv_ctx->maxFd = (clifd > s_srv_ctx->maxFd ? clifd : s_srv_ctx->maxFd);
}
/*
int select (int __nfds, fd_set *__restrict __readfds,fd_set *__restrict __writefds,
fd_set *__restrict __exceptfds,struct timeval *__restrict __timeout);
__nfds:集合数量+1。
__readfds:可读文件描述符的集合即接收信息
__writefds:可写文件描述符的集合发送信息
__exceptfds:异常文件描述符
__timeout:表示在一段时间内,需要监视的文件描述符没有事件发生则放回,返回值为0
*/
//只监控可读的文件描述符
int retval = select(s_srv_ctx->maxFd + 1, readfds, NULL, NULL, &tv);
if (retval == -1)
{
cout << "select error" << endl;
return 0;
}
if (retval == 0)
{
cout << "select time out" << endl;
continue ;
}
/*
int FD_ISSET(int fd, fd_set *fdset); // 检查集合中指定的文件描述符是否可以读写
*/
//检测服务fd是否可读文件描述符集合中,可读表面有新的客户端连接请求
if (FD_ISSET(server_fd, readfds))
{
//create new socket
struct sockaddr_in cliarrt;
socklen_t len = sizeof(cliarrt);
int conn = -1;
while (conn == -1)
{
conn = accept(server_fd, (struct sockaddr*)&cliarrt, &len);
if (conn == -1)
{
perror("accept client error");
}
}
//将检测的client文件描述符,放到数组中
for (int i = 0; i < MAX_FD_SIZE; i++)
{
if (s_srv_ctx->clifds[i] < 0)
{
s_srv_ctx->clifds[i] = conn;
s_srv_ctx->cli_cnt++;
break;
}
}
}
//轮询客户端fd数组
char buf[1024] = {0};
for (int i = 0; i < s_srv_ctx->cli_cnt; i++)
{
int cl = s_srv_ctx->clifds[i];
if (cl < 0)
{
continue;
}
//存在可读集合中,读取客户端发送的信息
if (FD_ISSET(cl, readfds))
{
int readCt = read(cl, buf, 1024);
if (readCt <= 0)
{
FD_CLR(cl, &s_srv_ctx->allfds);
close(cl);
s_srv_ctx->clifds[i] = -1;
continue;
}
}
//返回信息给客户端
string wrtBuf = "hello,client " + to_string(i);
write(cl, wrtBuf.c_str(), wrtBuf.length() + 1);
}
}
return 0;
}
参考:
http://www.cnblogs.com/Anker/archive/2013/08/14/3258674.html