赞
踩
设计灵感:
1 单线程io多路复用服务端
2 使用poll实现
3 将server_sockfd client_sockfd 设为非阻塞,实现最大io效率
4 使用套接字选项SO_REUSEADDR 用于测试环境调试
5 将server_sockfd 和每一个有效的client_sockfd 都设为poll的监控事件
6 有客户端关闭连接时,自动从数组中删除,并调整相应的count值
功能:
可实现多客户端同时连接,echo收发无感
注意事项:
1 运行环境 unix-like gnu_c
2 开启服务端后 可无限开启客户端
3 赠送对比用双循环阻塞服务端
4 赠送测试用客户端
poll多路复用服务端:
- #define _GNU_SOURCE
- #include <stdio.h>
- #include <sys/socket.h>
- #include <sys/un.h>
- #include <fcntl.h>
- #include <arpa/inet.h>
- #include <netinet/in.h>
- #include <sys/poll.h>
- #include <stdlib.h>
- #include <string.h>
- #include <unistd.h>
-
- #define SERVER_IP "192.168.142.132"
- #define SERVER_PORT 50013
- // 设置非阻塞用函数
- void set_non_blocking(int sockfd)
- {
- // 获取默认flags
- int flags = fcntl(sockfd, F_GETFL, 0);
- if (flags == -1)
- {
- perror("fcntl F_GETFL");
- }
- // 位运算 加上O_NONBLOCK
- flags |= O_NONBLOCK;
- // 设置 flags
- if (fcntl(sockfd, F_SETFL, flags) == -1)
- {
- perror("fcntl F_SETFL");
- }
- }
- int main()
- {
- int server_sockfd, client_sockfd;
- struct sockaddr_in server_sockaddr, client_sockaddr;
- memset(&server_sockaddr, 0, sizeof(server_sockaddr));
- memset(&client_sockaddr, 0, sizeof(client_sockaddr));
- socklen_t client_sockaddr_len = sizeof(client_sockaddr);
- socklen_t server_sockaddr_len = sizeof(server_sockaddr);
- ssize_t send_bytes, recv_bytes;
- char send_buf[1024] = "How can I he53453oday ?";
- char recv_buf[1024] = {0};
- // 循环用i,j,计数用count:pollfds[count]中每增加一个元素 count++
- int i = 0, j = 0, count = 0;
- // 系统允许的单进程 最大fd数量 1024576
- long max_fds = sysconf(_SC_OPEN_MAX);
- // 创建一个最大fd数量的数组,并将所有元素的所有字段设为0
- struct pollfd *pollfds = calloc(max_fds, sizeof(struct pollfd));
- if (pollfds == NULL)
- {
- perror("calloc");
- }
- // 将每个元素的fd设为-1,表示无效
- for (i = 0; i < max_fds; i++)
- {
- pollfds[i].fd = -1;
- }
- // 又名 listen socket 用于监听请求,ipv4 tcp
- server_sockfd = socket(AF_INET, SOCK_STREAM, 0);
- if (server_sockfd == -1)
- {
- perror("socket");
- }
- // 为什么要将server_sockfd设为非阻塞?
- // server_sockfd相关的accept被poll监视,只有读就绪(有连接到来时)才回调用accept
- // 这意味着accept通常不会阻塞,但是在极端情况下,客户端连接有迅速关闭,则accept有可能阻塞
- set_non_blocking(server_sockfd);
- // SO_REUSEADDR 地址端口复用,任意时刻只有一个socket能监听,主要用于程序重启
- // SO_REUSEPORT 地址端口复用,同时监听,自动负载均衡
- int optval = 1;
- if (setsockopt(server_sockfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) == -1)
- {
- perror("setsockopt");
- }
- // 设置协议
- server_sockaddr.sin_family = AF_INET;
- // 设置转换端口
- server_sockaddr.sin_port = htons(SERVER_PORT);
- // 设置转换ip地址
- if (inet_pton(AF_INET, SERVER_IP, &server_sockaddr.sin_addr.s_addr) == -1)
- {
- perror("inet_pton");
- }
- // 绑定
- if (bind(server_sockfd, (struct sockaddr *)&server_sockaddr, server_sockaddr_len) == -1)
- {
- perror("bind");
- }
- // 监听,调整了最大等待数量为1024
- if (listen(server_sockfd, 1024) == -1)
- {
- perror("listen");
- }
- // 将server_sockfd加入POLLIN监听事件
- pollfds[0].fd = server_sockfd;
- pollfds[0].events = POLLIN;
- // count现在是0,++后变成1,遍历i<count,也就是判断pollfds[0]
- // 但是pollfds[0]读就绪后,主要执行逻辑是accept,因此单独判断
- count++;
- printf("server start...\n");
- while (1)
- {
- // 如果没有事件真实发生,程序将阻塞在此,而不是一味地循环
- int r = poll(pollfds, count, -1);
- // 至少有一个revent被设置
- if (r > 0)
- {
- // 如果有连接请求
- if (pollfds[0].revents & POLLIN)
- {
- // 就连接
- client_sockfd = accept(server_sockfd, (struct sockaddr *)&client_sockaddr, &client_sockaddr_len);
- if (client_sockfd == -1)
- {
- perror("accept");
- }
- // 将返回的client_sockfd设为非阻塞
- set_non_blocking(client_sockfd);
- // 如果代码首次到这,count就是1,pollfds[1]被填充,符合逻辑
- pollfds[count].fd = client_sockfd;
- // 同时监控读写
- pollfds[count].events = POLLIN | POLLOUT;
- // 向后挪一位
- count++;
- }
- // 首次加入监控事件的accept返回的client_sockfd不会在本次循环中被poll监控,需要等到下次循环
- for (i = 1; i < count; i++)
- {
- // 从索引1开始(索引0用于accept已单独处理),如果被监控的fd读就绪
- if (pollfds[i].revents & POLLIN)
- {
- // 读
- recv_bytes = recv(pollfds[i].fd, recv_buf, sizeof(recv_buf), 0);
- if (recv_bytes == -1)
- {
- perror("recv");
- }
- // 如果客户端关闭了连接
- if (recv_bytes == 0)
- {
- printf("close by peer\n");
- // 服务器也关闭连接
- close(pollfds[i].fd);
- // 1 将后面的fd往前挪
- for (j = i; j < count - 1; j++)
- {
- pollfds[j] = pollfds[j + 1];
- }
- // 2 最后一个fd是一个无效的副本 手动重置
- pollfds[count - 1].fd = -1;
- pollfds[count - 1].events = 0;
- pollfds[count - 1].revents = 0;
- // 3 count应该减1
- count--;
- }
- // 收到了数据
- if (recv_bytes > 0)
- {
- // 打印收到的数据
- printf("%s\n", recv_buf);
- // 同时,如果这个fd是写就绪的
- if (pollfds[i].revents & POLLOUT)
- {
- // 写
- send_bytes = send(pollfds[i].fd, send_buf, strlen(send_buf), 0);
- if (send_bytes == -1)
- {
- perror("send");
- }
- }
- }
- }
- }
- }
- if (r == -1)
- {
- perror("poll");
- }
- }
- // 关闭server_sockfd,代码不可达
- close(server_sockfd);
- // 释放动态分配的内存,代码不可达
- free(pollfds);
-
- return 0;
- }

对比用双循环阻塞服务端:
- #define _GNU_SOURCE
- #include <stdio.h>
- #include <sys/socket.h>
- #include <sys/un.h>
- #include <arpa/inet.h>
- #include <netinet/in.h>
- #include <string.h>
- #include <unistd.h>
-
- #define SERVER_IP "192.168.142.132"
- #define SERVER_PORT 50013
-
- int main()
- {
- int server_sockfd, client_sockfd;
- struct sockaddr_in server_sockaddr, client_sockaddr;
- memset(&server_sockaddr, 0, sizeof(server_sockaddr));
- memset(&client_sockaddr, 0, sizeof(client_sockaddr));
- socklen_t client_sockaddr_len = sizeof(client_sockaddr);
- ssize_t send_bytes, recv_bytes;
- char send_buf[1024] = "How can I help you today ?";
- char recv_buf[1024] = {0};
-
- server_sockfd = socket(AF_INET, SOCK_STREAM, 0);
- if (server_sockfd == -1)
- {
- perror("socket");
- }
-
- int optval = 1;
- setsockopt(server_sockfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));
-
- server_sockaddr.sin_family = AF_INET;
- inet_pton(AF_INET, SERVER_IP, &server_sockaddr.sin_addr.s_addr);
- server_sockaddr.sin_port = htons(SERVER_PORT);
- if (bind(server_sockfd, (struct sockaddr *)&server_sockaddr, sizeof(server_sockaddr)) == -1)
- {
- perror("bind");
- }
-
- if (listen(server_sockfd, 16) == -1)
- {
- perror("listen");
- }
-
- printf("server start...\n");
-
- while (1)
- {
- client_sockfd = accept(server_sockfd, (struct sockaddr *)&client_sockaddr, &client_sockaddr_len);
- if (client_sockfd == -1)
- {
- perror("accept");
- }
- while (1)
- {
-
- recv_bytes = recv(client_sockfd, recv_buf, sizeof(recv_buf), 0);
- if (recv_bytes == -1)
- {
- perror("recv");
- }
- else if (recv_bytes == 0)
- {
- printf("closed by peer\n");
- break;
- }
- else
- {
- printf("%s\n", recv_buf);
- }
- send_bytes = send(client_sockfd, send_buf, strlen(send_buf), 0);
- if (send_bytes == -1)
- {
- perror("send");
- }
- }
- }
- close(server_sockfd);
- return 0;
- }

测试用客户端:
- #define _GNU_SOURCE
- #include <stdio.h>
- #include <sys/socket.h>
- #include <sys/un.h>
- #include <arpa/inet.h>
- #include <netinet/in.h>
- #include <string.h>
- #include <unistd.h>
- #include <stdlib.h>
- #include <time.h>
- #include <fcntl.h>
- #define SERVER_IP "192.168.142.132"
- #define SERVER_PORT 50013
- int set_non_blocking(int sockfd)
- {
- int flags = fcntl(sockfd, F_GETFL, 0);
- if (flags == -1)
- {
- perror("fcntl(F_GETFL)");
- }
-
- flags |= O_NONBLOCK;
-
- if (fcntl(sockfd, F_SETFL, flags) == -1)
- {
- perror("fcntl(F_SETFL)");
- }
-
- return 0;
- }
- int main()
- {
- int client_sockfd;
- struct sockaddr_in server_sockaddr, client_sockaddr;
- memset(&server_sockaddr, 0, sizeof(server_sockaddr));
- memset(&client_sockaddr, 0, sizeof(client_sockaddr));
- socklen_t client_sockaddr_len = sizeof(client_sockaddr);
- ssize_t send_bytes, recv_bytes;
- char send_buf[1024] = {0};
- char recv_buf[1024] = {0};
-
- client_sockfd = socket(AF_INET, SOCK_STREAM, 0);
- if (client_sockfd == -1)
- {
- perror("socket");
- }
-
- //set_non_blocking(client_sockfd);
-
- inet_pton(AF_INET, SERVER_IP, &server_sockaddr.sin_addr.s_addr);
- server_sockaddr.sin_port = htons(SERVER_PORT);
- server_sockaddr.sin_family = AF_INET;
- if (connect(client_sockfd, (struct sockaddr *)&server_sockaddr, sizeof(server_sockaddr)) == -1)
- {
- perror("connect");
- }
-
- getsockname(client_sockfd, (struct sockaddr *)&client_sockaddr, &client_sockaddr_len);
- snprintf(send_buf, sizeof(send_buf), "%u:he###llo s???ver !!!",
- ntohs(client_sockaddr.sin_port));
- while (1)
- {
- send_bytes = send(client_sockfd, send_buf, strlen(send_buf), 0);
- if (send_bytes == -1)
- {
- perror("send");
- }
- recv_bytes = recv(client_sockfd, recv_buf, sizeof(recv_buf), 0);
- if (recv_bytes == -1)
- {
- perror("recv");
- }
- printf("%s\n", recv_buf);
- sleep(2);
- }
- close(client_sockfd);
- return 0;
- }

Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。