1. 一次性连接
客服端与服务器连接一次,服务器只收发一次数据
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <fcntl.h>
#define BUFFER_LENGTH 1024
int main() {
int sockfd = socket(AF_INET, SOCK_STREAM, 0); //io
struct sockaddr_in servaddr;
memset(&servaddr, 0, sizeof(struct sockaddr_in)); // 192.168.1.123
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY); // 0.0.0.0
servaddr.sin_port = htons(9999);
if(-1 == bind(sockfd, (struct sockaddr*)&servaddr, sizeof(struct sockaddr))) {
printf(" bind failed: %s", strerror(errno));
return -1;
}
listen(sockfd, 10);
//sleep(10);
struct sockaddr_in clientaddr;
socklen_t len = sizeof(clientaddr);
int clientfd = accept(sockfd, (struct sockaddr*)&clientaddr, &len); // 有客户端连接才往下走,否则阻塞在这里
printf("clientfd: %d, sockfd: %d\n", clientfd, sockfd);
char buffer[BUFFER_LENGTH] = {0};
int ret = recv(clientfd, buffer, BUFFER_LENGTH, 0);
printf("buffer: %s\n", buffer);
send(clientfd, buffer, ret, 0);
getchar(); //block
return 0;
}
2. 一连接一线程
可以实现多个客户端与服务器连接,服务器可以多次收发数据
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <fcntl.h>
#include <pthread.h>
#define BUFFER_LENGTH 1024
//一连接一线程
void *client_thread(void * arg) { // 业务线程
int clientfd = *(int*)arg;
while (1) {
char buffer[BUFFER_LENGTH] = {0};
int ret = recv(clientfd, buffer, BUFFER_LENGTH, 0);
if(ret == 0) {
close(clientfd);
break;
}
printf("clientfd: %d, buffer: %s\n",clientfd, buffer);
send(clientfd, buffer, ret, 0);
}
}
int main() {
int sockfd = socket(AF_INET, SOCK_STREAM, 0); //io
struct sockaddr_in servaddr;
memset(&servaddr, 0, sizeof(struct sockaddr_in)); // 192.168.1.123
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY); // 0.0.0.0
servaddr.sin_port = htons(9999);
if(-1 == bind(sockfd, (struct sockaddr*)&servaddr, sizeof(struct sockaddr))) {
printf(" bind failed: %s", strerror(errno));
return -1;
}
listen(sockfd, 10);
//sleep(10);
struct sockaddr_in clientaddr;
socklen_t len = sizeof(clientaddr);
int clientfd = 0;
while (1) { // master 主线程
clientfd = accept(sockfd, (struct sockaddr*)&clientaddr, &len); // 有客户端连接才往下走,否则阻塞在这里
printf("clientfd: %d, sockfd: %d\n", clientfd, sockfd);
pthread_t threadid;
pthread_create(&threadid, NULL, client_thread, &clientfd);
}
//getchar(); //block
return 0;
}
3. select
select(maxfd, rfds, wfds, efds, timeout);
maxfd:fd的最大值,select中依靠这个值轮询,像是for()里的 int i
rfds:可读的集合
wfds:可写的集合
efds:出错的集合
timeout:多少时间轮询一次
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <fcntl.h>
#include <pthread.h>
#define BUFFER_LENGTH 1024
#define POLL_SIZE 1024
int main() {
//open
int sockfd = socket(AF_INET, SOCK_STREAM, 0); //io
struct sockaddr_in servaddr;
memset(&servaddr, 0, sizeof(struct sockaddr_in)); // 192.168.1.123
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY); // 0.0.0.0
servaddr.sin_port = htons(9999);
if(-1 == bind(sockfd, (struct sockaddr*)&servaddr, sizeof(struct sockaddr))) {
printf(" bind failed: %s", strerror(errno));
return -1;
}
listen(sockfd, 10);
struct sockaddr_in clientaddr;
socklen_t len = sizeof(clientaddr);
// nready 表示select中有多少个需要处理的io
//select(maxfd, rfds, wfds, efds, timeout);// maxfd:fd的最大值,select中依靠这个值轮询,rfds:可读的集合,wfds:可写的集合,efds:出错的集合,timeout:多少时间轮询一次
fd_set rfds, rset; // bit数组, 如果某个位置的bit位为1,则说明对应的fd有响应
FD_ZERO(&rfds); // 将rfds的每个bit位 置为零
FD_SET(sockfd, &rfds);
int maxfd = sockfd; // 在select里相当于for()循环里的int i;
int clientfd = 0;
while (1) {
rset = rfds; // 交给内核的io集合
// nready 表示select中有多少个需要处理的io
int nready = select(maxfd + 1, &rset, NULL, NULL, NULL);// maxfd:fd的最大值,select中依靠这个值轮询,rfds:可读的集合,wfds:可写的集合,efds:出错的集合,timeout:多少时间轮询一次
if (FD_ISSET(sockfd, &rset)) {
clientfd = accept(sockfd, (struct sockaddr*)&clientaddr, &len); // 有客户端连接才往下走,否则阻塞在这里
printf("accept: %d, sockfd: %d\n", clientfd, sockfd);
FD_SET(clientfd, &rfds);
if (clientfd > maxfd) maxfd = clientfd; // 假设当前连接的fd有4 5 6 7,但是当6断开后(4 5 7),再有客户端连接会顶替6的空缺(4 5 6 7),
// 但是如果我们打开了一个普通文件顶替了6的空缺, 就有可能把这个普通文件当作io来操作
if (--nready == 0) continue;
}
int i = 0;
for (i = sockfd + 1; i <= maxfd; ++ i) {
if (FD_ISSET(i, &rset)) {
char buffer[BUFFER_LENGTH] = {0};
int ret = recv(i, buffer, BUFFER_LENGTH, 0); // 一直等待clientfd的数据,否则阻塞在这里
if (ret == 0) {
close(i);
continue;
}
printf("accept: %d, buffer: %s\n", i, buffer);
send(i, buffer, ret, 0);
}
}
}
//getchar(); //block
return 0;
}
4. poll
poll(pfds, length, timeout);
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <fcntl.h>
#include <pthread.h>
#include <sys/poll.h>
#define BUFFER_LENGTH 1024
#define POLL_SIZE 1024
int main() {
//open
int sockfd = socket(AF_INET, SOCK_STREAM, 0); //io
struct sockaddr_in servaddr;
memset(&servaddr, 0, sizeof(struct sockaddr_in)); // 192.168.1.123
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY); // 0.0.0.0
servaddr.sin_port = htons(9999);
if(-1 == bind(sockfd, (struct sockaddr*)&servaddr, sizeof(struct sockaddr))) {
printf(" bind failed: %s", strerror(errno));
return -1;
}
listen(sockfd, 10);
//sleep(10);
struct sockaddr_in clientaddr;
socklen_t len = sizeof(clientaddr);
/*
struct pollfd {
int fd;
short events; // 我们监测io是否可读的集合
short revents; // 内核反馈出来的可读的io
}
*/
struct pollfd fds[POLL_SIZE] = {0};
fds[sockfd].fd = sockfd;
fds[sockfd].events = POLLIN;
int maxfd = sockfd;
int clientfd = 0;
while (1) {
int nready = poll(fds, maxfd + 1 , -1);
if (fds[sockfd].revents & POLLIN) {
clientfd = accept(sockfd, (struct sockaddr*)&clientaddr, &len); // 有客户端连接才往下走,否则阻塞在这里
printf("accept: %d\n", clientfd);
fds[clientfd].fd = clientfd;
fds[clientfd].events = POLLIN;
if (clientfd > maxfd) maxfd = clientfd; // 假设当前连接的fd有4 5 6 7,但是当6断开后(4 5 7),再有客户端连接会顶替6的空缺(4 5 6 7)
if (--nready == 0) continue;
}
int i = 0;
for (i = 0; i <= maxfd; ++ i) {
if (fds[i].revents & POLLIN) {
char buffer[BUFFER_LENGTH] = {0};
int ret = recv(i, buffer, BUFFER_LENGTH, 0); // 一直等待clientfd的数据,否则阻塞在这里
if(ret == 0){
fds[i].fd = -1;
fds[i].events = 0;
close(i);
continue;
}
printf("accept: %d, buffer: %s\n", i, buffer);
send(i, buffer, ret, 0);
}
}
}
return 0;
}
5. epoll
epoll_create(int size); // 在内核里创建epoll的组件
epoll_ctl(int epfd, EPOLL_OP, sockfd, event); // 每次epoll创建完就把这个epoll加入内核,虽然增加了系统调用,但是减少了系统调用
epoll_wait(epfd, events, length, 0); // 每次只需要把有用的io从内核带出来就可以了
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <fcntl.h>
#include <pthread.h>
#include <sys/poll.h>
#define BUFFER_LENGTH 1024
#define POLL_SIZE 1024
int main() {
//open
int sockfd = socket(AF_INET, SOCK_STREAM, 0); //io
struct sockaddr_in servaddr;
memset(&servaddr, 0, sizeof(struct sockaddr_in)); // 192.168.1.123
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY); // 0.0.0.0
servaddr.sin_port = htons(9999);
if(-1 == bind(sockfd, (struct sockaddr*)&servaddr, sizeof(struct sockaddr))) {
printf(" bind failed: %s", strerror(errno));
return -1;
}
listen(sockfd, 10);
//sleep(10);
struct sockaddr_in clientaddr;
socklen_t len = sizeof(clientaddr);
int epfd = epoll_create(1); // int size:每一次就绪总数能放多少(数组结构),3.0以后的版本改为链表结构后size就没有意义了,size>=1就行
struct epoll_event ev;
ev.events = EPOLLIN;
ev.data.fd = sockfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &ev); // key:sockfd, velue:ev, 一旦添加进来,不删除就会一直在
struct epoll_event events[1024] = {0}; // 用来保存从内核里带出的io
while (1) { //mainloop
int nready = epoll_wait(epfd, events, 1024, -1); // -1:一直阻塞, 0:不阻塞, 1:阻塞固定时长
if (nready < 0) continue;
int i = 0;
for (i = 0; i < nready; ++ i) {
int connfd = events[i].data.fd;
if (sockfd == connfd) {
//accept默认是LT, 如果设置成ET一定要设置成非阻塞的循环
/*
//ET: nonblock
while (clientfd < 0) {
clientfd = accept(listenfd, &clientaddr, &socklen);
}
*/
int clientfd = accept(sockfd, (struct sockaddr*)&clientaddr, &len); // 有客户端连接才往下走,否则阻塞在这里
if (clientfd <= 0) {
continue;
}
printf("clientfd: %d\n", clientfd);
ev.events = EPOLLIN; // 默认EPOLLLT:水平触发,只要数据没读完就一直读;
//ev.events = EPOLLIN | EPOLLET; // EPOLLET:边沿触发,只有数据发送过来时才读一次, 但是不会丢数据,他会读上次没读完的数据
ev.data.fd = clientfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, clientfd, &ev);
} else if (events[i].events & EPOLLIN) {
char buffer[10] = {0};
/*
ET: 设置成非阻塞循环的读
while (ret > 0) {
ret = recv(fd, buffer, length, 0);
}
*/
//LT(默认)
int n = recv(connfd, buffer, 10, 0);
if (n > 0) {
printf("recv : %s\n", buffer);
send(connfd, buffer, n, 0);
} else if (n == 0) {
printf("close\n");
epoll_ctl(epfd, EPOLL_CTL_DEL, connfd, NULL); // 将断开的io从内核中拿走
close(connfd);
}
}
}
}
return 0;
}
总结
一连接一线程:在实际场景里,一万个连接可能就几个连接在收发数据,而剩下的的线程就处于等待状态,系统开销大且浪费。
select:将需要处理的io集合拷贝进系统内核里遍历,然后将需要处理的io分成不同的集合返回。
poll:poll和select的处理方式类似,但是相比select的接口更少。
epoll:每当有一个io连接时就将其打包进系统内核,而系统内核只需要将需要事件处理的io返回即可,无需每次都将所有io集合拷贝进内核。(注意当io连接断开时,一定要将该io从内核中清除避免产生未知错误)