将之前letflysite.com文章搬运过来。
下面将使用c写几个之前网络模型代码。下面都是服务端代码,客户端简略运行telnet localhost 10003。
1. 阻塞型网络编程模型
4步:1.创建码头;2.连接码头;3.读取数据;4.关闭。
#include <cstdio>
#include <cstring>
#include <netinet/in.h>
#include <errno.h>
#include <unistd.h>
int main() {
// 1. 创建码头
// int socket(int domain , int type , int protocol);
// 首先,domain 需要被设置为 “AF_INET”,就像上面的struct sockaddr_in。
// 然后,type参数告诉内核这个socket 是什么类型,“SOCK_STREAM”或是“SOCK_DGRAM”。最后,只需要把protocol 设置为0 。
// socket()函数只是简单的返回一个你以后可以使用的套接字描述符。如果发生错误>,
// socket()函数返回 –1 。全局变量errno 将被设置为错误代码。(可以参考perror() 的manpages)
int ss = socket(AF_INET, SOCK_STREAM, 0);
// 捕获异常
if (ss == -1) {
printf("create socket failed, errno is %d\n", errno);
return -1;
}
// n: network; h: host; s: short; l: long. 主机的无符号短整形数转换成网络字>节顺序
struct sockaddr_in server_ip, clien_ip;
server_ip.sin_family = AF_INET; // 协议族
server_ip.sin_port = htons(10003);
server_ip.sin_addr.s_addr = htonl(INADDR_ANY); // 该宏默认为0,也就是你的主>机地址,方便可移>植性
memset(server_ip.sin_zero, 0, 8); // 8个字节
int err = bind(ss, (struct sockaddr *)(&server_ip), sizeof(struct sockaddr));
if (err == -1) {
printf("bind error, errno is %d\n", errno);
return -1;
}
// 最大连接数
err = listen(ss, 100);
if (err == -1) {
printf("listen error, errno is %d\n", errno);
close(ss);
return -1;
}
// 2.连接码头
socklen_t clien_len = sizeof(struct sockaddr);
int s = accept(ss, (struct sockaddr *)(&clien_ip), &clien_len);
if (s == -1) {
printf("accept error, errno is %d\n", errno);
close(ss);
return -1;
}
// 3.读取数据
char buf[1024];
read(s, buf, 100);
printf("buf is %s\n", buf);
// 4.关闭
close(s);
close(ss);
return 0;
}
3.非阻塞的服务端模型
#include <cstdio>
#include <cstring>
#include <errno.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <unistd.h>
int main() {
// 1. 创建码头
// int socket(int domain , int type , int protocol);
// 首先,domain 需要被设置为 “AF_INET”,就像上面的struct sockaddr_in。
// 然后,type参数告诉内核这个socket 是什么类型,“SOCK_STREAM”或是“SOCK_DGRAM”。最后,只需要把protocol 设置为0 。
// socket()函数只是简单的返回一个你以后可以使用的套接字描述符。如果发生错误>,
// socket()函数返回 –1 。全局变量errno 将被设置为错误代码。(可以参考perror() 的manpages)
int server_s = socket(AF_INET, SOCK_STREAM, 0);
// 捕获异常
if (server_s == -1) {
printf("create socket failed, errno is %d\n", errno);
return -1;
}
// n: network; h: host; s: short; l: long. 主机的无符号短整形数转换成网络字>节顺序
struct sockaddr_in server_ip, clien_ip;
server_ip.sin_family = AF_INET; // 协议族
server_ip.sin_port = htons(10003);
server_ip.sin_addr.s_addr = htonl(INADDR_ANY); // 该宏默认为0,也就是你的主>机地址,方便可移>植性
memset(server_ip.sin_zero, 0, 8); // 8个字节
int err = bind(server_s, (struct sockaddr *)(&server_ip), sizeof(struct sockaddr));
if (err == -1) {
printf("bind error, errno is %d\n", errno);
return -1;
}
// 最大连接数
err = listen(server_s, 100);
if (err == -1) {
printf("listen error, errno is %d\n", errno);
close(server_s);
return -1;
}
// fcntl()函数,处理多路复用I/O
int flags, client_s;
if ((flags = fcntl(server_s, F_GETFL, 0)) < 0) perror("fcntl F_GETFL");
flags |= O_ASYNC; // O_NONBLOCK为非阻塞I/O,O_ASYNC为信号驱动I/O
if (fcntl(server_s, F_SETFL, flags) < 0) perror("fcntl F_SETFL");
while (1) {
// 2.连接码头
socklen_t clien_len = sizeof(struct sockaddr);
client_s = accept(server_s, (struct sockaddr *)(&clien_ip), &clien_len);
if (client_s == -1) {
printf("accept error, errno is %d\n", errno);
close(server_s);
return -1;
}
// 3.读取数据
char read_buf[1024];
if (read(client_s, read_buf, 1024) < 0) {
perror("read");
close(client_s);
close(server_s);
return -1;
}
printf("read_buf is %s\n", read_buf);
}
// 4.关闭
close(client_s);
close(server_s);
return 0;
}
4. 基于select()接口的事件驱动服务端模型
在这之前我们认识两个函数send和recv。send的函数模型如下,ssize_t send(int sockfd, const void *buf, size_t len, int flags);recv的函数模型如下,ssize_t recv(int sockfd, void *buf, size_t len, int flags)。它们比read和write多了flags标志位。这两个函数要在我们tcp建立连接之后才可以写的函数。我们看看它有哪些flags函数。
flags | 说明 | recv | send |
MSG DONIROUTE | 不查路由表 | yes | |
MSG DONTWAIT | 本操作不阻塞 | yes | yes |
MSG OOB | 发送或接收带外数据 | yes | yes |
MSG WAITALL | 查看外来消息 | yes | |
MSG PEEK | 等待所有数据 | yes |
1,MSG DONIROUTE这通常是在我们发送数据的时候,比如说是在一个局域网里面,它不需要通过我们的网关到达我们的公网,只在当前这个局域网里面发送数据。
2,DONTWAIT,I/O是阻塞式还是非阻塞式
3,OOB,这个用到的不多,主要是在我们发收数据的时候,它会把最后一个字节,或最后一点点数据,通过我们的内核函数,在我们的应用程序里面不需要,一般设置为0。
跟前面创建码头一样,从第2步连接码头开始不一样
#include <cstdio>
#include <cstring>
#include <netinet/in.h>
#include <errno.h>
#include <unistd.h>
int main() {
// 1. 创建码头
// int socket(int domain , int type , int protocol);
// 首先,domain 需要被设置为 “AF_INET”,就像上面的struct sockaddr_in。
// 然后,type参数告诉内核这个socket 是什么类型,“SOCK_STREAM”或是“SOCK_DGRAM”。最后,只需要把protocol 设置为0 。
// socket()函数只是简单的返回一个你以后可以使用的套接字描述符。如果发生错误,
// socket()函数返回 –1 。全局变量errno 将被设置为错误代码。(可以参考perror() 的manpages)
int ss = socket(AF_INET, SOCK_STREAM, 0);
// 捕获异常
if (ss == -1) {
printf("create socket failed, errno is %d\n", errno);
return -1;
}
// n: network; h: host; s: short; l: long. 主机的无符号短整形数转换成网络字节顺序
struct sockaddr_in server_ip, clien_ip;
server_ip.sin_family = AF_INET; // 协议族
server_ip.sin_port = htons(10003);
server_ip.sin_addr.s_addr = htonl(INADDR_ANY); // 该宏默认为0,也就是你的主机地址,方便可移植性
memset(server_ip.sin_zero, 0, 8); // 8个字节
if (bind(ss, (struct sockaddr *)(&server_ip), sizeof(struct sockaddr)) == -1) {
printf("bind error, errno is %d\n", errno);
return -1;
}
// 最大连接数
if (listen(ss, 100) == -1) {
printf("accept error, errno is %d\n", errno);
close(ss);
return -1;
}
// 2.连接码头
fd_set global_readfs, current_readfs;
FD_ZERO(&global_readfs);
FD_SET(ss, &global_readfs);
int maxfd = ss;
while (1) {
// int select(int nfds, fd_set, *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout)
// fd_set 设置我的套接字进入为可读可写异常, timeout它超时了怎么办
// 套接字有三种方式,1.不阻塞,调用一次返回一次,timeout是0;2.永远等待,直到下一个套接字来了
// 此时timeout是null;3.介于中间我给它一定的时间等待
// fd_set底层实现是数组的结构,比如说我要关注1024个套接字,它就是1024/32=32个数组,我就可以监听1024个套接字
current_readfs = global_readfs;
if (select(maxfd + 1, ¤t_readfs, NULL, NULL, NULL) < 0) {
// void FD_CLR(int fd, fd_set *set)
// int FD_ISSET(int fd, fd_set *set)
// void FD_SET(int fd, fd_set *set)
// void FD_ZERO(fd_set *set)
// 我们可以调用这些宏,第一个参数都是套接字,增删改查
perror("select error.\n");
return -1;
}
for (int i = 0; i <= maxfd; ++i) {
// 判断是不是内核通知我的可读状态
if (FD_ISSET(i, ¤t_readfs))
if (ss == i) {
socklen_t clien_len = sizeof(struct sockaddr);
int clien_s = accept(ss, (struct sockaddr *)(&clien_ip), &clien_len);
if (clien_s == -1) {
printf("accept error, errno is %d\n", errno);
close(ss);
return -1;
}
printf("clien_s: %d\n", clien_s);
FD_CLR(i, ¤t_readfs);
maxfd = maxfd > clien_s ? maxfd : clien_s;
FD_SET(clien_s, &global_readfs);
} else {
printf("read clien_s:%d\n", i);
// 3.读取数据
char read_buf[1024];
int bytes = recv(i, read_buf, 1024, 0);
if (bytes < 0) {
perror("recv error.\n");
return -1;
}
if (bytes == 0 ) {
// 清除套接字集
FD_CLR(i, &global_readfs);
// 4.关闭
close(i);
continue;
}
printf("read_buf is %s\n", read_buf);
send(i, read_buf, strlen(read_buf), 0);
}
}
}
close(ss);
return 0;
}
epoll接口
#include <cstdio>
#include <cstring>
#include <netinet/in.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/epoll.h>
int main() {
// 1. 创建码头
// int socket(int domain , int type , int protocol);
// 首先,domain 需要被设置为 “AF_INET”,就像上面的struct sockaddr_in。
// 然后,type参数告诉内核这个socket 是什么类型,“SOCK_STREAM”或是“SOCK_DGRAM”。最后,只需要把protocol 设置为0 。
// socket()函数只是简单的返回一个你以后可以使用的套接字描述符。如果发生错误,
// socket()函数返回 –1 。全局变量errno 将被设置为错误代码。(可以参考perror() 的manpages)
int server_s = socket(AF_INET, SOCK_STREAM, 0);
// 捕获异常
if (server_s == -1) {
printf("create socket failed, errno is %d\n", errno);
return -1;
}
// n: network; h: host; s: short; l: long. 主机的无符号短整形数转换成网络字节顺序
struct sockaddr_in server_ip, clien_ip;
server_ip.sin_family = AF_INET; // 协议族
server_ip.sin_port = htons(10003);
server_ip.sin_addr.s_addr = htonl(INADDR_ANY); // 该宏默认为0,也就是你的主机地址,方便可移植性
memset(server_ip.sin_zero, 0, 8); // 8个字节
if (bind(server_s, (struct sockaddr *)(&server_ip), sizeof(struct sockaddr)) == -1) {
printf("bind error, errno is %d\n", errno);
return -1;
}
// 最大连接数
if (listen(server_s, 100) == -1) {
printf("listen error, errno is %d\n", errno);
close(server_s);
return -1;
}
// fcntl()函数,处理多路复用I/O
int flags, client_s;
if ((flags = fcntl(server_s, F_GETFL, 0)) < 0) perror("fcntl F_GETFL");
flags |= O_ASYNC; // O_NONBLOCK为非阻塞I/O,O_ASYNC为信号驱动I/O
if (fcntl(server_s, F_SETFL, flags) < 0) perror("fcntl F_SETFL");
int epollfd = epoll_create(500);
if (epollfd < 0) {
perror("epoll_create err:");
return -1;
}
struct epoll_event ev, events[500];
ev.data.fd = server_s;
ev.events = EPOLLIN;
if (epoll_ctl(epollfd, EPOLL_CTL_ADD, server_s, &ev) == -1) {
perror("epoll_ctl err:");
return -1;
}
while (1) {
int fds = epoll_wait(epollfd, events, 500, -1);
if (fds < 0) {
perror("epoll_wait err:");
return -1;
}
// 轮询
for (int i = 0; i < fds; ++i) {
int current_s = events[i].data.fd;
if (current_s == server_s) {
// 2.连接码头
socklen_t clien_len = sizeof(struct sockaddr);
client_s = accept(server_s, (struct sockaddr *)(&clien_ip), &clien_len);
if (client_s == -1) {
printf("accept error, errno is %d\n", errno);
close(server_s);
continue;
}
ev.data.fd = server_s;
ev.events = EPOLLIN | EPOLLET;
epoll_ctl(epollfd, EPOLL_CTL_ADD, server_s, &ev);
} else {
// 3.读取数据
char recv_buf[1024];
int bytes;
if ((bytes = recv(client_s, recv_buf, 1024, 0)) < 0) {
perror("recv");
close(client_s);
epoll_ctl(epollfd, EPOLL_CTL_DEL, current_s, &ev);
close(current_s);
continue;
}
printf("recv_buf is %s\n", recv_buf);
send(current_s, recv_buf, bytes, 0);
}
}
}
// 4.关闭
close(client_s);
close(server_s);
return 0;
}