<pre name="code" class="cpp">/*******************************************************************************************
使用select函数可以以非阻塞的方式和多个socket通信。程序只是演示select函数的使用,功能非常简单,即使某个连接关闭以后也不会修改当前连接数,连接数达到最大值后会终止程序。
1. 程序使用了一个数组fd_A,通信开始后把需要通信的多个socket描述符都放入此数组。
2. 首先生成一个叫sock_fd的socket描述符,用于监听端口。
3. 将sock_fd和数组fd_A中不为0的描述符放入select将检查的集合fdsr。
4. 处理fdsr中可以接收数据的连接。如果是sock_fd,表明有新连接加入,将新加入连接的socket描述符放置到fd_A。
*******************************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define MYPORT 1234 // the port users will be connecting to
#define BACKLOG 5 // how many pending connections queue will hold
#define BUF_SIZE 200
int fd_A[BACKLOG]; // accepted connection fd
int conn_amount; // current connection amount
void showclient()
{
int i;
printf("client amount: %d\n", conn_amount);
for (i = 0; i < BACKLOG; i++) {
printf("[%d]:%d ", i, fd_A[i]);
}
printf("\n\n");
}
int main(void)
{
int sock_fd, new_fd; // listen on sock_fd, new connection on new_fd
struct sockaddr_in server_addr; // server address information
struct sockaddr_in client_addr; // connector's address information
socklen_t sin_size;
int yes = 1;
char buf[BUF_SIZE];
int ret;
int i;
if ((sock_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
perror("socket");
exit(1);
}
if (setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1) {
perror("setsockopt");
exit(1);
}
server_addr.sin_family = AF_INET; // host byte order
server_addr.sin_port = htons(MYPORT); // short, network byte order
server_addr.sin_addr.s_addr = INADDR_ANY; // automatically fill with my IP
memset(server_addr.sin_zero, '\0', sizeof(server_addr.sin_zero));
if (bind(sock_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
perror("bind");
exit(1);
}
if (listen(sock_fd, BACKLOG) == -1) {
perror("listen");
exit(1);
}
printf("listen port %d\n", MYPORT);
fd_set fdsr;
int maxsock;
struct timeval tv;
conn_amount = 0;
sin_size = sizeof(client_addr);
maxsock = sock_fd;
while (1) {
// initialize file descriptor set
FD_ZERO(&fdsr);
FD_SET(sock_fd, &fdsr);
// timeout setting
tv.tv_sec = 30;
tv.tv_usec = 0;
// add active connection to fd set
for (i = 0; i < BACKLOG; i++) {
if (fd_A[i] != 0) {
FD_SET(fd_A[i], &fdsr);
}
}
ret = select(maxsock + 1, &fdsr, NULL, NULL, &tv);
if (ret < 0) {
perror("select");
break;
} else if (ret == 0) {
printf("timeout\n");
continue;
}
// check every fd in the set
for (i = 0; i < conn_amount; i++) {
if (FD_ISSET(fd_A[i], &fdsr)) {
ret = recv(fd_A[i], buf, sizeof(buf), 0);
if (ret <= 0) { // client close
printf("client[%d] close\n", i);
close(fd_A[i]);
FD_CLR(fd_A[i], &fdsr);
fd_A[i] = 0;
} else { // receive data
if (ret < BUF_SIZE)
memset(&buf[ret], '\0', 1);
printf("client[%d] send:%s\n", i, buf);
}
}
}
// check whether a new connection comes
if (FD_ISSET(sock_fd, &fdsr)) {
new_fd = accept(sock_fd, (struct sockaddr *)&client_addr, &sin_size);
if (new_fd <= 0) {
perror("accept");
continue;
}
// add to fd queue
if (conn_amount < BACKLOG) {
fd_A[conn_amount++] = new_fd;
printf("new connection client[%d] %s:%d\n", conn_amount,
inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
if (new_fd > maxsock)
maxsock = new_fd;
}
else {
printf("max connections arrive, exit\n");
send(new_fd, "bye", 4, 0);
close(new_fd);
break;
}
}
showclient();
}
// close other connections
for (i = 0; i < BACKLOG; i++) {
if (fd_A[i] != 0) {
close(fd_A[i]);
}
}
exit(0);
}
#include <unistd.h>
#include <sys/types.h> /* basic system data types */
#include <sys/socket.h> /* basic socket definitions */
#include <netinet/in.h> /* sockaddr_in{} and other Internet defns */
#include <arpa/inet.h> /* inet(3) functions */
#include <stdlib.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <poll.h> /* poll function */
#include <limits.h>
#define MAXLINE 10240
#ifndef OPEN_MAX
#define OPEN_MAX 40960
#endif
void handle(struct pollfd* clients, int maxClient, int readyClient);
int main(int argc, char **argv)
{
int servPort = 6888;
int listenq = 1024;
int listenfd, connfd;
struct pollfd clients[OPEN_MAX];
int maxi;
socklen_t socklen = sizeof(struct sockaddr_in);
struct sockaddr_in cliaddr, servaddr;
char buf[MAXLINE];
int nready;
bzero(&servaddr, socklen);
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(servPort);
listenfd = socket(AF_INET, SOCK_STREAM, 0);
if (listenfd < 0) {
perror("socket error");
}
int opt = 1;
if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) {
perror("setsockopt error");
}
if(bind(listenfd, (struct sockaddr *) &servaddr, socklen) == -1) {
perror("bind error");
exit(-1);
}
if (listen(listenfd, listenq) < 0) {
perror("listen error");
}
clients[0].fd = listenfd;
clients[0].events = POLLIN;
int i;
for (i = 1; i< OPEN_MAX; i++)
clients[i].fd = -1;
maxi = listenfd + 1;
printf("pollechoserver startup, listen on port:%d\n", servPort);
printf("max connection is %d\n", OPEN_MAX);
for ( ; ; ) {
nready = poll(clients, maxi + 1, -1);
//printf("nready is %d\n", nready);
if (nready == -1) {
perror("poll error");
}
if (clients[0].revents & POLLIN) {
connfd = accept(listenfd, (struct sockaddr *) &cliaddr, &socklen);
sprintf(buf, "accept form %s:%d\n", inet_ntoa(cliaddr.sin_addr), cliaddr.sin_port);
printf(buf, "");
for (i = 0; i < OPEN_MAX; i++) {
if (clients[i].fd == -1) {
clients[i].fd = connfd;
clients[i].events = POLLIN;
break;
}
}
if (i == OPEN_MAX) {
fprintf(stderr, "too many connection, more than %d\n", OPEN_MAX);
close(connfd);
continue;
}
if (i > maxi)
maxi = i;
--nready;
}
handle(clients, maxi, nready);
}
}
void handle(struct pollfd* clients, int maxClient, int nready) {
int connfd;
int i, nread;
char buf[MAXLINE];
if (nready == 0)
return;
for (i = 1; i< maxClient; i++) {
connfd = clients[i].fd;
if (connfd == -1)
continue;
if (clients[i].revents & (POLLIN | POLLERR)) {
nread = read(connfd, buf, MAXLINE);//读取客户端socket流
if (nread < 0) {
perror("read error");
close(connfd);
clients[i].fd = -1;
continue;
}
if (nread == 0) {
printf("client close the connection");
close(connfd);
clients[i].fd = -1;
continue;
}
write(connfd, buf, nread);//响应客户端
if (--nready <= 0)//没有连接需要处理,退出循环
break;
}
}
}
/*该程序一个简单的聊天室程序,用Linux C++写的,服务器主要是用epoll模型实现.程序共包含2个头文件和3个cpp文件。其中3个cpp文件中,每一个cpp文件都是一个应用程序,server.cpp:服务器程序,client.cpp:单个客户端程序,tester.cpp:模拟高并发,开启10000个客户端去连服务器。*/
//******************************************************************************************
//utils.h头文件,就包含一个设置socket为不阻塞函数,如下:
int setnonblocking(int sockfd)
{
CHK(fcntl(sockfd, F_SETFL, fcntl(sockfd, F_GETFD, 0)|O_NONBLOCK));
return 0;
}
//******************************************************************************************
//local.h头文件,一些常量的定义和函数的声明,如下:
#define BUF_SIZE 1024 //默认缓冲区
#define SERVER_PORT 44444 //监听端口
#define SERVER_HOST "192.168.34.15" //服务器IP地址
#define EPOLL_RUN_TIMEOUT -1 //epoll的超时时间
#define EPOLL_SIZE 10000 //epoll监听的客户端的最大数目
#define STR_WELCOME "Welcome to seChat! You ID is: Client #%d"
#define STR_MESSAGE "Client #%d>> %s"
#define STR_NOONE_CONNECTED "Noone connected to server except you!"
#define CMD_EXIT "EXIT"
//两个有用的宏定义:检查和赋值并且检测
#define CHK(eval) if(eval < 0){perror("eval"); exit(-1);}
#define CHK2(res, eval) if((res = eval) < 0){perror("eval"); exit(-1);}
//==========================================================================================
//函数名: setnonblocking
//函数描述: 设置socket为不阻塞
//输入: [in] sockfd socket标示符
//输出: 无
//返回: 0
//==========================================================================================
int setnonblocking(int sockfd);
//==========================================================================================
//函数名: handle_message
//函数描述: 处理每个客户端socket
//输入: [in] new_fd socket标示符
//输出: 无
//返回: 返回从客户端接受的数据的长度
//==========================================================================================
int handle_message(int new_fd);
//******************************************************************************************
//server.cpp文件,epoll模型就在这里实现,如下:
#include "local.h"
#include "utils.h"
using namespace std;
// 存放客户端socket描述符的list
list<int> clients_list;
int main(int argc, char *argv[])
{
int listener; //监听socket
struct sockaddr_in addr, their_addr;
addr.sin_family = PF_INET;
addr.sin_port = htons(SERVER_PORT);
addr.sin_addr.s_addr = inet_addr(SERVER_HOST);
socklen_t socklen;
socklen = sizeof(struct sockaddr_in);
static struct epoll_event ev, events[EPOLL_SIZE];
ev.events = EPOLLIN | EPOLLET; //对读感兴趣,边沿触发
char message[BUF_SIZE];
int epfd; //epoll描述符
clock_t tStart; //计算程序运行时间
int client, res, epoll_events_count;
CHK2(listener, socket(PF_INET, SOCK_STREAM, 0)); //初始化监听socket
setnonblocking(listener); //设置监听socket为不阻塞
CHK(bind(listener, (struct sockaddr *)&addr, sizeof(addr))); //绑定监听socket
CHK(listen(listener, 1)); //设置监听
CHK2(epfd,epoll_create(EPOLL_SIZE)); //创建一个epoll描述符,并将监听socket加入epoll
ev.data.fd = listener;
CHK(epoll_ctl(epfd, EPOLL_CTL_ADD, listener, &ev));
while(1)
{
CHK2(epoll_events_count,epoll_wait(epfd, events, EPOLL_SIZE, EPOLL_RUN_TIMEOUT));
tStart = clock();
for(int i = 0; i < epoll_events_count ; i++)
{
if(events[i].data.fd == listener) //新的连接到来,将连接添加到epoll中,并发送欢迎消息
{
CHK2(client,accept(listener, (struct sockaddr *) &their_addr, &socklen));
setnonblocking(client);
ev.data.fd = client;
CHK(epoll_ctl(epfd, EPOLL_CTL_ADD, client, &ev));
clients_list.push_back(client); // 添加新的客户端到list
bzero(message, BUF_SIZE);
res = sprintf(message, STR_WELCOME, client);
CHK2(res, send(client, message, BUF_SIZE, 0));
}else
{
CHK2(res,handle_message(events[i].data.fd)); //注意:这里并没有调用epoll_ctl重设置socket的事件类型,但还是可以继续收到客户端发送过来的信息
}
}
printf("Statistics: %d events handled at: %.2f second(s)\n", epoll_events_count, (double)(clock() - tStart)/CLOCKS_PER_SEC);
}
close(listener);
close(epfd);
return 0;
}
int handle_message(int client)
{
char buf[BUF_SIZE], message[BUF_SIZE];
bzero(buf, BUF_SIZE);
bzero(message, BUF_SIZE);
int len;
CHK2(len,recv(client, buf, BUF_SIZE, 0)); //接受客户端信息
if(len == 0) //客户端关闭或出错,关闭socket,并从list移除socket
{
CHK(close(client));
clients_list.remove(client);
}
else //向客户端发送信息
{
if(clients_list.size() == 1)
{
CHK(send(client, STR_NOONE_CONNECTED, strlen(STR_NOONE_CONNECTED), 0));
return len;
}
sprintf(message, STR_MESSAGE, client, buf);
list<int>::iterator it;
for(it = clients_list.begin(); it != clients_list.end(); it++)
{
if(*it != client)
{
CHK(send(*it, message, BUF_SIZE, 0));
}
}
}
return len;
}
//******************************************************************************************
//tester.cpp文件,模拟服务器的高并发,开启10000个客户端去连接服务器,如下:
#include "local.h"
#include "utils.h"
using namespace std;
char message[BUF_SIZE]; //接受服务器信息
list<int> list_of_clients; //存放所有客户端
int res;
clock_t tStart;
int main(int argc, char *argv[])
{
int sock;
struct sockaddr_in addr;
addr.sin_family = PF_INET;
addr.sin_port = htons(SERVER_PORT);
addr.sin_addr.s_addr = inet_addr(SERVER_HOST);
tStart = clock();
for(int i=0 ; i<EPOLL_SIZE; i++) //生成EPOLL_SIZE个客户端,这里是10000个,模拟高并发
{
CHK2(sock,socket(PF_INET, SOCK_STREAM, 0));
CHK(connect(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0);
list_of_clients.push_back(sock);
bzero(&message, BUF_SIZE);
CHK2(res,recv(sock, message, BUF_SIZE, 0));
printf("%s\n", message);
}
list<int>::iterator it; //移除所有客户端
for(it = list_of_clients.begin(); it != list_of_clients.end() ; it++)
close(*it);
printf("Test passed at: %.2f second(s)\n", (double)(clock() - tStart)/CLOCKS_PER_SEC);
printf("Total server connections was: %d\n", EPOLL_SIZE);
return 0;
}
//******************************************************************************************
//单个客户端去连接服务器,client.cpp文件,如下:
#include "local.h"
#include "utils.h"
using namespace std;
char message[BUF_SIZE];
/*
流程:
调用fork产生两个进程,两个进程通过管道进行通信
子进程:等待客户输入,并将客户输入的信息通过管道写给父进程
父进程:接受服务器的信息并显示,将从子进程接受到的信息发送给服务器
*/
int main(int argc, char *argv[])
{
int sock, pid, pipe_fd[2], epfd;
struct sockaddr_in addr;
addr.sin_family = PF_INET;
addr.sin_port = htons(SERVER_PORT);
addr.sin_addr.s_addr = inet_addr(SERVER_HOST);
static struct epoll_event ev, events[2];
ev.events = EPOLLIN | EPOLLET;
//退出标志
int continue_to_work = 1;
CHK2(sock,socket(PF_INET, SOCK_STREAM, 0));
CHK(connect(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0);
CHK(pipe(pipe_fd));
CHK2(epfd,epoll_create(EPOLL_SIZE));
ev.data.fd = sock;
CHK(epoll_ctl(epfd, EPOLL_CTL_ADD, sock, &ev));
ev.data.fd = pipe_fd[0];
CHK(epoll_ctl(epfd, EPOLL_CTL_ADD, pipe_fd[0], &ev));
// 调用fork产生两个进程
CHK2(pid,fork());
switch(pid)
{
case 0: // 子进程
close(pipe_fd[0]); // 关闭读端
printf("Enter 'exit' to exit\n");
while(continue_to_work)
{
bzero(&message, BUF_SIZE);
fgets(message, BUF_SIZE, stdin);
// 当收到exit命令时,退出
if(strncasecmp(message, CMD_EXIT, strlen(CMD_EXIT)) == 0)
{
continue_to_work = 0;
}
else
{
CHK(write(pipe_fd[1], message, strlen(message) - 1));
}
}
break;
default: // 父进程
close(pipe_fd[1]); // 关闭写端
int epoll_events_count, res;
while(continue_to_work)
{
CHK2(epoll_events_count,epoll_wait(epfd, events, 2, EPOLL_RUN_TIMEOUT));
for(int i = 0; i < epoll_events_count ; i++)
{
bzero(&message, BUF_SIZE);
if(events[i].data.fd == sock) //从服务器接受信息
{
CHK2(res,recv(sock, message, BUF_SIZE, 0));
if(res == 0) //服务器已关闭
{
CHK(close(sock));
continue_to_work = 0;
}
else
{
printf("%s\n", message);
}
}
else //从子进程接受信息
{
CHK2(res, read(events[i].data.fd, message, BUF_SIZE));
if(res == 0)
{
continue_to_work = 0;
}
else
{
CHK(send(sock, message, BUF_SIZE, 0));
}
}
}
}
}
if(pid)
{
close(pipe_fd[0]);
close(sock);
}else
{
close(pipe_fd[1]);
}
return 0;
}