目录
几个重要的信号
- SIGPIPE管道中止,当写入无人读取的管道时产生该信号,默认终止进程
- SIGCHLD子进程结束或停止时发送
- SIGALRM定时器信号,以秒为单位,默认终止进程
- SIGUSR1/SIGUSR2自定义,默认终止进程
- SIGINT键盘输入的退出信号
- SIGQUIT键盘输入的退出信号
- SIGHUP控制终端的挂起信号
SIGPIPE
网络程序必须要处理SIGPIPE信号,否则当客户端
退出后,服务器仍然向该SOCKET发数据时,则会
引起Crash
SIGCHLD
- 僵尸进程是一个早已死亡的进程,但在进程表中仍占有位置
- Linux中当子进程结束的时候,他并没有被完全销毁,因为父进程还
要用它的信息 - 父进程没有处理SIGCHLD信号或调用wait / waitpid()等待子进
程结束,就会出现僵尸进程
发送信号方式
硬件方式
如ctrl+ C、ctrl+\等
软件方式
kill api
安装信号方式
- 简单方式
signal(int sig, void (*func)(int); - 高级方式
int sigaction(int sig, const struct sigaction act, struct sigaction oact);
sigaction结构体:
struct sigaction {
void(*sa_handler)(int); //处理函数
void(*sa_sigaction)(int, siginfo_t*, void*); //处理函数
sigset_tsa_mask; //掩码
int sa_flags; //根据SA SIGINFO标记选择
sa_handler/sa_sigaction
void(*sa_restorer)(void); //设置为NULL, -般不用
}
两种方式例子:
#include<iostream>
#include<signal.h>
#include <unistd.h>
void sighandle(int sig) {
std::cout << "receved signal" <<sig<<std::endl;
}
int main(int argc, char* argv[]) {
signal(SIGINT, sighandle);
signal(SIGQUIT, sighandle);
signal(SIGHUP, sighandle);
pause();
return 0;
}
#include<iostream>
#include<signal.h>
#include <unistd.h>
void sighandler(int sig){
std::cout << "receved signal" <<sig<<std::endl;
}
int main(int argc, char* argv[]) {
struct sigaction act, oact;
act.sa_handler = sighandler;
//将所有信号位置一,不进行处理
sigfillset(&act.sa_mask);
//仅使用两个处理函数的第一个
act.sa_flags = 0;
sigaction(SIGINT,&act, &oact);
pause();
return 0;
}
后台进程
将进程编程后台的方式:
fork方式
调用系统的daemon API
Fork方式
fork一个子进程,父进程退出,子进程成为孤儿进程,被init进程接管
调用setsid建立新的进程会话
将当前工作目录切换到根目录
将标准输入,输出,出错重定向到/dev/null
#include<iostream>
#include<unistd.h>
#include <stdlib.h>
#include<fcntl.h>
void daemonize(){
int fd;
pid_t pid;
if((pid=fork())<0){
std::cout<<"can't create supercess!"<<std::endl;
}else{
if(pid!=0){//如果是父进程,则推出
exit(0);
}
}
setsid();
if(chdir("/")<0){
std::cout<<"can't change dir!"<<std::endl;
exit(-1);
}
fd = open("/dev/null", O_RDWR);
dup2(fd, STDIN_FILENO);
dup2(fd, STDOUT_FILENO);
dup2(fd, STDERR_FILENO);
return;
}
int main(int argc, char* argv[]) {
daemonize();
while(1){
sleep(1);
}
}
daemon API方式
#include<iostream>
#include<unistd.h>
#include <stdlib.h>
int main(int argc, char* argv[]) {
//两个参数都为0,表示改变目录,第二个表示关闭标准输入输出文件
if(daemon(0,0)==-1){
std::cout<<"error"<<std::endl;
exit(-1);
}
while(1){
sleep(1);
}
}
网络编程基础
TCP服务端实现
重要结构体
struct sockaddr_in
{
sa_family_t sin_family; //协议
uint16_t sin_port;
struct in_addr sin_addr;
char sin_zero[8] //填充位
}
struct in _addr
in_addr_t s_addr;
}
struct sockaddr
sa_family_t sin_family;
char sin_zero[14];
=======
#include<iostream>
#include<netinet/in.h>
#include<sys/socket.h>
#include<unistd.h>
#include<string.h>
#define PORT 8111
#define MESSAGE_LEN 1024
int main(int argc, char* argv[]){
int socket_fd;
int on = 1;
int ret = -1;
//并发10个
int backlog = 10;
int accept_fd;
char in_buff[MESSAGE_LEN] = {0,};
struct sockaddr_in localaddr, remoteaddr;
//用man socket可查函数具体
socket_fd = socket(AF_INET, SOCK_STREAM, 0);
if(socket_fd==-1){
std::cout<<"Failed to create socket!"<<std::endl;
exit(-1);
}
ret = setsockopt(
socket_fd,
SOL_SOCKET,
SO_REUSEADDR,
&on,
sizeof(on)
);
if(ret==-1){
std::cout<<"Failed to set socket options!"<<std::endl;
}
localaddr.sin_family = AF_INET;
localaddr.sin_port = PORT;
//所有地址都能侦听这个端口:
localaddr.sin_addr.s_addr = INADDR_ANY;
bzero(&(localaddr.sin_zero),8);
ret = bind(socket_fd, (struct sockaddr *)&localaddr, sizeof(struct sockaddr));
if(ret==-1){
std::cout<<"Failed to bind addr!"<<std::endl;
exit(-1);
}
//开启监听
ret = listen(socket_fd, backlog);
if(ret==-1){
std::cout<<"Failed to listen socket!"<<std::endl;
exit(-1);
}
while(1){
socklen_t addr_len = sizeof(struct sockaddr);
accept_fd = accept(socket_fd, (struct sockaddr *)&remoteaddr, &addr_len);
while(true){
//不断接受数据
ret = recv(accept_fd, (void*)in_buff, MESSAGE_LEN, 0);
if(ret==0){break;}
std::cout<<"recv:"<<in_buff<<std::endl;
//数据原样回传
send(accept_fd, (void*)in_buff, MESSAGE_LEN, 0);
}
close(accept_fd);
}
close(socket_fd);
return 0;
}
实现TCP客户端
TCP Client网络编程基本步骤
创建socket,指定使用TCP协议
使用connect连接服务器
使用recv/send接收/发送数据
#include<iostream>
#include<netinet/in.h>
#include<sys/socket.h>
#include<sys/types.h>
#include <stdio.h>
#include<unistd.h>
#include<string.h>
#include<arpa/inet.h>
#include <stdlib.h>
int main(int argc, char* argv[]){
struct sockaddr_in serveraddr;
int socket_fd = socket(AF_INET, SOCK_STREAM, 0);
if(socket_fd<0){
std::cout<<"Failed to create socket!"<<std::endl;
exit(-1);
}
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = 8111;
//inet_addr()将字符串转为整型
serveraddr.sin_addr.s_addr = inet_addr("127.0.0.1");
int ret = connect(socket_fd,
(struct sockaddr *)&serveraddr,
sizeof(struct sockaddr)
);
if(ret<0){
std::cout<<"Failed to connect server!"<<std::endl;
exit(-1);
}
char sendbuf[1024] = {0,};
char recvbuf[1024] = {0,};
while(1){
memset(sendbuf, 0, 1024);
fgets(sendbuf,1024,stdin);
ret = send(socket_fd, sendbuf, strlen(sendbuf), 0);
if(ret<=0){//没有发送数据,说明连接断开了
std::cout<<"Failed to send data!"<<std::endl;
break;
}
if(strcmp(sendbuf, "quit")==0){
break;
}
ret = recv(socket_fd, recvbuf, 1024, 0);
recvbuf[ret] = '\0';
std::cout<<"recv:"<<recvbuf<<std::endl;
}
close(socket_fd);
}
实现UDP通信
UDP Server网络编程基本步骤
创建socket ,指定使用UDP协议
将socket与地址和端口绑定
使用recv/send接收/发送数据
使用close关闭连接
高性能网络服务器处理并发
通过fork实现高性能网络服务器
通过select实现高性能网络服务器
通过epoll实现高性能网络服务器
利用I/O事件处理库来实现高性能网络服务器
通过fork实现高性能网络服务器
每收到一个连接就创一个子进程
父进程负责接受连接
通过fork创建子进程
只需修改上面的server端代码:
while(1){
socklen_t addr_len = sizeof(struct sockaddr);
accept_fd = accept(socket_fd, (struct sockaddr *)&remoteaddr, &addr_len);
//每当获取到一个新连接就fork一个子进程
pid = fork();
if(pid==0){//子进程
while(true){
//不断接受数据
ret = recv(accept_fd, (void*)in_buff, MESSAGE_LEN, 0);
if(ret==0){break;}
std::cout<<"recv:"<<in_buff<<std::endl;
**加粗样式** //数据原样回传
send(accept_fd, (void*)in_buff, MESSAGE_LEN, 0);
}
close(accept_fd);
}
}
if(pid!=0){
//套接字也是文件,当server端监听到有连接时,
//应用程序会请求内核创建Socket,
//Socket创建好后会返回一个文件描述符给应用程序
close(socket_fd);//文件描述符fork后共享,所以子进程不能关闭
}
可惜进程非常占用资源,创建子进程也耗费时间,fork并不可取
通过select实现高性能网络服务器
什么是异步IO
- 所谓异步IO ,是指以事件触发的机制来对IO操作进行处理。
- 与多进程和多线程技术相比,异步I/O技术的最大优势是系统开销小,系统不必创建进程/线程,也不必维护这些进程/线程,从而大大减小了系统的开销。
理解select模型
- 理解select模型的关键在于理解fd set类型
- fd_ set就是多个整型字的集合,每个bit代表-个文
件描述符 - FD ZERO表示将所有位置0
- FDSET是将fdset中的某-位置1
- select函数执行后,系统会修改fd_set中的内容
- select函数执行后,应用层要得新设置fd_set中的内容
要求
遍历文件描述符集中的所有描述符,找出有变化的描述符
对于侦听的socket和数据处理的socket要区别对待
socket必须设置为非阻塞方式工作
修改后的服务端:
#include<iostream>
#include<netinet/in.h>
#include<sys/socket.h>
#include<unistd.h>
#include<string.h>
#include<fcntl.h>
#define PORT 8111
#define MESSAGE_LEN 1024
#define FD_SIZE 1024
int main(int argc, char* argv[]){
int socket_fd;
int on = 1;
int ret = -1;
//并发10个
int backlog = 10;
int accept_fd;
char in_buff[MESSAGE_LEN] = {0,};
int max_fd = -1;
int events = 0;
fd_set fd_sets;
int accept_fds[FD_SIZE] = {-1,};
int curpos = -1;
int flags;
struct sockaddr_in localaddr, remoteaddr;
//用man socket可查函数具体
socket_fd = socket(AF_INET, SOCK_STREAM, 0);
if(socket_fd==-1){
std::cout<<"Failed to create socket!"<<std::endl;
exit(-1);
}
//将socket变成异步
flags = fcntl(socket_fd, F_GETFL, 0);
fcntl(socket_fd, F_SETFL, flags|O_NONBLOCK);
max_fd = socket_fd;
ret = setsockopt(
socket_fd,
SOL_SOCKET,
SO_REUSEADDR,
&on,
sizeof(on)
);
if(ret==-1){
std::cout<<"Failed to set socket options!"<<std::endl;
}
localaddr.sin_family = AF_INET;
localaddr.sin_port = PORT;
//所有地址都能侦听这个端口:
localaddr.sin_addr.s_addr = INADDR_ANY;
bzero(&(localaddr.sin_zero),8);
ret = bind(socket_fd, (struct sockaddr *)&localaddr, sizeof(struct sockaddr));
if(ret==-1){
std::cout<<"Failed to bind addr!"<<std::endl;
exit(-1);
}
//开启监听
ret = listen(socket_fd, backlog);
if(ret==-1){
std::cout<<"Failed to listen socket!"<<std::endl;
exit(-1);
}
int pid;
while(1){
FD_ZERO(&fd_sets);
FD_SET(socket_fd, &fd_sets);
for(int i=0; i<FD_SIZE; i++){
if(accept_fds[i]!=-1){
if(accept_fds[i]>max_fd){
max_fd = accept_fds[i];
}
FD_SET(accept_fds[i], &fd_sets);
}
}
events = select(max_fd+1, &fd_sets, NULL, NULL, NULL);
if(events<0){
std::cout<<"Failed to use select!"<<std::endl;
break;
}else if(events==0){
std::cout<<"timeout!"<<std::endl;
continue;
}else {
if(FD_ISSET(socket_fd, &fd_sets)){
for(int i=0; i<FD_SIZE; i++){
if(accept_fds[i]==-1){
curpos = i;
break;
}
}
socklen_t addr_len = sizeof(struct sockaddr);
accept_fd = accept(socket_fd, (struct sockaddr *)&remoteaddr, &addr_len);
flags = fcntl(accept_fd, F_GETFL, 0);
fcntl(accept_fd, F_SETFL, flags|O_NONBLOCK);
accept_fds[curpos] = accept_fd;
}
for(int i=0; i<FD_SIZE; i++){
if(accept_fds[i]!=-1&&FD_ISSET(accept_fds[i], &fd_sets)){
memset(in_buff, 0, MESSAGE_LEN);
ret = recv(accept_fd, (void*)in_buff, MESSAGE_LEN, 0);
if(ret==0){
close(accept_fds[i]);
break;
}
std::cout<<"recv:"<<in_buff<<std::endl;
send(accept_fds[i], (void*)in_buff, MESSAGE_LEN, 0);
}
}
}
}
close(socket_fd);
return 0;
}
epoll实现高性能网络服务器
优点:
- 没有文件描述符的限制
- 工作效率不会随着文件描述符的增加而下降
- Epoll经过系统优化更高效
Epoll事件的触发模式
- Level Trigger没有处理反复发送
- Edge Trigger只发送一次
Epoll重要的API
- int epoll _create()参数无意义,可忽略
- int epoll_ ctl(epfd, op, fd, struct epoll_event *event)向文件描述符fd添加事件
- int epoll _wait(epfd, events, maxevents, timeout)等待事件
Epoll的事件
- EPOLLET,水平触发
- EPOLLIN,有数据来
- EPOLLOUT,写数据
- EPOLLPRI,出现中断问题
- EPOLLERR,读写出问题
- EPOLLHUP,挂起
epoll _ctl 相关操作
对文件描述符操作:
- EPOLL CTL ADD
- EPOLL CTL MOD
- EPOLL CTL DEL
Epoll重要的结构体
typedef union epoll data {
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
} epoll_data_t;
struct epoll_ event {
uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};
修改后的server代码:
#include<iostream>
#include<netinet/in.h>
#include<sys/socket.h>
#include<unistd.h>
#include<fcntl.h>
#include<string.h>
#include<sys/epoll.h>
#define PORT 8111
#define MESSAGE_LEN 1024
#define MAX_EVENTS 20
#define TIMEOUT 500
int main(int argc, char* argv[]){
int socket_fd;
int on = 1;
int ret = -1;
int flag = 1;
//并发10个
int backlog = 10;
int accept_fd;
char in_buff[MESSAGE_LEN] = {0,};
struct sockaddr_in localaddr, remoteaddr;
//用man socket可查函数具体
socket_fd = socket(AF_INET, SOCK_STREAM, 0);
if(socket_fd==-1){
std::cout<<"Failed to create socket!"<<std::endl;
exit(-1);
}
//设为非阻塞
flag = fcntl(accept_fd, F_GETFL, 0);
fcntl(accept_fd, F_SETFL, flag|O_NONBLOCK);
ret = setsockopt(
socket_fd,
SOL_SOCKET,
SO_REUSEADDR,
&on,
sizeof(on)
);
if(ret==-1){
std::cout<<"Failed to set socket options!"<<std::endl;
}
localaddr.sin_family = AF_INET;
localaddr.sin_port = PORT;
//所有地址都能侦听这个端口:
localaddr.sin_addr.s_addr = INADDR_ANY;
bzero(&(localaddr.sin_zero),8);
ret = bind(socket_fd, (struct sockaddr *)&localaddr, sizeof(struct sockaddr));
if(ret==-1){
std::cout<<"Failed to bind addr!"<<std::endl;
exit(-1);
}
//开启监听
ret = listen(socket_fd, backlog);
if(ret==-1){
std::cout<<"Failed to listen socket!"<<std::endl;
exit(-1);
}
//创建描述符
int epoll_fd;
epoll_fd = epoll_create(256);
struct epoll_event ev;
//侦听数据到来事件
ev.events = EPOLLIN;
//这样通过ev.data.fd就可以知道是那个文件发的,如果拿到事件描述符为socket_fd,说明需要创建新socket
ev.data.fd = socket_fd;
//侦听描述符
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, socket_fd, &ev);
struct epoll_event events[MAX_EVENTS];
int event_number;
while(1){
//开启侦听描述符, events为epoll数组,MAX_EVENTS为最大事件数,返回同一时刻并发数
event_number = epoll_wait(epoll_fd, events, MAX_EVENTS,TIMEOUT);
for(int i=0; i<event_number; i++){
//如果是侦听者socket,创建新连接
if(events[i].data.fd==socket_fd){
socklen_t addr_len = sizeof(struct sockaddr);
accept_fd = accept(socket_fd, (struct sockaddr *)&remoteaddr, &addr_len);
//设为非阻塞
flag = fcntl(accept_fd, F_GETFL, 0);
fcntl(socket_fd, F_SETFL, flag|O_NONBLOCK);
//添加到epoll进行侦听
ev.events = EPOLLIN|EPOLLET;
ev.data.fd = accept_fd;
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, accept_fd, &ev);
}else if(events[i].events & EPOLLIN){
do{
memset(in_buff, 0, MESSAGE_LEN);
ret = recv(events[i].data.fd, (void*)in_buff, MESSAGE_LEN, 0);
if(ret==0){
close(events[i].data.fd);
}
if(ret == MESSAGE_LEN){
std::cout<<"maybe have data"<<std::endl;
}
}while(ret < 0 && errno == EINTR);//EINTR操作中断,重新读
if(ret < 0){
switch(errno){
case EAGAIN://读完数据
break;
default:
break;
}
}
if(ret>0){
std::cout<<"recv:"<<in_buff<<std::endl;
//数据原样回传
send(events[i].data.fd, (void*)in_buff, MESSAGE_LEN, 0);
}
}
}
}
close(socket_fd);
return 0;
}
epoll+fork实现高性能网络服务器
修改后的server代码:
#include<iostream>
#include<netinet/in.h>
#include<sys/socket.h>
#include<unistd.h>
#include<fcntl.h>
#include<string.h>
#include<sys/epoll.h>
#include <sys/wait.h>
#include<sys/types.h>
#define PORT 8111
#define MESSAGE_LEN 1024
#define MAX_EVENTS 20
#define TIMEOUT 500
#define MAX_PROCESS 4
int main(int argc, char* argv[]){
int socket_fd;
int on = 1;
int ret = -1;
int flag = 1;
pid_t pid = -1;
//并发10个
int backlog = 10;
int accept_fd;
char in_buff[MESSAGE_LEN] = {0,};
struct sockaddr_in localaddr, remoteaddr;
//用man socket可查函数具体
socket_fd = socket(AF_INET, SOCK_STREAM, 0);
if(socket_fd==-1){
std::cout<<"Failed to create socket!"<<std::endl;
exit(-1);
}
//设为非阻塞
flag = fcntl(accept_fd, F_GETFL, 0);
fcntl(accept_fd, F_SETFL, flag|O_NONBLOCK);
ret = setsockopt(
socket_fd,
SOL_SOCKET,
SO_REUSEADDR,
&on,
sizeof(on)
);
if(ret==-1){
std::cout<<"Failed to set socket options!"<<std::endl;
}
localaddr.sin_family = AF_INET;
localaddr.sin_port = PORT;
//所有地址都能侦听这个端口:
localaddr.sin_addr.s_addr = INADDR_ANY;
bzero(&(localaddr.sin_zero),8);
ret = bind(socket_fd, (struct sockaddr *)&localaddr, sizeof(struct sockaddr));
if(ret==-1){
std::cout<<"Failed to bind addr!"<<std::endl;
exit(-1);
}
//开启监听
ret = listen(socket_fd, backlog);
if(ret==-1){
std::cout<<"Failed to listen socket!"<<std::endl;
exit(-1);
}
//创建描述符
int epoll_fd;
struct epoll_event ev;
struct epoll_event events[MAX_EVENTS];
int event_number;
//创建预设进程
for(int i = 0; i<MAX_PROCESS; i++){
if(pid != 0){
pid = fork();
}
}
if(pid == 0){
epoll_fd = epoll_create(256);
ev.events = EPOLLIN;
ev.data.fd = socket_fd;//由于socket_fd是在fork前创建,因此epoll非阻塞时,有连接进来将出现惊群现象,然后只有一个进程将抢到,其他进程再次进入睡眠
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, socket_fd, &ev);
while(1){
//开启侦听描述符, events为epoll数组,MAX_EVENTS为最大事件数,返回同一时刻并发数
event_number = epoll_wait(epoll_fd, events, MAX_EVENTS,TIMEOUT);
for(int i=0; i<event_number; i++){
//如果是侦听者socket,创建新连接
if(events[i].data.fd==socket_fd){
socklen_t addr_len = sizeof(struct sockaddr);
accept_fd = accept(socket_fd, (struct sockaddr *)&remoteaddr, &addr_len);
//设为非阻塞
flag = fcntl(accept_fd, F_GETFL, 0);
fcntl(socket_fd, F_SETFL, flag|O_NONBLOCK);
//添加到epoll进行侦听
ev.events = EPOLLIN|EPOLLET;
ev.data.fd = accept_fd;
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, accept_fd, &ev);
}else if(events[i].events & EPOLLIN){
do{
memset(in_buff, 0, MESSAGE_LEN);
ret = recv(events[i].data.fd, (void*)in_buff, MESSAGE_LEN, 0);
if(ret==0){
close(events[i].data.fd);
}
if(ret == MESSAGE_LEN){
std::cout<<"maybe have data"<<std::endl;
}
}while(ret < 0 && errno == EINTR);//EINTR操作中断,重新读
if(ret < 0){
switch(errno){
case EAGAIN://读完数据
break;
default:
break;
}
}
if(ret>0){
std::cout<<"recv:"<<in_buff<<std::endl;
//数据原样回传
send(events[i].data.fd, (void*)in_buff, MESSAGE_LEN, 0);
}
}
}
}
close(socket_fd);
}else{//pid!=0父进程
do{
pid = waitpid(-1, NULL, 0);//等待子进程结束, 否则先结束父进程,子进程就会变成由init进程管理的进程,waitpid(-1, NULL, 0)在所有子进程停止后会返回-1
}while(pid!=-1);
return 0;
}
}