天河网站(建设信科网络),wordpress禁止加载谷歌字体,大兴网站建设设计公司,做网站推广 seo的一、关于socket通信 服务器端工作流程#xff1a;
调用 socket() 函数创建套接字 用 bind() 函数将创建的套接字与服务端IP地址绑定调用listen()函数监听socket() 函数创建的套接字#xff0c;等待客户端连接 当客户端请求到来之后调用 accept()函数接受连接请求#xff0c…
一、关于socket通信 服务器端工作流程
调用 socket() 函数创建套接字 用 bind() 函数将创建的套接字与服务端IP地址绑定调用listen()函数监听socket() 函数创建的套接字等待客户端连接 当客户端请求到来之后调用 accept()函数接受连接请求返回一个对应于此连接的新的套接字做好通信准备调用 write()/read() 函数和 send()/recv()函数进行数据的读写通过 accept() 返回的套接字和客户端进行通信 关闭socketclose
客户端工作流程
调用 socket() 函数创建套接字调用 connect() 函数连接服务端调用write()/read() 函数或者 send()/recv() 函数进行数据的读写关闭socket(close)二、用select实现服务器端编程
select函数楼主在之前文章中select函数用法已经提及不在多做缀述。下面贴上服务器端代码servce.c 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 #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 3 int 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; //IPV4 ser_addr.sin_port htons(ser_port); ser_addr.sin_addr.s_addr INADDR_ANY; //指定的是所有地址 //creat socket if( (ser_souck_fd socket(AF_INET,SOCK_STREAM,0)) 0 ) { perror(creat failure); return -1; } //bind soucket if(bind(ser_souck_fd, (const struct sockaddr *)ser_addr,sizeof(ser_addr)) 0) { perror(bind failure); return -1; } //listen if(listen(ser_souck_fd, backlog) 0) { perror(listen failure); return -1; } //fd_set fd_set ser_fdset; int max_fd1; struct timeval mytime; printf(wait for client connnect!\n); while(1) { mytime.tv_sec27; mytime.tv_usec0; FD_ZERO(ser_fdset); //add standard input FD_SET(0,ser_fdset); if(max_fd 0) { max_fd0; } //add serverce FD_SET(ser_souck_fd,ser_fdset); if(max_fd ser_souck_fd) { max_fd ser_souck_fd; } //add client for(i0;iCLI_NUM;i) //用数组定义多个客户端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多路复用 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)) //标准输入是否存在于ser_fdset集合中也就是说检测到输入时做如下事情 { printf(send message to); bzero(input_message,BUFF_SIZE); fgets(input_message,BUFF_SIZE,stdin); for(i0;iCLI_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; //一个客户端到来分配一个fdCLI_NUM3则最多只能有三个客户端超过4以后跳出for循环flags重新被赋值为-1 for(i0;iCLI_NUM;i) { if(client_fds[i] 0) { flagsi; 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!cant 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 message for(i0; iCLI_NUM; i) { if(client_fds[i] ! 0) { if(FD_ISSET(client_fds[i],ser_fdset)) { bzero(resv_message,BUFF_SIZE); int byte_numread(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!); } //某个客户端退出 else //cancel fdset and set fd0 { 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的话一个客户端退出会造成服务器也退出。 } } } } } return 0; }
select实现多路复用多路复用顾名思义就是说各做各的事标准输入事件到来有相关函数处理。服务器处理服务器的事件客户端到来时有相关函数对其进行处理通过select遍历各fd的读写情况就不用担心阻塞了。
三、用epoll实现客户端编程
1、客户端程序epoll_client.c 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 #includestdio.h #includestdlib.h #includenetinet/in.h #includesys/socket.h #includearpa/inet.h #includestring.h #includeunistd.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用于注册事件,数组用于回传要处理的事件 int epfdepoll_create(256);//创建一个epoll的句柄其中256为你epoll所支持的最大句柄数 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.fdserver_sock_fd;//设置与要处理的事件相关的文件描述符 ev.eventsEPOLLIN|EPOLLET;//设置要处理的事件类型 epoll_ctl(epfd,EPOLL_CTL_ADD,server_sock_fd,ev);//注册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 nfdsepoll_wait(epfd,events,20,500);//等待epoll事件的发生 for(i0;infds;i) { if(events[i].eventsEPOLLOUT) //有数据发送写socket { bzero(input_msg, BUFFER_SIZE); fgets(input_msg, BUFFER_SIZE, stdin); sockfd events[i].data.fd; write(sockfd, recv_msg, n); ev.data.fdsockfd; ev.eventsEPOLLIN|EPOLLET; epoll_ctl(epfd,EPOLL_CTL_ADD,sockfd,ev); } else if(events[i].eventsEPOLLIN)//有数据到来读socket { bzero(recv_msg, BUFFER_SIZE); if((n read(server_sock_fd, recv_msg, BUFFER_SIZE)) 0 ) { printf(read error!); } ev.data.fdserver_sock_fd; ev.eventsEPOLLOUT|EPOLLET; printf(%s\n,recv_msg); } } } } return 0; }
2、关于epoll函数
相比于selectepoll最大的好处在于它不会随着监听fd数目的增长而降低效率。因为在内核中的select实现中它是采用轮询来处理的轮询的fd数目越多自然耗时越多。并且在linux/posix_types.h头文件有这样的声明 #define __FD_SETSIZE 1024 表示select最多同时监听1024个fd
一共三个函数 1 2 1、 int epoll_create (int size); 创建一个epoll的句柄
size用来告诉内核这个监听的数目一共有多大。这个参数不同于select()中的第一个参数给出最大监听的fd1的值。需要注意的是当创建好epoll句柄后它就是会占用一个fd值在linux下如果查看/proc/进程id/fd/是能够看到这个fd的所以在使用完epoll后必须调用close()关闭否则可能导致fd被耗尽。 1 2、 int epoll_ctl (int epfd , int op, int fd, struct epoll_event *event);
epoll的事件注册函数它不同与select()是在监听事件时告诉内核要监听什么类型的事件而是在这里先注册要监听的事件类型。第一个参数是epoll_create()的返回值第二个参数表示动作用三个宏来表示
EPOLL_CTL_ADD注册新的fd到epfd中
EPOLL_CTL_MOD修改已经注册的fd的监听事件
EPOLL_CTL_DEL从epfd中删除一个fd
第三个参数是需要监听的fd
第四个参数是告诉内核需要监听什么事struct epoll_event结构如下 1 2 3 4 5 struct epoll_event { __uint32_t events; /* Epoll events */ epoll_data_t data; /* User data variable */ }; 1 2 3 4 5 6 7 typedef union epoll_data { void *ptr; int fd; __uint32_t u32; __uint64_t u64; } epoll_data_t;
events可以是以下几个宏的集合
EPOLLIN 表示对应的文件描述符可以读包括对端SOCKET正常关闭EPOLLOUT表示对应的文件描述符可以写EPOLLPRI表示对应的文件描述符有紧急的数据可读这里应该表示有带外数据到来EPOLLERR表示对应的文件描述符发生错误EPOLLHUP表示对应的文件描述符被挂断EPOLLET 将EPOLL设为边缘触发(Edge Triggered)模式这是相对于水平触发(Level Triggered)来说的。EPOLLONESHOT只监听一次事件当监听完这次事件之后如果还需要继续监听这个socket的话需要再次把这个socket加入到EPOLL队列里1 3、 int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
等待事件的产生类似于select()调用。
参数events用来从内核得到事件的集合
maxevents告之内核这个events有多大这个 maxevents的值不能大于创建epoll_create()时的size
参数timeout是超时时间毫秒0会立即返回-1将不确定也有说法说是永久阻塞。该函数返回需要处理的事件数目如返回0表示已超时。
使用步骤
1首先通过create_epoll(int maxfds)来创建一个epoll的句柄其中maxfds为你epoll所支持的最大句柄数。这个函数会返回一个新的epoll句柄之后的所有操作将通过这个句柄来进行操作。在用完之后记得用close()来关闭这个创建出来的epoll句柄。
2然后每一帧的调用epoll_wait (int epfd, epoll_event events, int max events, int timeout) 来查询所有的网络接口。
3kdpfd为用epoll_create创建之后的句柄events是一个epoll_event*的指针当epoll_wait这个函数操作成功之后epoll_events里面将储存所有的读写事件。max_events是当前需要监听的所有socket句柄数。最后一个timeout是 epoll_wait的超时为0的时候表示马上返回为-1的时候表示一直等下去直到有事件范围为任意正整数的时候表示等这么长的时间如果一直没有事件则返回。一般如果网络主循环是单独的线程的话可以用-1来等这样可以保证一些效率如果是和主逻辑在同一个线程的话则可以用0来保证主循环的效率。 epoll_wait返回之后应该是一个循环遍历所有的事件。
基本上都是如下的框架 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 for( ; ; ) { nfds epoll_wait(epfd,events,20,500); for(i0;infds;i) { if(events[i].data.fdlistenfd) //有新的连接 { connfd accept(listenfd,(sockaddr *)clientaddr, clilen); //accept这个连接 ev.data.fdconnfd; ev.eventsEPOLLIN|EPOLLET; epoll_ctl(epfd,EPOLL_CTL_ADD,connfd,ev); //将新的fd添加到epoll的监听队列中 } else if( events[i].eventsEPOLLIN ) //接收到数据读socket { n read(sockfd, line, MAXLINE)) 0 //读 ev.data.ptr md; //md为自定义类型添加数据 ev.eventsEPOLLOUT|EPOLLET; epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,ev);//修改标识符等待下一个循环时发送数据异步处理的精髓 } else if(events[i].eventsEPOLLOUT) //有数据待发送写socket { struct myepoll_data* md (myepoll_data*)events[i].data.ptr; //取数据 sockfd md-fd; send( sockfd, md-ptr, strlen((char*)md-ptr), 0 ); //发送数据 ev.data.fdsockfd; ev.eventsEPOLLIN|EPOLLET; epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,ev); //修改标识符等待下一个循环时接收数据 } else { //其他的处理 } } }