1. 程式人生 > >epoll ET模式伺服器和客戶端原始碼例子

epoll ET模式伺服器和客戶端原始碼例子

關於epoll替代select作為高效能伺服器的事件通知機制的資料相當多,我就不在這裡班門弄斧了,有興趣的同學可以參考末尾的文獻連結。

這裡說明如下:

1.epoll是linux下高併發伺服器的完美方案,因為是基於事件觸發的,所以比select快的不只是一個數量級。

2.單執行緒epoll,觸發量可達到15000,參見文獻[4]

3.高效能server要使用非阻塞方式

使用ET模型的時候,一定要注意,每次收到有效通知,然後讀取資料的時候,務必每次讀取乾淨(讀到出錯為止)。當再次呼叫check(sockfd)的時候才能正確返回。 目前使用的epoll模型大多都是ET模式,socket都要設定為非阻塞的。 網上找了很多原始碼例子,但是大多不理想,下面是我根據網上資料修改嘗試出的一個例子,供大家參考。後續更進一步地研究可以參考nginx,memcache,Apache traffic server這類開原始碼。

epoll伺服器端

//compile: g++ -g epoll_server.cpp -o epoll_server
//run: ./epoll_server
//
#include <sys/socket.h>
#include <sys/epoll.h>
#include <sys/sendfile.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <fcntl.h>
#include <errno.h>


#define MAX_EVENTS 10
#define LISTENQ 20
#define PORT 5000 //8080


//設定socket連線為非阻塞模式
void setnonblocking (int fd)
{
	int opts;

	opts = fcntl (fd, F_GETFL);
	if (opts < 0)
	{
		perror ("fcntl(F_GETFL)\n");
		exit (1);
	}
	opts = (opts | O_NONBLOCK);
	if (fcntl (fd, F_SETFL, opts) < 0)
	{
		perror ("fcntl(F_SETFL)\n");
		exit (1);
	}
}

int main ()
{
	struct epoll_event ev, events[MAX_EVENTS];
	int listenfd, connfd, nfds, epfd, sockfd, i, nread, n;
	struct sockaddr_in local, remote;
    socklen_t addrlen;
	char buf[BUFSIZ];

	//建立listen socket
	if ((listenfd = socket (AF_INET, SOCK_STREAM, 0)) < 0)
	{
		perror ("sockfd\n");
		exit (1);
	}
	setnonblocking (listenfd);
	bzero (&local, sizeof (local));
	local.sin_family = AF_INET;
	local.sin_addr.s_addr = htonl (INADDR_ANY);;
	local.sin_port = htons (PORT);
	if (bind (listenfd, (struct sockaddr *) &local, sizeof (local)) < 0)
	{
		perror ("bind\n");
		exit (1);
	}
	listen (listenfd, LISTENQ);

	epfd = epoll_create (MAX_EVENTS);
	if (epfd == -1)
	{
		perror ("epoll_create");
		exit (EXIT_FAILURE);
	}
	ev.events = EPOLLIN;
	ev.data.fd = listenfd;
	if (epoll_ctl (epfd, EPOLL_CTL_ADD, listenfd, &ev) == -1)
	{
		perror ("epoll_ctl: listen_sock");
		exit (EXIT_FAILURE);
	}

	for (;;)
	{
		nfds = epoll_wait (epfd, events, MAX_EVENTS, -1);
		if (nfds == -1)
		{
			perror ("epoll_wait error");
			exit (EXIT_FAILURE);
		}

		for (i = 0; i < nfds; ++i)
		{
			sockfd = events[i].data.fd;
			if (sockfd == listenfd)
			{
				while ((connfd = accept (listenfd, (struct sockaddr *) &remote, &addrlen)) > 0)
				{
                    char *ipaddr = inet_ntoa (remote.sin_addr);
                    printf("accept a connection from [%s]\n", ipaddr);
					setnonblocking (connfd);	//設定連線socket為非阻塞
					ev.events = EPOLLIN | EPOLLET;	//邊沿觸發要求套接字為非阻塞模式;水平觸發可以是阻塞或非阻塞模式
					ev.data.fd = connfd;
					if (epoll_ctl (epfd, EPOLL_CTL_ADD, connfd, &ev) == -1)
					{
						perror ("epoll_ctl: add");
						exit (EXIT_FAILURE);
					}
				}
				if (connfd == -1)
				{
					if (errno != EAGAIN && errno != ECONNABORTED && errno != EPROTO && errno != EINTR)
						perror ("accept");
				}
				continue;
			}
			if (events[i].events & EPOLLIN)
			{
				n = 0;
				while ((nread = read (sockfd, buf + n, BUFSIZ - 1)) > 0)
				{
					n += nread;
				}
				if (nread == -1 && errno != EAGAIN)
				{
					perror ("read error");
				}
                printf("recv from client data [%s]\n", buf);
				ev.data.fd = sockfd;
				ev.events = events[i].events | EPOLLOUT;
				if (epoll_ctl (epfd, EPOLL_CTL_MOD, sockfd, &ev) == -1)
				{
					perror ("epoll_ctl: mod");
				}
			}
			if (events[i].events & EPOLLOUT)
			{
				snprintf (buf, sizeof(buf), "HTTP/1.1 200 OK\r\nContent-Length: %d\r\n\r\nHello World", 11);
				int nwrite, data_size = strlen (buf);
				n = data_size;
				while (n > 0)
				{
					nwrite = write (sockfd, buf + data_size - n, n);
					if (nwrite < n)
					{
						if (nwrite == -1 && errno != EAGAIN)
						{
							perror ("write error");
						}
						break;
					}
					n -= nwrite;
				}
                printf("send to client data [%s]\n", buf);
				close (sockfd);
                events[i].data.fd = -1;
			}
		}
	}
	close (epfd);
	close (listenfd);
	return 0;
}

epoll客戶端

//compile: g++ -g epoll_client.cpp -o epoll_client
//run: ./epoll_client
//
#include <iostream>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>

using namespace std;

#define PORT 5000

int main(int argc, char* argv[])
{
    int sockfd, on = 1;
    char buffer[512] = {0};
    struct sockaddr_in servaddr;
    memset(&servaddr, 0, sizeof(servaddr));

    if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0){
        cout << "create socket fail" << endl;
        return -1;
    }
    cout << "succeed to create client socket fd " << sockfd  << endl;

    setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
    cout << "set socket reuse by etsockopt" << endl;

    servaddr.sin_port = htons((short)PORT);
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); //此處更改epoll伺服器地址

    if(connect(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0){
        cout << "connect error" << endl;
        return -1;
    }
    cout << "succeed to connect epoll server " << endl;

    char target[] = "The Author: 
[email protected]
"; memcpy(buffer, target, strlen(target)); int wlen = send(sockfd, buffer, strlen(buffer), 0); if(wlen <= 0) cout << " send data to server fail " << strerror(errno) << endl; cout << "send data to server on success, data: [" << buffer << "]"<< endl; memset(buffer, 0, sizeof(buffer)); int rlen = recv(sockfd, buffer, sizeof(buffer), 0); if(rlen <= 0) cout << " receive data from server fail " << strerror(errno) << endl; cout << "receive data from server on success, data: [" << buffer << "]" << endl; return 0; }

大家可以在上述框架上進一步修改.下面是執行圖



下面的參考文獻按照我認為的優先順序遞減排列

相關推薦

epoll ET模式伺服器客戶原始碼例子

關於epoll替代select作為高效能伺服器的事件通知機制的資料相當多,我就不在這裡班門弄斧了,有興趣的同學可以參考末尾的文獻連結。 這裡說明如下: 1.epoll是linux下高併發伺服器的完美方案,因為是基於事件觸發的,所以比select快的不只是一個數量級。 2.單

FTP伺服器客戶原始碼編寫問題(ftp server client source)

其實FTP也就是普通的Socket程式,只是需要按照FTP協議(RFC959, 1635?可能我記錯了)去做,也就是每個訊息有固定的結構的,比如頭3個位元組必須是200,201,300,400之類的數字表示操作結果。 寫FTP協議的程式主要要明白的一個關鍵問題是雙socket,一個control socke

golang thrift 原始碼分析,伺服器客戶究竟是如何工作的

首先編寫thrift檔案(rpcserver.thrift),執行thrift --gen go rpcserver.thrift,生成程式碼 namespace go rpc service RpcService { string SayHi(1: s

netty原始碼解解析(4.0)-20 ChannelHandler: 自己實現一個自定義協議的伺服器客戶

  本章不會直接分析Netty原始碼,而是通過使用Netty的能力實現一個自定義協議的伺服器和客戶端。通過這樣的實踐,可以更深刻地理解Netty的相關程式碼,同時可以瞭解,在設計實現自定義協議的過程中需要解決的一些關鍵問題。   本週章涉及到的程式碼可以從github上下載: https://git

socket程式設計回射伺服器客戶

//回射伺服器 #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet

網路程式設計(二)——伺服器客戶資訊的獲取

  目錄 1、字串IP地址和二進位制IP地址結構的轉換 2.套接字檔案描述符的判定 3、IP地址與域名之間的相互轉換 4、協議名稱處理函式 1、字串IP地址和二進位制IP地址結構的轉換 #include <sys/socket.h> #inclu

第一個Netty程式——構建執行Echo伺服器客戶

在構建之前,需要安裝開發環境:JDK和Apache Maven以及IDE。 pom檔案: <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-ins

Java thrift伺服器客戶建立例項

首先環境介紹一下: 1.IntelliJ IDEA 2017.1 2.thrift-0.9.3 相信大家在看我這篇文章的時候已經對thrift通訊框架已有所調研,這裡就不再贅述了,直接進入正題: <1>建立HelloWorld.thrift namespace jav

Spring Boot2.0 Oauth2 伺服器客戶配置及原理

一、應用場景 為了理解OAuth的適用場合,讓我舉一個假設的例子。 有一個"雲沖印"的網站,可以將使用者儲存在Google的照片,沖印出來。使用者為了使用該服務,必須讓"雲沖印"讀取自己儲存在Google上的照片。 問題是隻有得到使用者的授權,Google才會同意"雲沖印"讀取這些

教你如何構建非同步伺服器客戶的 Kotlin 框架 Ktor

Ktor 是一個使用 Kotlin 以最小的成本快速建立 Web 應用程式的框架。 Ktor 是一個用於在連線系統(connected systems)中構建非同步伺服器和客戶端的 Kotlin 框架。它由 Kotlin 團隊建立,因此,它充分利用了 Kotlin 的語言特性,為開發者提供出色的體驗和執

【Redis】Redis在Ubuntu中的伺服器客戶操作

伺服器端 伺服器端的命令為: redis-server 可以使用help檢視幫助文件 redis-server --help 個人習慣 ps aux | grep redis   # 檢視redis伺服器程序 sudo kill -9

ROS學習筆記18 (編寫簡單的伺服器客戶 (C++))

1 編寫Service節點 這裡,我們將建立一個簡單的service節點("add_two_ints_server"),該節點將接收到兩個整型數字,並返回它們的和。 進入先前你在catkin workspace教程中所建立的beginner_tutorials包所在的目錄

ROS學習筆記19 (編寫簡單的伺服器客戶 (Python))

1 編寫服務端節點 我們會建立服務端節點 ("add_two_ints_server") ,節點接收兩個整型數字,並返回和 進入beginner_tutorials包 $ roscd beginner_tutorials 確保你確保已經在之前建立好AddTwoInts

(4)編寫簡單的伺服器客戶

目錄 編寫Service節點 程式碼 程式碼解釋 編寫Client節點 程式碼 程式碼解釋 編譯節點 編譯節點

Linux Epoll ET模式EPOLLOUTEPOLLIN觸發時刻

ET模式稱為邊緣觸發模式,顧名思義,不到邊緣情況,是死都不會觸發的。 EPOLLOUT事件: EPOLLOUT事件只有在連線時觸發一次,表示可寫,其他時候想要觸發,那你要先準備好下面條件: 1.某次write,寫滿了傳送緩衝區,返回錯誤碼為EAGAIN。 2.對端讀取了

Socket通訊實現伺服器客戶對話

廣域網和區域網 介紹socket通訊前我們先介紹一下廣域網與區域網的概念。區域網簡稱LAN,是指在某一區域幾臺計算機組成的計算機組,區域網是封閉的,區域網經常採用共享通道,即共用同一條電纜。廣域網簡稱WAN,是一種跨越大的,地域性的地區性網路集合廣域網包含大大小

網路程式設計 伺服器客戶之間流的傳遞

import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.Socket; /* 實現TCP客戶端,連線到伺服器 和伺服器

C#實現伺服器客戶之間通訊

TCP  套接字程式設計 伺服器端實現步驟: 1、使用Socket類建立套接字。 2、利用Bind方法將建立的套接字繫結到指定的地址結構。 3、利用Listen方法設定套接字為監聽模式,使得伺服器進入被動開啟狀態。 4、接受客戶端的連線請求。 5、接收、應答客戶端的資料請求

python實現tcp伺服器客戶(socket)

python實現tcp伺服器和客戶端(socket) 1.socket模組 socket是什麼 socket最初是為了同一主機上的應用程式建立的,使得一個程式與另外一個程式之間可以通訊,也就是所謂的程序間通訊,有兩種型別的socket:基於檔案和麵向網路的。

用node.js模擬伺服器客戶

伺服器 程式碼 var net = require("net") var server = net.createServer(); server.listen(12306,"127.0.0.1") server.on("listening",function(){ consol