流程(模型)
1.定义套接字:int
int sfd; // 监听套接字
int cfd; // 传输套接字
2.创建tcp协议套接字:socket();
int socket(int domain, int type, int protocol);
-
domain: 通信域
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sOzCZetu-1604814834270)(en-resource://database/1190:1)] -
type: 类型
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HumrEsBk-1604814834274)(en-resource://database/1192:1)]
TCP:SOCK_STREAM
UDP:SOCK_DGRAM -
protocol: 协议
该参数取决于type值, 大多数 type 像 SOCK_STREAM 和 SOCK_DGRAM 只能使用默认值,要指定默认的protocol,可将该参数设为零
3.绑定服务器IP和端口:
int bind(int socket, sockaddr *addr, int length);
- 具体的IP和端口设置可看本文最后的代码
struct sockaddr {
sa_family_t sa_family;
char sa_data[14];
}
由于 sa_data[14] 允许 sockaddr 结构引用多种不同类型的网络地址,因此 Linux 提供了一个专用于 IP 的地址结构 sockaddr_in;
4.监听绑定的端口:
int listen(int sockfd, int backlog);
- backlog 为最大可连接数
流程图
非阻塞IO(non-blocking IO)
- 旨在提高系统的吞吐量
通过如下函数可将某个句柄fd设为非阻塞状态:
fcntl(fd, F_SETFL, O_NONBLOCK);
多路复用IO(IO multiplexing)
- 更为高效地检测【可读,可写,是否出错】,让单线程就能同时处理多个网络连接的IO
select/epoll
- 不断轮询所负责的所有socket,当某个socket有数据到达了,就通知用户进程
- 如果处理的连接数不是很高的话,使用select/epoll的web server不一定比使用multi-threading+blocking IO的web server性能更好,可能延迟还更大
select
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
- 设置fd_set函数
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);
- 注意:调用select后,没有相应状态(可读,可写,出错)的fd会被移除出fd_set,因此后面代码会有rset = rfds ;
epoll
int epoll_create(int size);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
- select当发生事件时,回去遍历所有的fd,效率不高,而epoll会将所有发生事件的fd存到evens里,之后直接遍历evens就行了
PS: EPOLLET (边沿触发),EPOLLLT(水平触发)
TCP服务器代码
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <sys/epoll.h>
#include <errno.h>
#define LISTEN_BACKLOG 1024
#define EPOLL_SIZE 1024
#if 1 /* select/epoll */
int main(void)
{
int sfd, cfd;
int sin_size;
int rcv_len = 0;
char rcv_buf[128] = {0};
struct sockaddr_in my_addr, client_addr;
int nready;
int max_fd;
fd_set rset, rfds;
sfd = socket(PF_INET, SOCK_STREAM, 0);
if (sfd < 0)
{
perror("socket");
return -1;
}
memset(&my_addr, 0, sizeof(my_addr));
my_addr.sin_family = AF_INET;
my_addr.sin_port = htons(8000);
my_addr.sin_addr.s_addr = inet_addr("192.168.240.160");//INADDR_ANY
do {
if (bind(sfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr)) == -1)
{
perror("bind");
break;
}
if (listen(sfd, LISTEN_BACKLOG) == -1)
{
perror("listen");
break;
}
#if 0 /* select */
FD_ZERO(&rfds);
FD_SET(sfd, &rfds);
max_fd = sfd;
while (1)
{
rset = rfds;
nready = select(max_fd + 1, &rset, NULL, NULL, NULL);
if (nready < 0)
{
perror("select");
continue;
}
if (FD_ISSET(sfd, &rset))
{
sin_size = sizeof(struct sockaddr_in);
memset(client_addr, 0, sizeof(client_addr));
cfd = accept(sfd, (struct sockaddr *)&client_addr, &sin_size);
if (cfd == -1)
{
perror("accept");
break;
}
printf("Client IP:%s\n", inet_ntoa(client_addr.sin_addr));
if (max_fd == FD_SETSIZE)
{
printf("Out of range\n");
}
FD_SET(cfd, &rfds);
if (cfd > max_fd)
{
max_fd = cfd;
}
if (--nready == 0) continue;
}
int i;
for (i = sfd+1; i < max_fd + 1; i++)
{
if (FD_ISSET(i, &rset))
{
memset(rcv_buf, 0, sizeof(rcv_buf));
rcv_len = recv(i, rcv_buf, sizeof(rcv_buf), 0);
if (rcv_len > 0)
{
if (send(cfd, rcv_buf, rcv_len, 0) < 0)
{
perror("send");
break;
}
}
else if (rcv_len == 0)
{
FD_CLR(i, &rfds);
close(i);
break;
}
else
{
if (errno == EAGAIN || errno == EWOULDBLOCK)
{
printf("read all data");
}
FD_CLR(i, &rfds);
close(i);
}
if (--nready == 0) break;
}
}
}
#else /* epoll */
int epoll_fd;
struct epoll_event ev, events[EPOLL_SIZE] = {0};
int i;
epoll_fd = epoll_create(1);
ev.events = EPOLLIN;
ev.data.fd = sfd;
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, sfd, &ev) == -1)
{
perror("epoll_ctl:listen");
break;
}
while (1)
{
nready = epoll_wait(epoll_fd, events, EPOLL_SIZE, -1);
for (i = 0; i < nready; i++)
{
if (events[i].data.fd == sfd)
{
sin_size = sizeof(struct sockaddr_in);
memset(&client_addr, 0, sizeof(client_addr));
cfd = accept(sfd, (struct sockaddr *)&client_addr, &sin_size);
if (cfd == -1)
{
perror("accept");
break;
}
printf("Client IP:%s\n", inet_ntoa(client_addr.sin_addr));
ev.events = EPOLLIN | EPOLLET;
ev.data.fd = cfd;
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, cfd, &ev) == -1)
{
perror("epoll_ctl:accept");
break;
}
}
else
{
int clinet_fd = events[i].data.fd;
memset(rcv_buf, 0, sizeof(rcv_buf));
rcv_len = recv(clinet_fd, rcv_buf, sizeof(rcv_buf), 0);
if (rcv_len > 0)
{
if (send(cfd, rcv_buf, rcv_len, 0) < 0)
{
perror("send");
break;
}
}
else if (rcv_len == 0)
{
close(clinet_fd);
ev.events = EPOLLIN | EPOLLET;
ev.data.fd = clinet_fd;
epoll_ctl(epoll_fd, EPOLL_CTL_DEL, clinet_fd, &ev);
break;
}
else
{
if (errno == EAGAIN || errno == EWOULDBLOCK)
{
printf("read all data");
}
ev.events = EPOLLIN | EPOLLET;
ev.data.fd = clinet_fd;
epoll_ctl(epoll_fd, EPOLL_CTL_DEL, clinet_fd, &ev);
close(clinet_fd);
}
}
}
}
#endif
} while(0);
close(sfd);
return 0;
}
#else
int main(void)
{
int sfd, cfd;
int sin_size;
int rcv_len = 0;
char rcv_buf[128] = {0};
struct sockaddr_in my_addr, client_addr;
sfd = socket(PF_INET, SOCK_STREAM, 0);
if (sfd < 0)
{
perror("socket");
return -1;
}
memset(&my_addr, 0, sizeof(my_addr));
my_addr.sin_family = AF_INET;
my_addr.sin_port = htons(8000);
my_addr.sin_addr.s_addr = inet_addr("192.168.240.160");//INADDR_ANY
do {
if (bind(sfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr)) == -1)
{
perror("bind");
break;
}
if (listen(sfd, LISTEN_BACKLOG) == -1)
{
perror("listen");
break;
}
sin_size = sizeof(struct sockaddr_in);
cfd = accept(sfd, (struct sockaddr *)&client_addr, &sin_size);
if (cfd == -1)
{
perror("accept");
break;
}
printf("Client IP:%s\n", inet_ntoa(client_addr.sin_addr));
while ((rcv_len = recv(cfd, rcv_buf, sizeof(rcv_buf), 0)) > 0)
{
printf("[recv]:%s\n", rcv_buf);
if (send(cfd, rcv_buf, rcv_len, 0) < 0)
{
perror("send");
break;
}
memset(rcv_buf, 0, sizeof(rcv_buf));
}
} while(0);
close(cfd);
close(sfd);
return 0;
}
#endif