TCP LANの簡易版の下でシングルスレッドechoサーバのネットワークプログラミング


Linuxのエコー手続き、エコーサービスにC ++で書かれたこの論文では、サーバはクライアントにデータを受信する、技術の主な用途は、原子炉モード、ソケット、非ブロックIO、プロセス、シングルスレッドのサーバープログラミングを持っていますおよびその他の知識。

1. エコーサーバーの基本的な機能

クライアントは、コンテンツサーバに送信するのROT13暗号化クライアントに送り返さ後。

2. サーバーIOモデル

養子縁組IO多重化、非ブロッキングIO +最初のLinuxで5 IOモデルの下に導入され、Linux上で多重化されたIOのファイルディスクリプタの機構を使用するモデル(反応器モード)。

2.1 IOモデル

  • IOモデルにブロッキング
    操作がIO完了するまで、呼び出し側プロセスはブロックされます
  • IOモデルをノンブロッキング
    カーネルデータの時に呼び出し元プロセスは、プロセスがスリープ状態に置かれることはありません、ポーリング方法を取る準備ができていません。
  • IO多重化モデル
    を呼び出すクライアント・プロセスカーネルデータの準備ができていない場合は選択し、世論調査やファイルディスクリプタシステムコールは、3つのすべてのシステム上でブロックされますが、クライアント・プロセスをブロックせずに呼び出します。
  • ドライブ信号IOモデル
    カーネルデータの準備ができたときに、呼び出し元のプロセスに通知SIGIOを送るデータの準備ができていないときに、呼び出し元のプロセスをブロックしません。
  • 非同期IOモデル
    呼び出し元のプロセスをジョブを開始し、カーネルは(ユーザーにカーネルバッファからのデータのコピーを含む)全体の動作が完了した後、呼び出し元のプロセスを通知できるようにカーネルに指示します。

要約すると、ブロッキングとノンブロッキング違いは次のとおりです。呼び出し元プロセスはすぐに戻りますか。同期および非同期違いは次のとおりです。呼び出し元のプロセスがブロックされ、ユーザ空間にカーネル空間からデータをコピーします。

2.2非ブロックIOとなぜIO多重化?

ここで選択したマニュアルでは、答えを与えます

Linuxでは、()を選択し、それにもかかわらず、その後の読み取りブロックしながら、「読書のための準備」として、ソケットファイルディスクリプタを報告することがあります。データが到着したが、検査時に間違ったチェックサムを持っており、破棄されたとき、これは、例えば発生する可能性があります。ファイル記述子が誤って準備として報告されている他の状況があるかもしれません。したがって、ブロックしてはならないソケットにO_NONBLOCKを使用する方が安全かもしれません。

ここでの意味は、このセクションを破棄し、その後、プロトコルスタック部新しいチェックサムエラーをチェックするために、新しいデータソケットは、バッファ部が到着受信した場合、あり、その後、ソケットディスクリプタ読み出し可能なレポートを選択しますが、ソケットがノンブロッキングに設定されていない場合、この時間は、読み取り可能なデータを読み込むための何の呼び出しはありませんでした、これは現在のスレッドをブロックします読んで。ソケットがセットにノンブロッキングされている場合は、エラーが返され、データが読まれないことがあります。

2.3のepollのトリガ機構の違い

2.3.1レベルトリガとエッジトリガ

選択するだけでレベルトリガ、ファイルディスクリプタサポートレベルトリガとエッジトリガモード、トリガ・レベルのデフォルト・モードをサポートしています。だから、レベルトリガとエッジトリガそれがあるとの違いは何ですか?
トリガレベル:限り、条件が満たされているとして、その後、トリガイベント(データは読んでいない、カーネルはいつもあなたをお知らせいたします)。
エッジトリガ:たびの状態が変化し、トリガイベント。

、終了していない場合は、ファイルディスクリプタが読んだときに、データを繰り返し(読んで、すなわち、すべてをお読みくださいバッファ)、またはそれはEAGAINが値0になるまでのリターンを読んで出会うまで:エッジがあり、問題があるトリガシステムは、変更されません。このファイルディスクリプタを気付くことはありませんファイルディスクリプタ相互の状況、そして今回死んだようなファイルディスクリプタを考慮します。

2.3.1選択、投票、ファイルディスクリプタ

  • 問題は、選択存在する
    ;(1)限られた数のそのFD_SET
    ;(2)ソケットポーリング方法の非効率的なFD_SETを横断するたびに、
    カーネルモードで、ストレージスペースFD構造を大量に維持するために、(3)必要とオーバーヘッドと大の間で送信されます。
  • 問題は、ポーリング存在し
    、(1)配列FD_SETユーザーモードとカーネルモード伝送の多数、大きなオーバーヘッドとの間に
    上述したように、(2)エッジの問題。
  • -------理由ファイルディスクリプタ効率的な動作、頻繁まれ操作区別
    (1)新しいepoll_createディスクリプタと、
    (2)epoll_ctrlは(監視されるすべての接続を追加または削除)
    (3)アクティブな接続イベントがepoll_waitを返します

epollを三つの要素:赤黒木、MMAPとリンクされたリスト。
MMAP、MMAPカーネル空間と同じ物理メモリアドレスにマッピングされたユーザのアドレス空間のアドレスの量を達成するために、メモリのカーネルとユーザスペースによって達成内部ファイルディスクリプタは、カーネルモードとユーザモードとの間のデータ交換を減少させます。
赤黒木保存されたファイルディスクリプタは、ソケットのパフォーマンスを追加および削除するには良い時間をソケットを聞いています。

2.4は、問題と解決策が発生する可能性があります

2.4.1サーバーの問題

  1. (子供が接続サービス上でいくつかのクライアントを持ち続けて)接続要求は、顧客の要求を処理するために子プロセスをフォークに到着、しかし、いくつかの理由で、監視サーバが終了し、リスナーサーバを起動し、状況を仮定し、再起動サーバーを監視します。

それはバインド・コールが失敗したポート(接続を取り扱う子プロセスの前に派生)既存の接続を、バンドルしようとすると、監視サーバの再起動は、ソケットを呼び出して再起動し、デフォルトでは、バインドは、聞きます。

解決策:ソケットと2つのコールがソケットオプション設定SO_REUSEADDRバインドの間、呼び出しが成功した。この時点でバインドします。(すべてのTCPソケットサーバは、サーバが、この場合には再起動ができるようにするには、このオプションを指定する必要があります)

  1. )(新しい接続が得られことは受け入れる際に、到着し、原因の同時接続の数が多いサーバには、状況を仮定し、私が何をすべきか、EMFILEエラーを返しますか?

、この状況は、このプロセスのファイル記述子が限界に達したことを意味し、新しいソケット接続のためのファイルディスクリプタを作成することはできませんが、この「接続」のファイルディスクリプタを取得していないので、我々は(閉じることはできません)、プログラムは実行を続け新しい接続が保留中のためのイベントがepoll_wait()はfdが他の接続の正常な動作に影響を与え、ビジーループにまだ読める、こうした相互の原因サーバプログラムで聞いて、すぐに戻ります。

解決策:ファイルディスクリプタのハード制限として、我々は、接続の数がソフトリミットを超えた場合、自分自身のやや低いソフトリミットを設定し、新しい接続をクローズするためのイニシアチブを取ることができます。

2.4.1クライアント

複数のプロセス(フォークを使用して)、または複数のスレッドに非ブロックIO、通常、アプリケーション・タスクを使用している場合、私はここで使用している現在のプロセスが子をサーバからメッセージをコピーするために使用されている二つのサブプロセスに分割されています標準出力に出力し、親プロセスサーバーのメッセージングクライアントからのコピーの標準入力に、図に示すように。親と子が同じソケットを共有:内部のソケットを書く親プロセスを、ソケットから読ん子は、2つのファイル記述子が同じソケットを参照してください。
ここに画像を挿入説明
######## echo.h ############

#ifndef _ECHO_
#define _ECHO_

#include <sys/types.h> //常用数据类型定义头文件
#include <sys/socket.h>
#include <sys/epoll.h>
#include <fcntl.h>
#include <error.h>
#include <arpa/inet.h> //地址转换
#include <unistd.h> //提供对操作系统应用接口访问功能的头文件 fork 等、、、
#include <stdlib.h>  //常用的系统函数,free、、、
#include <stdio.h>
#include <string>
#include <cstring>

using namespace std;

#define MAX_EVENT_NUMBER 1024
#define BUF_SIZE 300
#define EPOLL_SIZE 100 //epoll最大监听数

#define PORT "2019"  //atoi(PORT),将字符串变为整形
#define SERVERIP "127.0.0.1"


int setNonblocking(int sockfd){
	int flags = fcntl(sockfd,F_GETFL);//获取旧的文件标志
	int newflags = flags | O_NONBLOCK;
	int n;
	if((n = fcntl(sockfd,newflags)) == -1){
		perror("fcntl error");
		exit(-1);}
	return flags;
}

void addfd(int epollfd,int sockfd,int state){
	struct epoll_event event;
	event.data.fd = sockfd;
	event.events = state;
	epoll_ctl(epollfd,EPOLL_CTL_ADD,sockfd,&event);
}

void handleEvent(struct epoll_event * EventLoop, int eventsNumber,int epollfd,int listenfd){
	int confd = 0;
	struct sockaddr_in clientaddr;
	char buf[BUF_SIZE];
	for(int i= 0; i< eventsNumber;++i){
		if(EventLoop[i].data.fd == listenfd){ //如果是第一次建立连接
			socklen_t clientlength = sizeof(clientaddr);
			confd = accept(listenfd,(struct sockaddr*) &clientaddr,&clientlength);
			addfd(epollfd,confd,EPOLLIN);
			//clients.push_back(clientsocket);
		}
		else{
			confd = EventLoop[i].data.fd;
			bzero(&buf,strlen(buf));
			int len = sprintf(buf,"After RTO :");
			int nread = 0;
			if((nread = read(confd,buf+len,BUF_SIZE))<0){
				perror("read error");
				exit(-1);}
		        for(unsigned int i = nread;i < strlen(buf);++i){ //ROT	加密
					if((buf[i] >= 'a' && buf[i] <= 'm')  || (buf[i] >= 'A' && buf[i] <= 'M')){
						buf[i] = char(buf[i]  + 13);	
						continue;}
					else if((buf[i] >= 'm' && buf[i] <= 'z') || (buf[i] >= 'M' && buf[i] <= 'Z')){
						buf[i] = char(buf[i]  - 13);		
						continue;}
				}
			buf[strlen(buf)+1] = 0;		
			write(confd,buf,strlen(buf)+1);
		}
	}
}


#endif

########## ############# echoserver.cpp

#include "echo.h"

int main(){
	int error = 0;
	char buf[BUF_SIZE];
	struct sockaddr_in serveraddr;
	bzero(&serveraddr,sizeof(serveraddr));
	serveraddr.sin_family = AF_INET;
	inet_aton(SERVERIP,&serveraddr.sin_addr);
	serveraddr.sin_port = htons(atoi(PORT));
	int listenfd = -1;
	
	if((listenfd = socket(PF_INET,SOCK_STREAM,0)) == -1){
		perror("socket error");
		exit(-1);
	}

	int optval = 0;
	socklen_t optlen;
	optlen = sizeof(optval);
	optval = 1;
	setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,(void*) optval,optlen);
	if((bind(listenfd,(struct sockaddr *)&serveraddr,sizeof(serveraddr))) == -1){
		perror("bind error");
		exit(-1);
	}

	int listenopt = 10;
	char * ptr;
	if((ptr = getenv("LISTENQ")) != NULL)
		listenopt = atoi(ptr);	
	if((listen(listenfd,listenopt)) == -1){
		perror("listen error");
		exit(-1);
	}
	
	int epollfd = epoll_create(EPOLL_SIZE); //监听的数目
	struct epoll_event EventLoop[MAX_EVENT_NUMBER];
	addfd(epollfd,listenfd,EPOLLIN);//添加监听事件,调用epoll_ctl

	int eventsNumber = 0;
	
	while(1){
		eventsNumber = epoll_wait(epollfd,EventLoop,EPOLL_SIZE,-1); //-1表示阻塞
		if(eventsNumber == -1){
			perror("epollwait error");
			exit(-1);
		}
		handleEvent(EventLoop,eventsNumber,epollfd,listenfd);
	}


	close(listenfd);
	close(epollfd);
}


################ echocli.cpp #######################

#include "echo.h"

void readfrom(int clientsocket,char * buf);
void writeto(int clientsocket,char * buf);

int main(){
	int clientsocket;
	char buf[BUF_SIZE];
	if((clientsocket = socket(PF_INET,SOCK_STREAM,0))== -1){
		perror("client socket error");
		exit(-1);
	}

	struct sockaddr_in serveraddr;
	bzero(&serveraddr,sizeof(serveraddr));
	serveraddr.sin_family = AF_INET;
	inet_aton(SERVERIP,&serveraddr.sin_addr);
	serveraddr.sin_port = htons(atoi(PORT));

	if((connect(clientsocket,(struct sockaddr *) & serveraddr,sizeof(serveraddr))) == -1){
		perror("connect error");
		exit(-1);
	}

	pid_t pid = fork();
	if(pid == 0){
		writeto(clientsocket,buf);
	}
	else{
		readfrom(clientsocket,buf);
	}
	close(clientsocket);
	return 0;
}

void readfrom(int clientsocket,char * buf){
	while(1){
		if((read(clientsocket,buf,BUF_SIZE)) == 0){
			perror("client read error");
			exit(-1);
		}
		printf("%s",buf);
	}
}

void writeto(int clientsocket,char * buf){
	while(1){
		fgets(buf,BUF_SIZE,stdin);
	if(!strcmp(buf,"exit\n")){
		shutdown(clientsocket,SHUT_WR);
		return;
	}
	write(clientsocket,buf,strlen(buf));
	}
}

概要

唯一のエコーの基本的な機能を実現することができます。このコードは、フォローアップを改善し、完璧ではありません。
ブロガーのブログの多くに基づいて、前のノートブックにコピーされるので、それがリンクを見つけることができません、あなたが侵害感じる場合は、私にはプライベートの手紙を喜ばせる、私は最初の場所でコンテンツを削除します。

おすすめ

転載: blog.csdn.net/YoungSusie/article/details/91583147