国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

epoll并發(fā)服務(wù)器的實現(xiàn)

這篇具有很好參考價值的文章主要介紹了epoll并發(fā)服務(wù)器的實現(xiàn)。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點擊"舉報違法"按鈕提交疑問。

Epoll并發(fā)聊天服務(wù)器的實現(xiàn)

一、相關(guān)知識

1.實現(xiàn)并發(fā)通信的三種方式

? 實現(xiàn)并發(fā)通信主要有三種方式:多進程服務(wù)器、多路復(fù)用服務(wù)器(I/O復(fù)用)、多線程服務(wù)器

  • 多進程服務(wù)器

? 多進程服務(wù)器指的是利用不同進程處理來自不同客戶端發(fā)來的連接請求,進程之間以輪轉(zhuǎn)的方式運行,由于各個進程之間輪轉(zhuǎn)運行的時間間隔很小,故在用戶看來其實現(xiàn)了并行處理所有的客戶請求。

? 多進程服務(wù)器主要使用fork()函數(shù)進行創(chuàng)建子進程,將主進程和子進程隔離開來對各個客戶端的請求進行分別響應(yīng),fork()函數(shù)的原型為:

#include<unisted.h>

pid_t fork(void);
//成功時返回進程ID,失敗時返回-1

fork()函數(shù)將創(chuàng)建調(diào)用的進程副本,也就是說,并非根據(jù)完全不同的程序創(chuàng)建進程,而是復(fù)制正在運行的、調(diào)用fork函數(shù)的進程

? 在子進程完成其任務(wù)之后,若程序員沒有對其進行銷毀,那么該子進程會變成僵尸進程(Zombie),若父進程未主動要求獲得子進程的結(jié)束狀態(tài)值,操作系統(tǒng)將一直保存,并讓子進程長時間處于僵尸進程狀態(tài)。我們可以使用wait()函數(shù)銷毀僵尸進程:

#include<sys/wait.h>

pid_t wait(int * statloc);
//成功時返回終止的子進程ID,失敗時返回-1

? 調(diào)用此函數(shù)時如果已有子進程終止,那么子進程終止時傳遞的返回值(exit函數(shù)的參數(shù)值、main函數(shù)的return返回值)將保存到該函數(shù)的參數(shù)所指內(nèi)存空間。

? 但函數(shù)參數(shù)指向的單元中還包含其他信息,因此需要通過下列宏進行分離:

  • WIFEXITED子進程正常終止時返回“真”
  • WEXITSTATUS返回子進程的返回值
int status;
pid_t pid=fork();
.....
if(pid == 0)return 3;
wait(&status);
if(WIFEXITED(status))
    //輸出3
    printf("Child send: %d \n",WEXITSTATUS(status));

調(diào)用wait函數(shù)時,如果沒有已終止的子進程,那么程序?qū)?strong>阻塞直到有子進程終止,因此需謹慎調(diào)用該函數(shù)!

? 相較于wait()銷毀僵尸進程,waitpid()是更好的函數(shù),并且使用相關(guān)信號(signal()函數(shù))可以在子進程調(diào)用完成后自動銷毀,但是多進程服務(wù)器并不是本文的重點,故不再贅述。

  • 多路復(fù)用服務(wù)器

? 下面用兩張圖簡單介紹多進程服務(wù)器與多路復(fù)用服務(wù)器的區(qū)別
epoll并發(fā)服務(wù)器的實現(xiàn),服務(wù)器,linux,c語言,網(wǎng)絡(luò)epoll并發(fā)服務(wù)器的實現(xiàn),服務(wù)器,linux,c語言,網(wǎng)絡(luò)

? IO多路復(fù)用相對于阻塞式和非阻塞式的好處就是它可以監(jiān)聽多個 socket ,并且不會消耗過多資源。select函數(shù)是實現(xiàn)I/O多路復(fù)用的最簡單也是最重要的函數(shù)。

? 當(dāng)用戶進程調(diào)用 select 時,它會監(jiān)聽其中所有 socket() 直到有一個或多個 socket 數(shù)據(jù)已經(jīng)準(zhǔn)備好,否則就一直處于阻塞狀態(tài)。select()的缺點在于單個進程能夠監(jiān)視的文件描述符的數(shù)量存在最大限制,select()所維護的存儲大量文件描述符的數(shù)據(jù)結(jié)構(gòu),隨著文件描述符數(shù)量的增大,其復(fù)制的的開銷也線性增長。同時,由于網(wǎng)絡(luò)響應(yīng)時間的延遲使得大量的tcp鏈接處于非常活躍狀態(tài),但調(diào)用select()會對所有的socket進行一次線性掃描,所以這也浪費了一定的開銷。

? select()函數(shù)的參數(shù)有些復(fù)雜,下面使用注釋對select()的各個參數(shù)進行詳細的解釋

#include<sys/select.h>
#include<sys/time.h>

int select(
int maxfd, fd_set * readset, fd_set * writeset, fd_set * exceptset, const struct timeval * timeout);
//maxfd:監(jiān)視對象文件描述符數(shù)量
//readset:將所有關(guān)注”是否存在待讀取數(shù)據(jù)“的文件描述符注冊到fd_set型變量,并傳遞其地址值
//writeset:將所有關(guān)注”是否可傳輸無阻塞數(shù)據(jù)“的文件描述符注冊到fd_set型變量,并傳遞其地址值
//exceptset:將所有關(guān)注”是否發(fā)生異?!暗奈募枋龇缘絝d_set型變量,并傳遞其地址值
//timeout:調(diào)用select函數(shù)后,為防止陷入無限循環(huán)的阻塞,傳遞超時信息
//返回值:發(fā)生錯誤時返回-1,超時返回時返回0,因發(fā)生關(guān)注的事件返回時,返回大于0的值,該值是發(fā)生事件的文件描述符數(shù)

? select函數(shù)使用具體流程:

  1. 定義select需要的各種參數(shù),例如fd_max、timeout、所需監(jiān)聽的套接字列表等。
  2. 使用select函數(shù),并對其返回值進行判斷。
  3. 若返回值為0,那么繼續(xù)循環(huán)使用select監(jiān)視套接字列表;若大于0,使用FD_ISSET確認是哪一個列表發(fā)生了變化,之后對select函數(shù)的返回值(即發(fā)生變化的套接字)進行操作。

? select不合理的兩個缺點:

  • 調(diào)用后常見的針對所有文件描述符的循環(huán)語句。
  • 每次調(diào)用函數(shù)時都需要向該函數(shù)傳遞監(jiān)視對象信息。

? 調(diào)用select函數(shù)后,不是把發(fā)生變化的文件描述符單獨集中到一起,而是通過觀察作為監(jiān)視對象的fd_set變量的變化,找出發(fā)生變化的文件描述符,因此無法避免針對所有監(jiān)視對象的循環(huán)語句。

? 向系統(tǒng)傳遞監(jiān)視對象信息是select函數(shù)的瓶頸,因為這種應(yīng)用程序與系統(tǒng)層面的交互將對程序造成很大負擔(dān)。

? 由于select函數(shù)是針對套接字的處理,而套接字是由操作系統(tǒng)管理的,故無法繞開應(yīng)用程序與操作系統(tǒng)之間的交互,那么可行的優(yōu)化方法是,僅向操作系統(tǒng)傳遞1次監(jiān)視對象,監(jiān)視范圍或內(nèi)容發(fā)生變化時只通知發(fā)生變化的事項。

? epoll()函數(shù)解決了select()函數(shù)在處理上的瓶頸,其優(yōu)點為:

  • 無需編寫以監(jiān)視狀態(tài)變化為目的的針對所有文件描述符的循環(huán)語句。
  • 調(diào)用對應(yīng)于select函數(shù)的epoll_wait函數(shù)時無需每次傳遞監(jiān)視對象信息。

? epoll實現(xiàn)需要的三個函數(shù)

  • epoll_create:創(chuàng)建保存epoll文件描述符的空間,取代了select中自己創(chuàng)建fd_set的操作,由操作系統(tǒng)負責(zé)保存監(jiān)視對象文件描述符
  • epoll_ctl:向空間注冊并注銷文件描述符
  • epoll_wait:與select函數(shù)類似,等待文件描述符發(fā)生變化

? epoll方式通過epoll_event結(jié)構(gòu)體將發(fā)生變化的文件描述符單獨集中到一起

struct epoll_event
{
    __uint32_t events;
    epoll_data_t data;
}
	typedef union epoll_data
    {
        void * ptr;
        int fd;
        __uint32_t u32;
        __uint64_t u64;
    }epoll_data_t;
#include<sys/epoll.h>

int epoll_create(int size);
//成功時返回epoll文件描述符,失敗時返回-1
//調(diào)用epoll_create時創(chuàng)建的文件描述符保存空間稱為”epoll例程”,通過參數(shù)size傳遞的值決定epoll例程的大小,但該值僅供操作系統(tǒng)參考
#include<sys/epoll.h>

int epoll_ctl(int epfd, int op, int fd, struct epoll_event);
//成功時返回0,失敗時返回-1
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
//成功時返回發(fā)生事件的文件描述符數(shù),失敗時返回-1
//該函數(shù)的調(diào)用方式如下:
int event_cnt;
struct epoll_event * ep_events;
.....
ep_events = malloc(sizeof(struct epoll_event)*EPOLL_SIZE);//EPOLL_SIZE是宏常量
.....
event_cnt = epoll_wait(epfd, ep_events, EPOLL_SIZE, -1);
//調(diào)用函數(shù)后,返回發(fā)生事件的文件描述符數(shù),同時在第二個參數(shù)指向的緩沖中保存發(fā)生事件的文件描述符集合,因此無需像select那樣插入針對所有文件描述符的循環(huán)

? epoll()有兩種不同的觸發(fā)方式,分別是條件觸發(fā)和邊緣觸發(fā)

  • 條件觸發(fā):只要輸入緩沖有數(shù)據(jù)就會一直通知該事件,即多次注冊
  • 邊緣觸發(fā):輸入緩沖收到數(shù)據(jù)時僅注冊1次該事件,即使輸入緩沖中還留有數(shù)據(jù),也不會再進行注冊

? epoll默認使用條件觸發(fā),即在有限的輸入緩沖下,多個客戶端對其進行連接那么會觸發(fā)多次wait函數(shù)。

將注冊客戶端套接字中的

event.events = EPOLLIN

改為

event.events = EPOLLIN | EPOLLET

就可以實現(xiàn)epoll的邊緣觸發(fā),即從客戶端接收數(shù)據(jù)時,僅注冊1次事件,但是需要進行額外的處理,例如將其變?yōu)榉亲枞J健?/p>

  • 多線程服務(wù)器

? 多進程服務(wù)器的瓶頸在于在切換進程時涉及到內(nèi)核態(tài)和用戶態(tài)的切換以及上下文的保存,這種操作在大量的切換過程中十分占用資源,而利用線程可以很好地避開進程切換的瓶頸,多線程的優(yōu)點為:

  • 線程的創(chuàng)建和上下文切換比進程的創(chuàng)建和上下文切換更快。
  • 線程間交換數(shù)據(jù)時無需特殊技術(shù)。

? 多線程服務(wù)器的編寫涉及到線程庫(pthread.h)以及信號量的互斥同步等操作,在實現(xiàn)起來相對復(fù)雜,這里不在贅述。

2 Epoll的兩種觸發(fā)方式

? epoll()函數(shù)具有兩種觸發(fā)模式:邊緣觸發(fā)(ET, Edge Triggered)和條件觸發(fā)(LT, Level Triggered),這兩種模式定義了epoll()函數(shù)如何響應(yīng)文件描述符的就緒事件。

  1. 條件觸發(fā)(Level Triggered, LT):
    • 在此模式下,只要文件描述符處于就緒狀態(tài)(例如,有數(shù)據(jù)可讀、可寫),epoll_wait就會通知這個事件。
    • 它更容易理解和使用,因為只要條件滿足,事件就會一直被報告。
    • 但是,這可能導(dǎo)致效率問題,尤其是在高負載時。如果應(yīng)用程序沒有讀取所有可用數(shù)據(jù),下次調(diào)用epoll_wait時,它仍然會報告相同的文件描述符,可能導(dǎo)致多余的處理。
  2. 邊緣觸發(fā)(Edge Triggered, ET):
    • 在邊緣觸發(fā)模式下,只有文件描述符狀態(tài)發(fā)生變化時(例如,從非就緒變?yōu)榫途w),epoll_wait才會通知事件。
    • 這種模式對于提高效率非常有用,因為它減少了事件的重復(fù)報告。
    • 但是,它也更難正確地使用。應(yīng)用程序必須確保每次都處理所有的可用數(shù)據(jù),因為新的數(shù)據(jù)到來不會再次觸發(fā)事件,除非文件描述符的狀態(tài)再次改變。

? 上面的描述可能比較晦澀,用一個簡單的例子來講解兩種觸發(fā)模式的特點:

? 兒子:“媽媽,我收到了500元壓歲錢?!?/em>

? 媽媽:“嗯,真棒!“

? 兒子:”我給隔壁小王買了烤鴨,花了200元?!?/em>

? 媽媽:“嗯,做的好!”

? 兒子:“媽媽,我還買了玩具,剩下50元?!?/em>

? 媽媽:”嗯,你可以自己分配!“

? 從上述對話中可以看出,兒子從收到壓歲錢開始一直向媽媽報告,這就時條件觸發(fā)的原理。如果將兒子比作輸入緩沖,壓歲錢比作輸入數(shù)據(jù),兒子的報告比作事件,可以將條件觸發(fā)簡單地描述為:

條件觸發(fā)方式中,只要輸入緩沖中有數(shù)據(jù)就會一直通知該事件。

? 例如,服務(wù)器端輸入緩沖中收到50字節(jié)的數(shù)據(jù)時,服務(wù)器端操作系統(tǒng)將通知該事件(注冊到發(fā)生變化的文件描述符)。但服務(wù)器端讀取20字節(jié)后還剩30字節(jié)的情況下,仍會注冊事件。

? 下面再看一個例子:

? 兒子:”媽媽,我收到了500元壓歲錢?!?/em>

? 媽媽:“嗯,再接再厲?!?/em>

? 兒子:“。。。。。。”

? 媽媽:“說話呀!壓歲錢呢?”

? 上面的對話就反映了邊緣觸發(fā)的工作模式,即輸入緩沖收到數(shù)據(jù)時僅注冊1次該事件,即使輸入緩沖中還留有數(shù)據(jù),也不會再進行注冊。

? epoll默認以條件觸發(fā)方式工作,select也同樣是以該方式工作的,這兩種觸發(fā)方式各有利弊,使用哪種模式取決于特定的應(yīng)用場景和對性能的需求。邊緣觸發(fā)通常用于高性能服務(wù)器,因為它可以減少系統(tǒng)調(diào)用次數(shù),而條件觸發(fā)則適用于簡單應(yīng)用或者對性能要求不高的場景。

3 線程的使用及線程同步

? 利用線程的高并發(fā)特點使客戶端的I/O操作分離,這樣可以使整個代碼更加清晰,并且能夠提高效率。

? 線程具有單獨的執(zhí)行流,因此需要單獨定義線程的main函數(shù),還需要請求操作系統(tǒng)在單獨的執(zhí)行流中執(zhí)行該函數(shù),完成該功能的函數(shù)如下:

#include<pthread.h>

int pthread_create(pthread_t * restrict thread, const pthread_attr_t * restrict attr, void * (* start_routine)(void *), void * restrict arg);
//成功時返回0,失敗時返回其他值
//thread:保存新創(chuàng)建進程ID的變量地址值
//attr:用于傳遞線程屬性的參數(shù),傳遞NULL時,創(chuàng)建默認屬性的線程
//start_routine:相當(dāng)于線程main函數(shù)的、在單獨執(zhí)行流中執(zhí)行的函數(shù)地址值
//arg:通過第三個參數(shù)傳遞調(diào)用函數(shù)時包含傳遞參數(shù)信息的變量地址值

? 線程相關(guān)代碼在編譯時需要添加-lpthread選項聲明需要連接線程庫,只有這樣才能調(diào)用頭文件pthread.h中聲明的函數(shù)

? 由于線程的執(zhí)行是非阻塞的,所以如果不使用相關(guān)函數(shù)控制線程的執(zhí)行流,必須使用sleep函數(shù)阻塞進程,讓進程等待所有線程都返回之后在返回!

? 下面介紹控制線程執(zhí)行流的函數(shù):

#include<pthread.h>

int pthread_join(pthread_t thread, void ** status);
//成功時返回0,失敗時返回其他值
//ptread:該參數(shù)值ID的線程終止后才會從該函數(shù)返回
//status:保存線程的main函數(shù)返回值的指針變量地址值

? 該函數(shù)可以阻塞進程的執(zhí)行。

? 當(dāng)多個線程同時運行時,就會產(chǎn)生臨界區(qū)的問題。

? 線程安全函數(shù)被多個線程同時調(diào)用時也不會引發(fā)問題,而非線程安全函數(shù)被同時調(diào)用時會引發(fā)問題。

? 在編譯時添加-D_REENTRANT宏使函數(shù)庫中的非線程安全函數(shù)變?yōu)槎x好的線程安全函數(shù)。

? 線程同步是在編寫有關(guān)線程程序時的核心問題,因為線程的調(diào)用順序是隨機的,并且同一個進程的線程共用一個棧區(qū),所以共享變量的使用需要格外謹慎,修改共享變量的代碼我們稱之為臨界區(qū)。對于臨界區(qū)的訪問需要通過同步和互斥來進行保護:

  • 同步:需要指定訪問同一內(nèi)存空間的線程執(zhí)行順序的情況

  • 互斥:同時訪問同一內(nèi)存空間時發(fā)生的情況

? 互斥的實質(zhì)就是上鎖機制,其依賴互斥量來實現(xiàn),下面介紹互斥量的創(chuàng)建及銷毀函數(shù):

#include<pthread.h>

int pthread_mutex_init(pthread_mutex_t * mutex, const pthread_mutexattr * attr);
int pthread_mutex_destroy(pthread_mutex_t * mutex);
//成功時返回0,失敗時返回其他值
//mutex:創(chuàng)建互斥量時傳遞保存互斥量的變量地址值,銷毀時傳遞需要銷毀的互斥量地址值
//attr:傳遞即將創(chuàng)建的互斥量屬性,沒有特別需要指定的屬性時傳遞NULL

? 接下來介紹利用互斥量鎖住或釋放臨界區(qū)時使用的函數(shù):

int pthread_mutex_lock(pthread_mutex_t * mutex);
int pthread_mutex_unlock(pthread_mutex_t * mutex);
//成功時返回0,失敗時返回其他值

? 函數(shù)用法:

pthread_mutex_lock(&mutex);
//臨界區(qū)的開始
//....
//臨界區(qū)的結(jié)束
pthread_mutex_unlock(&mutex);

? 為了提高效率,我們應(yīng)該最大限度減少互斥量lock、unlock函數(shù)的調(diào)用次數(shù)

? 同步需要使用信號量(semaphore)來完成,下面介紹信號量創(chuàng)建及銷毀的方法。

#include<semaphore.h>

int sem_init(sem_t * sem, int pshared, unsigned int value);
int sem_destroy(sem_t * sem);
//成功時返回0,失敗時返回其他值
//sem:傳遞信號量時傳遞保存信號量的變量地址,銷毀時傳遞需要銷毀的信號變量地址值
//pshared:傳遞其他值時,創(chuàng)建可由多個進程共享的信號量;傳遞0時,創(chuàng)建只允許1個進程內(nèi)部使用的信號量
//value:指定新創(chuàng)建的信號量初始值

? 信號量中相當(dāng)于互斥量lock、unlock的函數(shù),其中post讓信號量增加1,wait讓信號量減少1。

int sem_post(sem_t * sem);
int sem_wait(sem_t * sem);
//成功時返0,失敗時返回其他值

二、代碼實現(xiàn)

? 使用epoll實現(xiàn)聊天服務(wù)器的代碼不是很復(fù)雜,因為最核心的功能——數(shù)據(jù)的收發(fā)已經(jīng)被epoll函數(shù)很好地實現(xiàn)了,我們所要做的就是調(diào)用該函數(shù)并且在此基礎(chǔ)上添加一些簡單的功能。

chat_clnt.c:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <pthread.h>

#define BUF_SIZE 100
#define NAME_SIZE 20

void *send_msg(void *arg);
void *recv_msg(void *arg);
void error_handling(char *msg);

char name[NAME_SIZE] = "[DEFAULT]";
char msg[BUF_SIZE];

int main(int argc, char *argv[])
{
	int sock;
	struct sockaddr_in serv_addr;
	pthread_t snd_thread, rcv_thread;
	void *thread_return;
	if (argc != 4)
	{
		printf("Usage : %s <IP> <port> <name>\n", argv[0]);
		exit(1);
	}

	sprintf(name, "[%s]", argv[3]);
	sock = socket(PF_INET, SOCK_STREAM, 0);

	memset(&serv_addr, 0, sizeof(serv_addr));
	serv_addr.sin_family = AF_INET;
	serv_addr.sin_addr.s_addr = inet_addr(argv[1]);
	serv_addr.sin_port = htons(atoi(argv[2]));

	if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) == -1)
		error_handling("connect() error");

	pthread_create(&snd_thread, NULL, send_msg, (void *)&sock);
	pthread_create(&rcv_thread, NULL, recv_msg, (void *)&sock);
	pthread_join(snd_thread, &thread_return);
	pthread_join(rcv_thread, &thread_return);
	close(sock);
	return 0;
}

void *send_msg(void *arg) // send thread main
{
	int sock = *((int *)arg);
	char name_msg[NAME_SIZE + BUF_SIZE];
	while (1)
	{
		fgets(msg, BUF_SIZE, stdin);
		if (!strcmp(msg, "q\n") || !strcmp(msg, "Q\n"))
		{
			close(sock);
			exit(0);
		}
		sprintf(name_msg, "%s %s", name, msg);
		write(sock, name_msg, strlen(name_msg));
	}
	return NULL;
}

void *recv_msg(void *arg) // read thread main
{
	int sock = *((int *)arg);
	char name_msg[NAME_SIZE + BUF_SIZE];
	int str_len;
	while (1)
	{
		str_len = read(sock, name_msg, NAME_SIZE + BUF_SIZE - 1);
		if (str_len == -1)
			return (void *)-1;
		name_msg[str_len] = 0;
		fputs(name_msg, stdout);
	}
	return NULL;
}

void error_handling(char *msg)
{
	fputs(msg, stderr);
	fputc('\n', stderr);
	exit(1);
}

clnt_serv.c:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <pthread.h>

#define MAX_CLNT 256
#define BUF_SIZE 100
#define EPOLL_SIZE 50
void setnonblockingmode(int fd);
void error_handling(char *buf);

int main(int argc, char *argv[])
{
	int serv_sock, clnt_sock;
	struct sockaddr_in serv_adr, clnt_adr;
	socklen_t adr_sz;
	int str_len, i, j;
	char buf[BUF_SIZE];

	struct epoll_event *ep_events;
	struct epoll_event event;
	int epfd, event_cnt;

	int clnt_socks[MAX_CLNT];
	int clnt_cnt = 0;
	pthread_mutex_t mutex;

	if (argc != 2)
	{
		printf("Usage : %s <port>\n", argv[0]);
		exit(1);
	}

	serv_sock = socket(PF_INET, SOCK_STREAM, 0);
	memset(&serv_adr, 0, sizeof(serv_adr));
	serv_adr.sin_family = AF_INET;
	serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);
	serv_adr.sin_port = htons(atoi(argv[1]));

	if (bind(serv_sock, (struct sockaddr *)&serv_adr, sizeof(serv_adr)) == -1)
		error_handling("bind() error");
	if (listen(serv_sock, 5) == -1)
		error_handling("listen() error");

	epfd = epoll_create(EPOLL_SIZE);
	ep_events = malloc(sizeof(struct epoll_event) * EPOLL_SIZE);

	setnonblockingmode(serv_sock);
	event.events = EPOLLIN;
	event.data.fd = serv_sock;
	epoll_ctl(epfd, EPOLL_CTL_ADD, serv_sock, &event);

	while (1)
	{
		event_cnt = epoll_wait(epfd, ep_events, EPOLL_SIZE, -1);
		if (event_cnt == -1)
		{
			puts("epoll_wait() error");
			break;
		}

		// puts("return epoll_wait");
		for (i = 0; i < event_cnt; i++)
		{
			if (ep_events[i].data.fd == serv_sock)//client comes!
			{
				adr_sz = sizeof(clnt_adr);
				clnt_sock = accept(serv_sock, (struct sockaddr *)&clnt_adr, &adr_sz);
				setnonblockingmode(clnt_sock);
				event.events = EPOLLIN | EPOLLET;
				event.data.fd = clnt_sock;
				pthread_mutex_lock(&mutex);
				clnt_socks[clnt_cnt++] = clnt_sock;
				pthread_mutex_unlock(&mutex);

				epoll_ctl(epfd, EPOLL_CTL_ADD, clnt_sock, &event);
				printf("connected client: %d \n", clnt_sock);
			}
			else
			{
				while (1)
				{
					str_len = read(ep_events[i].data.fd, buf, BUF_SIZE);
					if (str_len == 0) // close request!
					{
						epoll_ctl(epfd, EPOLL_CTL_DEL, ep_events[i].data.fd, NULL);
						close(ep_events[i].data.fd);
						pthread_mutex_lock(&mutex);
						for (j = 0; j < clnt_cnt; j++)
						{
							if (ep_events[i].data.fd == clnt_socks[j])
							{
								while (j < clnt_cnt - 1)
								{
									clnt_socks[j] = clnt_socks[j + 1];
									j++;
								}
								break;
							}
						}
						clnt_cnt--;
						pthread_mutex_unlock(&mutex);
						printf("closed client: %d \n", ep_events[i].data.fd);
						break;
					}
					else if (str_len < 0)
					{
						if (errno == EAGAIN)
							break;
					}
					else
					{
						pthread_mutex_lock(&mutex);
						for (j = 0; j < clnt_cnt; j++)
						{
							if (clnt_socks[j] != ep_events[i].data.fd)
								write(clnt_socks[j], buf, str_len);
						}
						pthread_mutex_unlock(&mutex);
					}
				}
			}
		}
	}
	close(serv_sock);
	close(epfd);
	return 0;
}

void setnonblockingmode(int fd)
{
	int flag = fcntl(fd, F_GETFL, 0);
	fcntl(fd, F_SETFL, flag | O_NONBLOCK);
}
void error_handling(char *buf)
{
	fputs(buf, stderr);
	fputc('\n', stderr);
	exit(1);
}

Makefile:

CC = gcc
CFLAGS = -Wall -pthread
TARGET_SERVER = chat_serv
TARGET_CLIENT = chat_clnt

all: $(TARGET_SERVER) $(TARGET_CLIENT)

$(TARGET_SERVER): chat_serv.o
	$(CC) $(CFLAGS) -o $(TARGET_SERVER) chat_serv.o

$(TARGET_CLIENT): chat_clnt.o
	$(CC) $(CFLAGS) -o $(TARGET_CLIENT) chat_clnt.o

chat_serv.o: chat_serv.c
	$(CC) $(CFLAGS) -c chat_serv.c

chat_clnt.o: chat_clnt.c
	$(CC) $(CFLAGS) -c chat_clnt.c

clean:
	rm -f *.o $(TARGET_SERVER) $(TARGET_CLIENT)

三、運行結(jié)果展示

epoll并發(fā)服務(wù)器的實現(xiàn),服務(wù)器,linux,c語言,網(wǎng)絡(luò)

當(dāng)有客戶接入聊天室的時候會服務(wù)器會顯示其占用的文件描述符;每一個客戶在發(fā)送消息是會在其發(fā)送的消息前附加上其昵稱,這樣更容易識別是哪一個客戶發(fā)來的消息。文章來源地址http://www.zghlxwxcb.cn/news/detail-773867.html

到了這里,關(guān)于epoll并發(fā)服務(wù)器的實現(xiàn)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實不符,請點擊違法舉報進行投訴反饋,一經(jīng)查實,立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費用

相關(guān)文章

  • TCP高并發(fā)服務(wù)器簡介(select、poll、epoll實現(xiàn)與區(qū)別)

    TCP高并發(fā)服務(wù)器簡介(select、poll、epoll實現(xiàn)與區(qū)別)

    一、創(chuàng)建套接字(socket函數(shù)): 二、填充服務(wù)器的網(wǎng)絡(luò)信息結(jié)構(gòu)體: 三、套接字和服務(wù)器的網(wǎng)絡(luò)信息結(jié)構(gòu)體進行綁定(bind函數(shù)): 四、套接字設(shè)置成被動監(jiān)聽(listen函數(shù)): 五、創(chuàng)建要監(jiān)聽的文件描述符集合: 使用select函數(shù)后,會將 沒有就緒的文件描述符 在集合中 去除

    2024年01月19日
    瀏覽(23)
  • C/S架構(gòu)學(xué)習(xí)之使用epoll實現(xiàn)TCP特大型并發(fā)服務(wù)器

    epoll實現(xiàn)TCP特大型并發(fā)服務(wù)器的流程: 一、創(chuàng)建套接字(socket函數(shù)): 通信域 選擇 IPV4 網(wǎng)絡(luò)協(xié)議、套接字類型選擇 流式 ; 二、填充服務(wù)器和客戶機的網(wǎng)絡(luò)信息結(jié)構(gòu)體: 1.分別定義服務(wù)器網(wǎng)絡(luò)信息結(jié)構(gòu)體變量 serveraddr 和客戶機網(wǎng)絡(luò)信息結(jié)構(gòu)體變量 clientaddr ; 2.分別求出服務(wù)

    2024年02月08日
    瀏覽(25)
  • epoll多路復(fù)用_并發(fā)服務(wù)器

    應(yīng)用程序: 驅(qū)動程序:

    2024年02月15日
    瀏覽(19)
  • 多進程并發(fā)TCP服務(wù)器模型(含客戶端)(網(wǎng)絡(luò)編程 C語言實現(xiàn))

    摘要 :大家都知道不同pc間的通信需要用到套接字sockte來實現(xiàn),但是服務(wù)器一次只能收到一個客戶端發(fā)來的消息,所以為了能讓服務(wù)器可以接收多個客戶端的連接與消息的傳遞,我們就引入了多進程并發(fā)這樣一個概念。聽名字就可以知道--需要用到進程,當(dāng)然也有多線程并發(fā)

    2024年02月17日
    瀏覽(101)
  • 【Linux網(wǎng)絡(luò)編程】TCP并發(fā)服務(wù)器的實現(xiàn)(IO多路復(fù)用select)

    【Linux網(wǎng)絡(luò)編程】TCP并發(fā)服務(wù)器的實現(xiàn)(IO多路復(fù)用select)

    服務(wù)器模型主要分為兩種, 循環(huán)服務(wù)器 和 并發(fā)服務(wù)器 。 循環(huán)服務(wù)器 : 在同一時間只能處理一個客戶端的請求。 并發(fā)服務(wù)器 : 在同一時間內(nèi)能同時處理多個客戶端的請求。 TCP的服務(wù)器默認的就是一個循環(huán)服務(wù)器,原因是有兩個阻塞 accept函數(shù) 和recv函數(shù) 之間會相互影響。

    2024年02月03日
    瀏覽(98)
  • Linux網(wǎng)絡(luò)編程:多路I/O轉(zhuǎn)接服務(wù)器(select poll epoll)

    Linux網(wǎng)絡(luò)編程:多路I/O轉(zhuǎn)接服務(wù)器(select poll epoll)

    文章目錄: 一:select 1.基礎(chǔ)API? select函數(shù) 思路分析 select優(yōu)缺點 2.server.c 3.client.c 二:poll 1.基礎(chǔ)API? poll函數(shù)? poll優(yōu)缺點 read函數(shù)返回值 突破1024 文件描述符限制 2.server.c 3.client.c 三:epoll 1.基礎(chǔ)API epoll_create創(chuàng)建? ?epoll_ctl操作? epoll_wait阻塞 epoll實現(xiàn)多路IO轉(zhuǎn)接思路 epoll優(yōu)缺點

    2024年02月11日
    瀏覽(20)
  • 用反應(yīng)器模式和epoll構(gòu)建百萬并發(fā)服務(wù)器

    用反應(yīng)器模式和epoll構(gòu)建百萬并發(fā)服務(wù)器

    此處的百萬并發(fā)指的是可以建立至少100w個客戶端連接,不考慮業(yè)務(wù)處理。 反應(yīng)器模式下的epoll相比起普通的epoll不同在于:普通的epoll在獲取到就緒狀態(tài)的event結(jié)構(gòu)體之后,先判斷是什么類型的fd,再進行操作。而reactor先判斷是什么類型的事件,再進行操作。本文從頭用react

    2024年02月02日
    瀏覽(25)
  • Linux多路IO復(fù)用技術(shù)——epoll詳解與一對多服務(wù)器實現(xiàn)

    Linux多路IO復(fù)用技術(shù)——epoll詳解與一對多服務(wù)器實現(xiàn)

    本文詳細介紹了Linux中epoll模型的優(yōu)化原理和使用方法,以及如何利用epoll模型實現(xiàn)簡易的一對多服務(wù)器。通過對epoll模型的優(yōu)化和相關(guān)接口的解釋,幫助讀者理解epoll模型的工作原理和優(yōu)缺點,同時附帶代碼實現(xiàn)和圖解說明。

    2024年02月05日
    瀏覽(25)
  • linux并發(fā)服務(wù)器 —— linux網(wǎng)絡(luò)編程(七)

    linux并發(fā)服務(wù)器 —— linux網(wǎng)絡(luò)編程(七)

    C/S結(jié)構(gòu) - 客戶機/服務(wù)器;采用兩層結(jié)構(gòu),服務(wù)器負責(zé)數(shù)據(jù)的管理,客戶機負責(zé)完成與用戶的交互;C/S結(jié)構(gòu)中,服務(wù)器 - 后臺服務(wù),客戶機 - 前臺功能; 優(yōu)點 1. 充分發(fā)揮客戶端PC處理能力,先在客戶端處理再提交服務(wù)器,響應(yīng)速度快; 2. 操作界面好看,滿足個性化需求; 3.

    2024年02月09日
    瀏覽(23)
  • Linux學(xué)習(xí)之網(wǎng)絡(luò)編程3(高并發(fā)服務(wù)器)

    Linux學(xué)習(xí)之網(wǎng)絡(luò)編程3(高并發(fā)服務(wù)器)

    Linux網(wǎng)絡(luò)編程我是看視頻學(xué)的,Linux網(wǎng)絡(luò)編程,看完這個視頻大概網(wǎng)絡(luò)編程的基礎(chǔ)差不多就掌握了。這個系列是我看這個Linux網(wǎng)絡(luò)編程視頻寫的筆記總結(jié)。 問題: 根據(jù)上一個筆記,我們可以寫出一個簡單的服務(wù)端和客戶端通信,但是我們發(fā)現(xiàn)一個問題——服務(wù)器只能連接一個

    2024年02月01日
    瀏覽(28)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包