1. 监听socket
int listen(int sockfd, int backlog);
作用:创建一个监听队列来存放待处理的客户连接(在队列中就已经处于ESTABLISHED而不用accept返回以后)。backlog参数提示内核监听队列的最大长度,如果队列长度超过backlog,则不受理新的客户连接,和客户端也会收到ECONNREFUSED的错误信息。
处于监听队列中的连接至少完成了三次握手中的两步,如果超出backlog的是SYN_RCVD,如果在backlog内则是ESTABLISHED状态。
2. 接受连接
int accept(int sockfd, struct sockaddr* addr, socklen_t* addrlen);
作用:从监听队列中取出连接,而不论连接处于何种状态,如果监听队列中处于ESTABLISHED状态的连接出现网络异常或者提前退出,accept都会成功返回!
3. 发起连接
int connect(int sockfd, const struct sockaddr* serv_addr, socklen_t addrlen);
常见错误号: ECONNREFUSED, 目标端口不存在,连接被拒绝;
ETIMEDOUT: 连接超时。
4. 关闭连接
int close(int sockfd); 注意并非立即关闭一个连接,而是将fd的引用计数减1.只有当fd的引用计数为0时才真正关闭连接。多进程程序中,一次fork系统调用默认将父进程的socket引用计数加1,因此父子进程都调用close才会真正关闭连接。
如果无论如何都要立即终止连接,可以使用shutdown系统调用: int shutdown( int sockfd, int howto); 专门为网络编程设计。
howto:
SHUT_RD: 关闭sockfd上读的这一半,应用程序不再对socket执行读操作,并且该socket接收缓冲区的数据都被丢弃;
SHUT_WR: 关闭sockfd上写的这一半,sockfd的发送缓冲区中的数据会在真正关闭连接之前全部发送出去,应用程序不能再读操作,连接处于半关闭状态;
SHUT_RDWR: 同时关闭sockfd上的读和写。
5. TCP数据读写
对文件的读写操作同样使用于socket,但是socket编程接口提供了几个专门用于socket数据读写的系统调用,增加了对数据读写的控制;
ssize_t recv( int sockfd, void* buf, size_t len, int flags);
ssize_t send( int sockfd, const void* buf, size_t len, int flags);
recv函数flags参数通常设为0. 返回实际读取到的数据长度,它可能小于我们期望的长度len,可能要多次recv;返回0意味着通信对方已经关闭连接。
flags设置MSG_OOB可以接收发送带外数据;注意只有一个字符被当作带外数据,而且接收时的数据将被带外数据截断,即带外数据前一部分和后一部分不能在同一个recv中得到。
例如发送正常数据“123”, 再发送带外数据“abc", 再发送正常数据”123“。再接收时第一次接收正常数据的话得到”123ab", 再接收带外数据得到“c",即只有字符'c'被当作带外数据,再接收正常数据时,得到“123”。
6. UDP数据读写
ssize_t recvfrom( int sockfd, void* buf, size_t len, int flags, struct sockaddr* src_addr, socklen_t* addrlen);
ssize_t sendto(int sockfd, const void* buf, size_t len, int flags, const struct sockaddr* dest_addr, socklen_t addrlen);
注意:recvfrom 和 sendto 也可以用于面向连接的socket数据读写,只不过最后两个参数传入NULL即可。
7. readv和writev函数
#include <sys/uio.h>
ssize_t readv(int fd, const struct iovec* vector, int count); --> 分散读
ssize_t writev(int fd, const struct iovec* vector, int count); --> 聚合写
struct iovec
{
void* iov_base;
size_t iov_len;
}
8. printf族函数
int printf(const char* format, ...);
int fprintf(FILE* stream, const char* format, ...);
int dprintf(int fd, const char* format, ...);
int sprintf(char* str, const char* format, ...);
int snprintf(char* str, size_t size, const char* format, ...); --> 在str中至多写入size个数据。
9. sendfile函数
#include <sys/sendfile.h>
ssize_t sendfile(int out_fd, int in_fd, off_t* offset, size_t count);
在两个fd之间传输数据(完全在内核中操作), 从而避免了内核缓冲区和用户缓冲区之间的数据拷贝,效率很高,即零拷贝。
in_fd是待读出内容的fd, out_fd是待写入内容的文件描述符。offset是指定从读入文件流的哪个位置开始读,如果为空则表示默认的起始位置。count是指定传输的字节数。
注:in_fd必须是一个支持类似mmap函数的文教描述符, 即必须是一个真实的文件,不能是socket和管道,而out_fd则必须是一个socket。sendfile几乎是专门为网络上传输文件而设计的。
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/sendfile.h>
#include <assert.h>
#include <string.h>
#include <fcntl.h>
#include <sys/stat.h>
int main(int argc, char* argv[])
{
if (argc <= 3)
{
printf("usage: %s, ip address, port number, filepath.\n", basename(argv[0]));
return -1;
}
const char* ip = argv[1];
const short port = atoi(argv[2]);
const char* filepath = argv[3];
int fd = open(filepath, O_RDWR);
if (fd < 0)
{
perror("open file error.");
exit(-1);
}
struct stat filestat;
fstat(fd, &filestat);
struct sockaddr_in address, clientAddr;
bzero(&address, sizeof(address));
address.sin_family = AF_INET;
address.sin_port = htons(port);
inet_pton(AF_INET, ip, &address.sin_addr.s_addr);
int sock = socket(AF_INET, SOCK_STREAM, 0);
assert(sock >= 0);
int ret = bind(sock, (struct sockaddr*)&address, sizeof(address));
assert(ret != -1);
ret = listen(sock, 5);
assert(ret != -1);
bzero(&clientAddr, sizeof(clientAddr));
socklen_t len = sizeof(clientAddr);
int connfd = accept(sock, (struct sockaddr*)&clientAddr, &len);
assert(connfd >= 0);
sendfile(connfd, fd, NULL, filestat.st_size);
close(connfd);
close(sock);
return 0;
}
10. splice函数 --> 零拷贝操作
ssize_t splice(int fd_in, loff_t* off_in, int fd_out, loff_t* off_out, size_t len, unsigned int flags);
fd_in 是待读取的文件描述符,如果是管道文件,off_in必须是NULL。
注意:fd_in和fd_out必须至少有一个管道文件描述符。
flags控制数据如何移动。
SPLICE_F_MOVE --> 如果合适的话,按整页内存移动数据。这只是对内核的一个提示,不过实际没有任何效果;
SPLICE_F_NONBLOCK --> 非阻塞的splice操作,实际要受fd的阻塞情况影响;
SPLICE_F_MORE --> 给内核的一个提示:后续的splice调用将读取更多数据;
SPLICE_F_GIFT --> 对splice没有效果。
可以使用splice实现高效的回射服务器。
将connfd 进行splice操作,数据读到一个pipefd[1]中,再用splice将pipefd[0]读到connfd中。
11. tee函数 --> 零拷贝
#include <fcntl.h>
ssize_t tee(int fd_in, int fd_out, size_t len, unsigned int flags);
fd_in和fd_out必须都是管道文件描述符!!不消耗数据,因此源文件描述符上的数据还可以用于后续的读操作。
#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <libgen.h>
#include <assert.h>
#define _GNU_SOURCE
int main(int argc, char* argv[])
{
if (argc != 2)
{
printf("usage: %s, <file>.\n", basename(argv[0]));
return 1;
}
const char* filepath = argv[1];
int fd = open(filepath, O_CREAT | O_WRONLY | O_TRUNC, 0666);
assert(fd >= 0);
int pipefd_stdout[2];
int ret = pipe(pipefd_stdout);
assert(ret != -1);
int pipefd_file[2];
assert((ret = pipe(pipefd_file)) != -1);
ret = splice(STDIN_FILENO, NULL, pipefd_stdout[1], NULL, 32768, SPLICE_F_MORE | SPLICE_F_MOVE);
if (ret == -1)
{
perror("splice error.");
exit(-1);
}
assert((ret = tee(pipefd_stdout[0], pipefd_file[1], 32768, SPLICE_F_NONBLOCK)) != -1);
assert((ret = splice(pipefd_file[0], NULL, fd, NULL, 32768, SPLICE_F_MOVE | SPLICE_F_MORE)) != -1);
assert((ret = splice(pipefd_stdout[0], NULL, STDOUT_FILENO, NULL, 32768, SPLICE_F_MOVE | SPLICE_F_MORE)) != -1);
close(pipefd_stdout[0]);
close(pipefd_stdout[1]);
close(pipefd_file[0]);
close(pipefd_file[1]);
return 0;
}
注:为什么要用tee从一个管道拷贝数据到另一个管道,然后再写入文件中,因为数据不仅需要流入文件中,还要输出到标准输出上,而tee是不消耗数据的!
12. 统一事件源
信号是一种异步事件:信号处理函数和程序的主循环是两条不同的执行路径,一种经典的信号处理方式是将处理逻辑放到程序的主循环中,信号处理函数在信号被触发时简单通知主循环程序接收到信号,并把信号值传递给主循环,主循环再根据接收到的信号值执行对应的逻辑代码。信号处理函数通常使用管道来将信号传递给主循环:信号处理函数在管道写端写入触发的信号值,主循环程序在管道读端读取信号值,主循环知道管道有数据可读的方式可以利用I/O复用监听管道的读端fd即可,这样就能把信号事件和其它I/O事件一样处理,即统一事件源。
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <sys/epoll.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <assert.h>
#include <fcntl.h>
#include <errno.h>
#include <vector>
#define MAX_EVENT_NUM 1024
int pipefd[2];
std::vector<int> clients;
int SetNonblocking(int fd)
{
int oldflag = fcntl(fd, F_GETFL);
int newflag = oldflag | O_NONBLOCK;
fcntl(fd, F_SETFL, newflag);
return oldflag;
}
int addfd(int epfd, int fd)
{
epoll_event evt;
evt.events = EPOLLIN | EPOLLET;
evt.data.fd = fd;
SetNonblocking(fd);
epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &evt);
}
void SigHandler(int sig)
{
int errnum = errno;
char val = static_cast<char>(sig);
write(pipefd[1], &val, sizeof(val));
errno = errnum;
}
int addsig(int sig)
{
struct sigaction sa;
memset(&sa, 0, sizeof(sa));
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sa.sa_handler = SigHandler;
int ret = sigaction(sig, &sa, nullptr);
return ret;
}
int acceptclient(int listenfd)
{
sockaddr_in clientaddr;
bzero(&clientaddr, sizeof(clientaddr));
socklen_t addrlen = sizeof(clientaddr);
int ret = accept(listenfd, (sockaddr*)&clientaddr, &addrlen);
assert(ret >= 0);
char addrbuf[INET_ADDRSTRLEN];
printf("New client: %s.\n",
inet_ntop(AF_INET, &clientaddr.sin_addr, addrbuf, sizeof(addrbuf)));
return ret;
}
int main(int argc, char* argv[])
{
if (argc != 3)
{
printf("usage: %s, ip address, port number.\n", basename(argv[0]));
exit(1);
}
const char* ip = argv[1];
const short port = static_cast<short>(atoi(argv[2]));
sockaddr_in addr;
bzero(&addr, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
inet_pton(AF_INET, ip, &addr.sin_addr.s_addr);
int listenfd = socket(AF_INET, SOCK_STREAM, 0);
assert(listenfd >= 0);
int ret = bind(listenfd, (sockaddr*)&addr, sizeof(addr));
assert(ret != -1);
assert((ret = listen(listenfd, 5)) != -1);
int epfd = epoll_create(5);
assert(epfd >= 0);
addfd(epfd, listenfd);
assert(pipe(pipefd) != -1);
addfd(epfd, pipefd[0]);
assert(addsig(SIGTERM) != -1);
assert(addsig(SIGINT) != -1);
assert(addsig(SIGHUP) != -1);
assert(addsig(SIGCHLD) != -1);
bool stopserver = false;
epoll_event events[MAX_EVENT_NUM];
while(!stopserver)
{
ret = epoll_wait(epfd, events, MAX_EVENT_NUM, -1);
if (ret < 0 && errno != EINTR)
{
perror("epoll_wait error.");
break;
}
else if (ret < 0 && errno == EINTR)
{
continue;
}
for (int i = 0; i < ret; ++i)
{
int sockfd = events[i].data.fd;
if (sockfd == listenfd)
{
int clientfd = acceptclient(listenfd);
addfd(epfd, clientfd);
clients.push_back(clientfd);
}
else if (sockfd == pipefd[0] && events[i].events & EPOLLIN)
{
char sigs[256];
memset(sigs, '\0', sizeof(sigs));
ret = read(pipefd[0], sigs, sizeof(sigs));
for (int i = 0; i < ret; ++i)
{
switch(sigs[i])
{
case SIGTERM:
printf("Receieved SIGTERM.\n");
stopserver = true;
break;
case SIGINT:
printf("Received SIGINT.\n");
stopserver = true;
break;
case SIGHUP:
printf("Received SIGHUP.\n");
break;
case SIGCHLD:
printf("Received SIGHUP.\n");
break;
}
}
}
}
}
close(pipefd[0]);
close(pipefd[1]);
for (auto client : clients)
close(client);
close(listenfd);
close(epfd);
}