// local.h
#include <iostream>
#include <list>
#include <sys/socket.h>
#include <sys/wait.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <sys/epoll.h>
#include <sys/sendfile.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <fcntl.h>
#include <errno.h>
#define BUF_SIZE 1024 //預設緩沖區
#define SERVER_PORT 44444 //監聽端口
#define SERVER_HOST "192.168.1.103" //伺服器IP位址
#define EPOLL_RUN_TIMEOUT -1 //epoll的逾時時間
//#define EPOLL_SIZE 10000 //epoll監聽的用戶端的最大數目
#define EPOLL_SIZE 1019 //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);
// utils.h
#include "local.h"
int setnonblocking(int sockfd)
{
CHK(fcntl(sockfd, F_SETFL, fcntl(sockfd, F_GETFD, 0)|O_NONBLOCK));
return 0;
}
server.cpp
生成server g++ -o server server.cpp
#include "local.h"
#include "utils.h"
using namespace std;
list<int>clients_list;
int main(int ac,char**av)
{
int listenfd;
struct sockaddr_in addr,their_addr;
addr.sin_family=AF_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];
char message[BUF_SIZE];
int epfd;
clock_t tStart;
int client,res,epoll_events_count;
CHK2(listenfd,socket(AF_INET,SOCK_STREAM,0));
setnonblocking(listenfd);
CHK(bind(listenfd,(struct sockaddr*)&addr,sizeof(addr)));
CHK(listen(listenfd,1));
CHK2(epfd,epoll_create(EPOLL_SIZE));
ev.events=EPOLLIN;
ev.data.fd=listenfd;
CHK(epoll_ctl(epfd,EPOLL_CTL_ADD,listenfd,&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==listenfd){
CHK2(client,accept(listenfd,(struct sockaddr*)&their_addr,
&socklen));
setnonblocking(client);
ev.data.fd=client;
CHK(epoll_ctl(epfd,EPOLL_CTL_ADD,client,&ev)); //沒有改變事件,依然是
//沿用的ev.events
//=EPOLL_IN,可讀事件
//這裡的ev起到一個綁定事件和描述符的中間變量,可以重複使用
//也即,每個描述符都可以綁定特殊的事件
clients_list.push_back(client);
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));
}
}
printf("statistics:%d events handled spent:%.2f second \n",
epoll_events_count,(double)(clock()-tStart)/CLOCKS_PER_SEC);
}
close(listenfd);
close(epfd);
return 0;
}
int handle_message(int client)
{
char buf[BUF_SIZE],message[BUF_SIZE];
bzero(buf,sizeof(buf));
bzero(message,sizeof(message));
int len;
CHK2(len,recv(client,buf,BUF_SIZE,0));
if(len==0){ //表示收到了FIN分節
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;
}
test.cpp 模拟高并發,連接配接成功後,函數就傳回了,所有連接配接又斷裂
g++ -o test test.cpp
#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 ac,char**av)
{
int sock;
struct sockaddr_in addr;
addr.sin_family=AF_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++){
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:%.4f 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;
}