Linux下网络socket编程——实现服务器(select)与多个客户端通信
一、關(guān)于socket通信
服務(wù)器端工作流程:
- 調(diào)用 socket() 函數(shù)創(chuàng)建套接字 用 bind() 函數(shù)將創(chuàng)建的套接字與服務(wù)端IP地址綁定
- 調(diào)用listen()函數(shù)監(jiān)聽(tīng)socket() 函數(shù)創(chuàng)建的套接字,等待客戶端連接 當(dāng)客戶端請(qǐng)求到來(lái)之后
- 調(diào)用 accept()函數(shù)接受連接請(qǐng)求,返回一個(gè)對(duì)應(yīng)于此連接的新的套接字,做好通信準(zhǔn)備
- 調(diào)用 write()/read() 函數(shù)和 send()/recv()函數(shù)進(jìn)行數(shù)據(jù)的讀寫(xiě),通過(guò) accept() 返回的套接字和客戶端進(jìn)行通信 關(guān)閉socket(close)
客戶端工作流程:
- 調(diào)用 socket() 函數(shù)創(chuàng)建套接字
- 調(diào)用 connect() 函數(shù)連接服務(wù)端
- 調(diào)用write()/read() 函數(shù)或者 send()/recv() 函數(shù)進(jìn)行數(shù)據(jù)的讀寫(xiě)
- 關(guān)閉socket(close)
?
二、用select實(shí)現(xiàn)服務(wù)器端編程:
select函數(shù)樓主在之前文章中(select函數(shù)用法)已經(jīng)提及,不在多做綴述。下面貼上服務(wù)器端代碼servce.c
#include <stdio.h> #include <netinet/in.h> //for souockaddr_in #include <sys/types.h> #include <sys/socket.h> #include <errno.h> #include <stdlib.h>#include <arpa/inet.h>//for select #include <sys/time.h> #include <sys/types.h> #include <unistd.h> #include <sys/select.h>#include <strings.h> //for bzero #include <string.h>#define BUFF_SIZE 1024 #define backlog 7 #define ser_port 11277 #define CLI_NUM 3int client_fds[CLI_NUM];int main(int agrc,char **argv) {int ser_souck_fd;int i; char input_message[BUFF_SIZE];char resv_message[BUFF_SIZE];struct sockaddr_in ser_addr;ser_addr.sin_family= AF_INET; //IPV4ser_addr.sin_port = htons(ser_port); ser_addr.sin_addr.s_addr = INADDR_ANY; //指定的是所有地址//creat socketif( (ser_souck_fd = socket(AF_INET,SOCK_STREAM,0)) < 0 ){perror("creat failure");return -1;} //bind soucketif(bind(ser_souck_fd, (const struct sockaddr *)&ser_addr,sizeof(ser_addr)) < 0){perror("bind failure");return -1;}//listenif(listen(ser_souck_fd, backlog) < 0) {perror("listen failure"); return -1;}//fd_setfd_set ser_fdset;int max_fd=1;struct timeval mytime;printf("wait for client connnect!\n");while(1){mytime.tv_sec=27;mytime.tv_usec=0;FD_ZERO(&ser_fdset);//add standard inputFD_SET(0,&ser_fdset);if(max_fd < 0){max_fd=0; }//add serverceFD_SET(ser_souck_fd,&ser_fdset);if(max_fd < ser_souck_fd){max_fd = ser_souck_fd;}//add client for(i=0;i<CLI_NUM;i++) //用數(shù)組定義多個(gè)客戶端fd{if(client_fds[i]!=0) {FD_SET(client_fds[i],&ser_fdset);if(max_fd < client_fds[i]){max_fd = client_fds[i]; }}}//select多路復(fù)用int ret = select(max_fd + 1, &ser_fdset, NULL, NULL, &mytime);if(ret < 0) { perror("select failure\n"); continue; } else if(ret == 0){printf("time out!");continue;}else{if(FD_ISSET(0,&ser_fdset)) //標(biāo)準(zhǔn)輸入是否存在于ser_fdset集合中(也就是說(shuō),檢測(cè)到輸入時(shí),做如下事情){printf("send message to");bzero(input_message,BUFF_SIZE);fgets(input_message,BUFF_SIZE,stdin);for(i=0;i<CLI_NUM;i++){if(client_fds[i] != 0){printf("client_fds[%d]=%d\n", i, client_fds[i]);send(client_fds[i], input_message, BUFF_SIZE, 0);}}}if(FD_ISSET(ser_souck_fd, &ser_fdset)) {struct sockaddr_in client_address;socklen_t address_len;int client_sock_fd = accept(ser_souck_fd,(struct sockaddr *)&client_address, &address_len);if(client_sock_fd > 0){int flags=-1;//一個(gè)客戶端到來(lái)分配一個(gè)fd,CLI_NUM=3,則最多只能有三個(gè)客戶端,超過(guò)4以后跳出for循環(huán),flags重新被賦值為-1for(i=0;i<CLI_NUM;i++){if(client_fds[i] == 0){flags=i; client_fds[i] = client_sock_fd;break;}}if (flags >= 0){printf("new user client[%d] add sucessfully!\n",flags);}else //flags=-1{ char full_message[]="the client is full!can't join!\n";bzero(input_message,BUFF_SIZE);strncpy(input_message, full_message,100);send(client_sock_fd, input_message, BUFF_SIZE, 0);}} }}//deal with the messagefor(i=0; i<CLI_NUM; i++){if(client_fds[i] != 0){if(FD_ISSET(client_fds[i],&ser_fdset)){bzero(resv_message,BUFF_SIZE);int byte_num=read(client_fds[i],resv_message,BUFF_SIZE);if(byte_num > 0){printf("message form client[%d]:%s\n", i, resv_message);}else if(byte_num < 0){printf("rescessed error!");}//某個(gè)客戶端退出else //cancel fdset and set fd=0{printf("clien[%d] exit!\n",i);FD_CLR(client_fds[i], &ser_fdset);client_fds[i] = 0;// printf("clien[%d] exit!\n",i);continue; //這里如果用break的話一個(gè)客戶端退出會(huì)造成服務(wù)器也退出。 }}}} }return 0; }select實(shí)現(xiàn)多路復(fù)用,多路復(fù)用,顧名思義,就是說(shuō)各做各的事,標(biāo)準(zhǔn)輸入事件到來(lái),有相關(guān)函數(shù)處理。服務(wù)器處理服務(wù)器的事件,客戶端到來(lái)時(shí)有相關(guān)函數(shù)對(duì)其進(jìn)行處理,通過(guò)select遍歷各fd的讀寫(xiě)情況,就不用擔(dān)心阻塞了。
三、用epoll實(shí)現(xiàn)客戶端編程:
1、客戶端程序(epoll_client.c):
#include<stdio.h> #include<stdlib.h> #include<netinet/in.h> #include<sys/socket.h> #include<arpa/inet.h> #include<string.h> #include<unistd.h> #include <sys/epoll.h> #include <errno.h> #include <fcntl.h>#define BUFFER_SIZE 1024 int main(int argc, const char * argv[]) { int i,n;int connfd,sockfd;struct epoll_event ev,events[20]; //ev用于注冊(cè)事件,數(shù)組用于回傳要處理的事件int epfd=epoll_create(256);//創(chuàng)建一個(gè)epoll的句柄,其中256為你epoll所支持的最大句柄數(shù)struct sockaddr_in client_addr;struct sockaddr_in server_addr; server_addr.sin_family = AF_INET; server_addr.sin_port = htons(11277); server_addr.sin_addr.s_addr =INADDR_ANY; bzero(&(server_addr.sin_zero), 8); int server_sock_fd = socket(AF_INET, SOCK_STREAM, 0); ev.data.fd=server_sock_fd;//設(shè)置與要處理的事件相關(guān)的文件描述符ev.events=EPOLLIN|EPOLLET;//設(shè)置要處理的事件類(lèi)型epoll_ctl(epfd,EPOLL_CTL_ADD,server_sock_fd,&ev);//注冊(cè)epoll事件if(server_sock_fd == -1) { perror("socket error"); return 1; } char recv_msg[BUFFER_SIZE]; char input_msg[BUFFER_SIZE]; if(connect(server_sock_fd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr_in)) == 0) { for(;;){int nfds=epoll_wait(epfd,events,20,500);//等待epoll事件的發(fā)生for(i=0;i<nfds;++i){ if(events[i].events&EPOLLOUT) //有數(shù)據(jù)發(fā)送,寫(xiě)socket{bzero(input_msg, BUFFER_SIZE); fgets(input_msg, BUFFER_SIZE, stdin); sockfd = events[i].data.fd;write(sockfd, recv_msg, n);ev.data.fd=sockfd;ev.events=EPOLLIN|EPOLLET;epoll_ctl(epfd,EPOLL_CTL_ADD,sockfd,&ev);} else if(events[i].events&EPOLLIN)//有數(shù)據(jù)到來(lái),讀socket{bzero(recv_msg, BUFFER_SIZE);if((n = read(server_sock_fd, recv_msg, BUFFER_SIZE)) <0 ){printf("read error!");}ev.data.fd=server_sock_fd;ev.events=EPOLLOUT|EPOLLET;printf("%s\n",recv_msg);}} }} return 0; }2、關(guān)于epoll函數(shù):
相比于select,epoll最大的好處在于它不會(huì)隨著監(jiān)聽(tīng)fd數(shù)目的增長(zhǎng)而降低效率。因?yàn)樵趦?nèi)核中的select實(shí)現(xiàn)中,它是采用輪詢來(lái)處理的,輪詢的fd數(shù)目越多,自然耗時(shí)越多。并且,在linux/posix_types.h頭文件有這樣的聲明:
#define __FD_SETSIZE 1024
表示select最多同時(shí)監(jiān)聽(tīng)1024個(gè)fd
一共三個(gè)函數(shù):
1、 int epoll_create (int size); 創(chuàng)建一個(gè)epoll的句柄size用來(lái)告訴內(nèi)核這個(gè)監(jiān)聽(tīng)的數(shù)目一共有多大。這個(gè)參數(shù)不同于select()中的第一個(gè)參數(shù),給出最大監(jiān)聽(tīng)的fd+1的值。需要注意的是,當(dāng)創(chuàng)建好epoll句柄后,它就是會(huì)占用一個(gè)fd值,在linux下如果查看/proc/進(jìn)程id/fd/,是能夠看到這個(gè)fd的,所以在使用完epoll后,必須調(diào)用close()關(guān)閉,否則可能導(dǎo)致fd被耗盡。
2、 int epoll_ctl (int epfd , int op, int fd, struct epoll_event *event);epoll的事件注冊(cè)函數(shù),它不同與select()是在監(jiān)聽(tīng)事件時(shí)告訴內(nèi)核要監(jiān)聽(tīng)什么類(lèi)型的事件,而是在這里先注冊(cè)要監(jiān)聽(tīng)的事件類(lèi)型。第一個(gè)參數(shù)是epoll_create()的返回值,第二個(gè)參數(shù)表示動(dòng)作,用三個(gè)宏來(lái)表示:
EPOLL_CTL_ADD:注冊(cè)新的fd到epfd中;
EPOLL_CTL_MOD:修改已經(jīng)注冊(cè)的fd的監(jiān)聽(tīng)事件;
EPOLL_CTL_DEL:從epfd中刪除一個(gè)fd;
第三個(gè)參數(shù)是需要監(jiān)聽(tīng)的fd
第四個(gè)參數(shù)是告訴內(nèi)核需要監(jiān)聽(tīng)什么事,struct epoll_event結(jié)構(gòu)如下:
struct epoll_event{__uint32_t events; /* Epoll events */epoll_data_t data; /* User data variable */ };typedef union epoll_data{void *ptr;int fd;__uint32_t u32;__uint64_t u64; } epoll_data_t;
events可以是以下幾個(gè)宏的集合:
- EPOLLIN :表示對(duì)應(yīng)的文件描述符可以讀(包括對(duì)端SOCKET正常關(guān)閉);
- EPOLLOUT:表示對(duì)應(yīng)的文件描述符可以寫(xiě);
- EPOLLPRI:表示對(duì)應(yīng)的文件描述符有緊急的數(shù)據(jù)可讀(這里應(yīng)該表示有帶外數(shù)據(jù)到來(lái));
- EPOLLERR:表示對(duì)應(yīng)的文件描述符發(fā)生錯(cuò)誤;
- EPOLLHUP:表示對(duì)應(yīng)的文件描述符被掛斷;
- EPOLLET: 將EPOLL設(shè)為邊緣觸發(fā)(Edge Triggered)模式,這是相對(duì)于水平觸發(fā)(Level Triggered)來(lái)說(shuō)的。
- EPOLLONESHOT:只監(jiān)聽(tīng)一次事件,當(dāng)監(jiān)聽(tīng)完這次事件之后,如果還需要繼續(xù)監(jiān)聽(tīng)這個(gè)socket的話,需要再次把這個(gè)socket加入到EPOLL隊(duì)列里
等待事件的產(chǎn)生,類(lèi)似于select()調(diào)用。
參數(shù)events用來(lái)從內(nèi)核得到事件的集合,
maxevents告之內(nèi)核這個(gè)events有多大,這個(gè) maxevents的值不能大于創(chuàng)建epoll_create()時(shí)的size,
參數(shù)timeout是超時(shí)時(shí)間(毫秒,0會(huì)立即返回,-1將不確定,也有說(shuō)法說(shuō)是永久阻塞)。該函數(shù)返回需要處理的事件數(shù)目,如返回0表示已超時(shí)。
使用步驟:
<1>首先通過(guò)create_epoll(int maxfds)來(lái)創(chuàng)建一個(gè)epoll的句柄,其中maxfds為你epoll所支持的最大句柄數(shù)。這個(gè)函數(shù)會(huì)返回一個(gè)新的epoll句柄,之后的所有操作將通過(guò)這個(gè)句柄來(lái)進(jìn)行操作。在用完之后,記得用close()來(lái)關(guān)閉這個(gè)創(chuàng)建出來(lái)的epoll句柄。
<2>然后每一幀的調(diào)用epoll_wait (int epfd, epoll_event events, int max events, int timeout) 來(lái)查詢所有的網(wǎng)絡(luò)接口。
<3>kdpfd為用epoll_create創(chuàng)建之后的句柄,events是一個(gè)epoll_event*的指針,當(dāng)epoll_wait這個(gè)函數(shù)操作成功之后,epoll_events里面將儲(chǔ)存所有的讀寫(xiě)事件。max_events是當(dāng)前需要監(jiān)聽(tīng)的所有socket句柄數(shù)。最后一個(gè)timeout是 epoll_wait的超時(shí),為0的時(shí)候表示馬上返回,為-1的時(shí)候表示一直等下去,直到有事件范圍,為任意正整數(shù)的時(shí)候表示等這么長(zhǎng)的時(shí)間,如果一直沒(méi)有事件,則返回。一般如果網(wǎng)絡(luò)主循環(huán)是單獨(dú)的線程的話,可以用-1來(lái)等,這樣可以保證一些效率,如果是和主邏輯在同一個(gè)線程的話,則可以用0來(lái)保證主循環(huán)的效率。 epoll_wait返回之后應(yīng)該是一個(gè)循環(huán),遍歷所有的事件。
基本上都是如下的框架:
for( ; ; ){nfds = epoll_wait(epfd,events,20,500);for(i=0;i<nfds;++i){if(events[i].data.fd==listenfd) //有新的連接{connfd = accept(listenfd,(sockaddr *)&clientaddr, &clilen); //accept這個(gè)連接ev.data.fd=connfd;ev.events=EPOLLIN|EPOLLET;epoll_ctl(epfd,EPOLL_CTL_ADD,connfd,&ev); //將新的fd添加到epoll的監(jiān)聽(tīng)隊(duì)列中}else if( events[i].events&EPOLLIN ) //接收到數(shù)據(jù),讀socket{n = read(sockfd, line, MAXLINE)) < 0 //讀ev.data.ptr = md; //md為自定義類(lèi)型,添加數(shù)據(jù)ev.events=EPOLLOUT|EPOLLET;epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev);//修改標(biāo)識(shí)符,等待下一個(gè)循環(huán)時(shí)發(fā)送數(shù)據(jù),異步處理的精髓}else if(events[i].events&EPOLLOUT) //有數(shù)據(jù)待發(fā)送,寫(xiě)socket{struct myepoll_data* md = (myepoll_data*)events[i].data.ptr; //取數(shù)據(jù)sockfd = md->fd;send( sockfd, md->ptr, strlen((char*)md->ptr), 0 ); //發(fā)送數(shù)據(jù)ev.data.fd=sockfd;ev.events=EPOLLIN|EPOLLET;epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev); //修改標(biāo)識(shí)符,等待下一個(gè)循環(huán)時(shí)接收數(shù)據(jù)}else{//其他的處理}}}
?
轉(zhuǎn)載于:https://www.cnblogs.com/wuyepeng/p/9726771.html
總結(jié)
以上是生活随笔為你收集整理的Linux下网络socket编程——实现服务器(select)与多个客户端通信的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: python基础学习——函数和方法的区别
- 下一篇: Linux su切换用户后命令提示符变为