网络:
1. OSI模型?
* 物理层:硬件连接的接口。
* 数据链路层: 通信道的无差错传输,提供数据成帧。
* 网络层: 将数据正确并且迅速的从原点主机传送到目的主机。
* 传输层: 掩盖下层结构细节,保证会话层的消息正确的传输到另一方的会话层。
* 会话层: 实现两端主机之间的会话管理。
* 表示层: 信息转换,压缩,解密,代码转换。
* 应用层: 提供日常的应用。
2. 网络?
网络连接也是一个文件,它也有文件描述符!
stdin:0 标准输入
stdout:1 标准输出
数据传送不会消失,顺序传送,发送和接收非同步。
TCP/IP
TCP:保证数据的可靠性,和正确性。
IP:控制数据如何从源头到达目的地。
3. 进程?
正在执行的程序被称为进程,进程树顶端是init控制进程。
* fork() 调用进程创建新的进程。
* wait() 进程同步措施,使一个进程等待进程,到另一个进程结束为止。
* exit() 终止一个进程的运行。
* exec() 系统调用。
pid_t fork(void); // 创建一个进程,返回进程id是pid_t类型。
4. TCP和UDP?
tcp:可靠的通信传输,面向连接,面向字节流,数据传输慢。
udp:不需要建立连接,不可靠通信传输,面向报文,速度快。
5. 常见端口及对应服务?
21 FTP文件传输协议
22 SSH
23 Telnet
80 HTTP超文本传输协议
6379 redis默认端口
6. tcp三次握手?
* 客户端发送请求SYN报文段到服务器,进入SYN_SEND状态,等待服务器确认。 发送 SYN=1 seq=client_isn
* 服务器收到SYN进行确认,回复给客户端,进入SYN_RECV状态。 发送 SYN=1 seq=server_isn ack=client_isn+1
* 客户端收到SYN_ACK报文段,进入ESTABLISHED状态,完成三次握手。 发送 SYN=0 seq=clien_isn+1 ack=server_isn+1
7. tcp四次挥手?
* 客户端发送连接释放报文,停止发送数据,进入终止等待状态 1 发送 FIN=1 seq=client_isn
* 服务器收到报文,发出确认报文ack带上自己序列号seq,进入等待关闭状态 发送 ack=client_isn+1 seq=server_isn
* 客户端收到服务器报文后,进入终止等待状态 2
* 服务器发送完数据后,向客户端发送释放报文,自身进入最后确认状态。 发送 FIN=1 ack=client_isn+1 seq=server_isn
* 客户端收到确认后,发送最后确认,进入wait状态,等待2*MSL时间。 发送 ack=server_isn+1 seq=client_isn+1
* 服务器收到客户端发出的确认,立即进入close状态。
8. 为什么结束连接的TIME_WAIT需要等待2MSL才能返回close状态?
客户端发送的最后一步的确认,不完全是可靠的,网络可能存在别的异常情况。
所以客户端会进入TIME_WAIT状态,设置定时器,如果服务器一直发送第三步消息,client会再次发送确认信息,并继续等待2MSL时间。
2MSL:网络的最大存活时间,一个发送和一个回复所需的最大时间。
直到服务器收到最后确认,不再发送FIN消息,就知道已经被成功接收,结束tcp连接。
9. 建立了连接,客户端故障。
tcp存在保活计时器。 服务器在既定时间(2h)没有收到客户端的任何消息。然后每隔一段时间(75s)发送一个探测报文段。
如果一连10次都没有反应,将自动关闭这个连接。
10. IP地址分类。
A 1.0.0.1 - 127.255.255.254
B 128.0.0.1 - 191.255.255.254
C 192.0.0.1 - 223.255.255.254
D 224.0.0.0 - 239.255.255.255
E 保留用于实验使用
11. tcp比udp慢的原因?
* 收发数据前后进行的连接设置及清除过程。
* 收发过程中为保证数据可靠性添加的流控制。
12. udp的传输函数:
ssize_t sendto(int sock, void *buff, size_t nbytes, int flags, struct sockaddr *to, socklen_t addrlen);
sock: 创建的套接字
buff: 传输数据的缓冲地址值。
nbytes: 待传输的字节数据
flags: 可选项参数,无则是0
to: 发送对象地址。
addrlen:地址结构体长度
13. udp的接收函数?
ssize_t recvfrom(int sock, void *buff, size_t nbytes, int flags, struct sockaddr* from, socklen_t addrlen);
和上面基本信息一样。
14:udp
不存在请求和连接,因此某种意义上说,不区分服务器和客户端。
15:回声消息,read和write
tcp在发送数据时候,如果数据过大,会分包发送,如果write和read是同时进行的,那么会存在,write还没有把数据全部发送到服务器,
服务器回复一部分数据时,read读取会出现问题。
16. 什么是协议?
网络中交换数据的标准规则,约定的集合。
17. 大端小端?
大端:高位字节存放到低位地址。
小端:高位字节存放到高位地址。
* 程序判断:int i = 0x12345678 char *c = (char *)&i; c[0] == 0x12 小端
* typedef union {
* int i;
* char c;
* } myunion;
* myunion.i = 1; return myunion.i == myunion.c
18. tcp和udp的4层协议栈?经过的层级结构差异
应用层, tcp/udp层,IP层,链路层。
19. write和read函数。
write和read交互,write可以直接一次性将数据发送到另一端,但是read不一定能全部读取,因为数据量太大的情况下,read读不全数据。
20. udp
tcp在不可靠的IP层进行流控制,udp缺少这种流控制。
tcp和udp最基本的区别就是流控制,其它区别所剩无几。
21. 压缩文件传输。
压缩文件必须选择tcp进行传输,如果中途有数据丢失,解压就会失败,所以必须是选择tcp进行传输。
22. gethostbyname 通过域名转换为IP
struct hostent{
char *h_name; // office name 官方域名
char **h_aliases; // alias list 同一IP可以绑定多个域名,出官方域名,还有其它域名。
int h_addrtype; // IP地址族信息,
int h_length; //
char **h_addr_list; // IP
}
23. Nagle算法。tcp 消息。
只有收到前一数据的ACK消息时,Nagle算法才发送下一次数据。
开启Nagle算法时候,会合理的减少流量,但是传输速度会降低。
关闭Nagle算法时候,会增加网络流量,传输速度会增加,尤其是在大数据量的传输中,这种速度会很明显。
注:未准确判断数据特性时不应禁用Nagle算法。
禁用Nagle算法: int opt_val = 1
开启Nagle算法: int opt_val = 0
setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (void*)&opt_val, sizeof(opt_val))
24. 并发服务器的实现。
多进程服务器: 通过创建多个进程提供服务。
多路复用服务器: 通过捆绑并统一管理I/O对象提供服务。
多线程服务器: 通过生成与客户端等量的线程提供服务。
25. 进程和子进程
fork创建进程之后,父进程返回子进程的进程ID
子进程返回0.
父子进程,只共享一段代码,有不同的数据存储空间。
26. 僵尸进程
子进程比父进程先结束,父进程又没有回收子进程,释放子进程占用的资源。
应向创建子进程的父进程传递子进程的exit参数或return返回。
* 只有父进程主动发起请求,操作系统才会传递该值。(父母要负责回收自己孩子。)
27. wait函数
调用wait函数可以将终止的子进程退出。wait函数会阻塞等待,直到有子进程退出。
WIFEXITED 子进程正常终止时返回 true
WEXITSTATUS 返回子进程的返回值
28. waitpid函数
wait函数会引起程序阻塞。waitpid(pid_t, pid, int * statloc, int options);成功返回终止的子进程ID,失败返回-1
参数: pid 等待目标子进程,若为-1,则可以等待任意子进程退出。
statloc: status options: WNOHANG即使没有终止的子进程,也不会进入阻塞状态。
29. 信号。
void (*signal(int signo, void(*func)(int)))(int);
void function(int flag) {}
调用signal signal(SIGNO, function)
接收第二个参数是一个函数指针(地址),函数的参数是int类型,返回值是void
30. 信号的高级用法:
struct sigaction act;
act.sa_handler = read_childproc;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
sigaction(SIGCHLD,&act,0); //注册信号,触发SIGCHILD信号后将会调用act函数
return 和 exit将会触发到该信号。
read_childproc函数如下:
void read_childproc(int sig){
int status;
pid_t id = waitpid(-1, &status, WNOHANG);
if(WIFEXITED(status)){
printf("Removed proc id : %d \n",id);
printf("child send : %d \n",WEXITSTATUS(status));
}
}
31. 进程间通信->pipe
int pipe(int fileds[2]) fileds[0] 出口, fileds[1] 入口
test:
int fds[2]; pipe(fds); fork一个父进程,和子进程,子进程写数据到文件,父进程从文件读数据,就实现了进程间通信、
32. fread和read
fread是C语言的库,read是系统调用。
read每次读取要求大小的数据,从用户态切换到内核态(损耗性能)
fread每次从内核缓冲区读取较多的数据,放到应用进程缓冲区,下次再取直接去应用进程缓冲区取,不用消耗太多性能。
33. fwrite和write
write是系统调用,每次需要将数据写到磁盘,写的大小是要求的大小,依然涉及频繁的用户态和内核态切换。
fwrite是库函数,每次讲数据写入到换乘区,等缓冲区满了,一次写入磁盘。或者使用fflush冲洗缓冲区。
34. sigaction 捕捉信号的类型
struct sigaction {
void (*sa_handler)(int); // 信号处理函数
void (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask; // 设置处理该信号时,暂时将sa_mask指定信号搁置
int sa_flags; // 信号其它相关操作。
void (*sa_restorer)(void); //
}
注:联合 信号的高级用法使用
35. 使用多任务并发服务器
fork出来的子进程,需要关闭ser_sockID 只保留父进程的ser_sockID,持续去accept客户端的连接。
父进程,只负责接收客户端的连接,客户端的cli_sockID 也需要关闭。
36. I/O复用.
select 将多个文件描述符,集中到一起进行监控。
fd_set set; FD_ZERO, 清零。 FD_SET 设置1, FD_CLR 单个清零。
37. select函数
int select(int maxfd, fd_set *readset, fd_set *writeset, fd_set *exceptset, const struct
timeval *timeout)
调用select函数后,除了发生变化的描述符外,剩下的所有位都将被初始化为0。
38. select使用。
在进行select使用时候,需要维护两个描述符集合(use,copy),两个内容一样。
复制的copy,用作select调用传入,当有收到消息时候,copy内除了有消息的描述符,其它都被置0。
所以只需要循环检查,FD_ISSET 文件描述符是否存在copy中,如果存在,就证明这个有消息了。开始处理吧。
39. 多播(组播)、广播.
int so_ard = 1
广播: setsockopt(sockid, SOL_SOCKET, SOL_BORADCAST, (void *)&so_ard, sizeof(so_ard))
40. 标准IO的缺点
* 不容易进行双向通信
* 有时可能需要频繁的调用fflush
* 需要以FILE结构体指针的形式返回文件描述符
41. select的弊端
无论如何优化程序性能,也无法同时接入上百个客户端,select不适合以web服务器端。
42. select函数
每次调用select函数时,向操作系统传递检视对象信息。
仅向操作系统传递1次监视对象,监视的范围发生变化时,只通知发生变化的事项。
43. epoll的优点
无需编写以检视状态变化为目的的针对所有文件描述符的循环语句。
调用对于select函数的epoll_wait函数时,无需每次传递检视对象信息。
44. epoll需要的3个函数
epoll_create 创建保存epoll文件描述符的空间
epoll_ctl 想空间注册并注销文件描述符
epoll_wait 与select函数类似,等待文件描述符发生变化
45. select和epoll不同点
select需要FD_SET, FD_CLR函数。 epoll需要通过epoll_wait函数。
select下调用select函数等待描述符变化。 epoll中调用epoll_wait函数。
select下通过fd_set查看检视对象的变化。 epoll中通过结构体epoll_event将发生变化的文件描述符几种到一起。
struct epoll_event {
__unit32_t events;
epoll_data_t data;
}
46. epoll_create(int size)函数
epoll_create创建的文件描述符保存空间称为"epoll例程",
参数size的值,决定epoll例程的大小。
47. epoll_ctl
生成epoll例程后,在内部注册监视对象文件描述符,使用epoll_ctl函数。
epoll_ctl(int epfd, int op, int fd, struct epoll_event * event)
op参数的模型如下
* EPOLL_CTL_ADD 注册到例程中
* EPOLL_CTL_DEL 例程中删除文件描述符
* EPOLL_CTL_MOD 更改文件描述符的关注事件发生情况
48. epoll_wait
int epoll_wait(int fd, struct epoll_event* events, int maxevents, int timeout)
epfd: 发生监视范围的epoll
events: 保存发生事件的文件描述符集合的结构体地址值
maxevents: 第二个参数中保存最大的事件数
timeout:等待时间
epoll回声服务器端
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#define BUFF_SIZE 100
#define EPOLL_SIZE 50
int main(int argc, char *argv[])
{
int ser_sock, cln_sock;
struct sockaddr_in ser_adr, cln_adr;
socketlen_t adr_sz;
int str_len, i;
char buf[BUFF_SIZE];
struct epoll_event *ep_events;
struct epoll_event event;
int epfd, event_cnt;
ser_sock = socket(PF_INET, SOCK_STREAM, 0);
memset(&ser_adr, 0, sizeof(ser_adr));
ser_adr.sin_family = AF_INET;
ser_adr.sin_addr.s_addr = htonl(INADDR_ANY);
ser_adr.sin_port = htons(atoi(argv[1]));
if(bind(ser_sock, (struct sockaddr*)&ser_adr, sizeof(ser_adr)) == -1)
error();
if(listen(ser_sock, 5) == -1)
error();
// 创建epoll的例程空间
epfd = epoll_create(EPOLL_SIZE); // 返回值和套接字相同,最后也需要close掉
ep_events = malloc(sizeof(struct epoll_event)*EPOLL_SIZE); // 申请一个epoll空间池
event.events = EPOLLIN; // 需要读取数据情况
event.data.fd = ser_sock; // socket的文件描述符
epoll_ctl(epfd, EPOLL_CTL_ADD, ser_sock, &event); // 添加一个事件
while(1)
{
// 返回发生事件的文件描述符数, event_cnt个数
event_cnt = epoll_wait(epfd, ep_events, EPOLL_SIZE, -1);
if(event_cnt == -1)
break;
for(i=0; i<event_cnt; i++)
{
if(ep_events[i].data.fd == ser_sock) // 新的客户端连接走这里。
{
// 将新连接的客户端fd加入到epoll例程中
adr_sz = sizeof(cln_adr);
cln_sock = accept(ser_sock, (struct sockaddr*)&cln_adr, &adr_sz);
event.events = EPOLLIN;
event.data.fd = cln_sock;
epoll_ctl(epfd, EPOLL_CTL_ADD, cln_sock, &event);
printf("connected client: %d\n", cln_sock);
}
else
{
// 实现和已连接的客户端通信
str_len = read(ep_events[i].data.fd, buf, BUFF_SIZE);
if(str_len == 0)
{
// 处理完事件之后,将其从例程中删除。
epoll_ctl(epfd, EPOLL_CTL_DEL, ep_events[i].data.fd, NULL);
close(ep_events[i].data.fd);
printf("close client:%d\n", ep_events[i].data.fd);
}
else
{
write(ep_events[i].data.fd, buf, str_len);
}
}
}
}
close(ser_sock);
close(epfd);
return 0;
}