游双 网络编程总结
像第五、六两章,目录上已经很清楚地表明了内容,只需要当成字典来用就行,或者不记得哪个去复习就行。只有遇到需要补充或者整理的,才进行处理。可以先不看,遇到不懂的,再看也行。可以直接看第七章。
第五章:
1.socket的主要API都定义在sys/socket.h头文件
实现主机名和IP地址之间的转换,以及服务名称和端口号之间的转换,定义在netdb.h头文件
字节序分为大端字节序(big endian)和小端字节序(little endian)。
现代PC大多采用小端字节序,因此小端字节序又被称为主机字节序。
一般大端字节序也称为网络字节序,即在网络中一般将字节序统一转化为大端进行传播,即接收方收到的第一个字节被当作高位看待
通过c语言的union结构实现机器的字节序的判断
将数据转化为同一种字节序进行传输的四个函数
2.通用socket地址结构体: sockaddr_storage和sockaddr,可以用于存放IPv4或IPv6地址等
地址族与协议族对应表。在bits/socket.h头文件中,地址族与协议族可以混用。
通用socket地址结构体设置与获取IP地址和端口号较烦琐,故一般使用专用socket地址结构体,如sockaddr_in和sockaddr_in6用于IPv4和IPv6。所有专用socket地址(以及sockaddr_storage)类型的变量在实际使用时都需要转化为通用socket地址类型sockaddr(强制转换即可),因为所有socket编程接口使用的地址参数的类型都是sockaddr(所有与socket编程相关的函数使用的地址参数的类型都是sockaddr)。
十进制字符串表示IPv4地址,以及用十六进制字符串表示IPv6地址。
(用点分十进制字符串表示的IPv4地址或用十六进制字符串表示的IPv6地址)和用网络字节序整数表示的IP地址之间的转换的函数
socket在linux中被当成文件描述符
socket函数:初始化socket,返回一个int类型
bind函数:给socket绑定IP和端口
listen函数:让socket处于监听状态
accept函数:从监听队列中获取连接
accept调用对于客户端网络断开毫不知情(这里与作者的实验和我的实验结果完全不一致???)
accept只是从监听队列中取出连接,而不论连接处于何种状态(如上面的ESTABLISHED状态和CLOSE_WAIT状态),更不关心任何网络状况的变化(这里与作者的实验和我的实验结果完全不一致???)
connnect:与服务器建立连接并返回一个socket供客户端读写,通过对socket的读写来与服务器进行通信。实际上只返回了一个int类型的变量,这就说明了connnect在系统内部完成了对真正的socket进行了一系列操作。
close:关闭指定的套接字。如果不止有一个进程引用了该套接字,则close的作用变为将套接字的引用计数减一。
shutdown:不论是否有其他进程引用了该套接字,都关闭此套接字。shutdown还可以只关socket的读或写。
linux下可以使用read和write或recv和send对数据进行读写。
recv成功时返回实际读取到的数据的长度,它可能小于我们期望的长度len。因此我们可能要多次调用recv,才能读取到完整的数据
send成功时返回实际写入的数据的长度
flags参数为数据收发提供了额外的控制,通常设置为0。flags参数只对send和recv的当前调用生效,而后面我们将看到如何通过setsockopt系统调用永久性地修改socket的某些属性。
发送和接收外带数据(紧急数据)。接收紧急数据会导致正常的数据不能通过一个recv调用全部读出
socket编程接口中用于UDP数据报读写的系统调用是recvfrom和sendto。recvfrom/sendto系统调用也可以用于面向连接(STREAM)的socket的数据读写,只需要把最后两个参数都设置为NULL
socket编程接口还提供了通用数据读写函数(recvmsg和sendmsg)
内核通知应用程序带外数据到达的两种常见方式是:I/O复用产生的异常事件和SIGURG信号。
利用sockatmark判断读取的数据是否为外带数据,然后利用带MSG_OOB标志的recv调用来接收带外数据
获取本端socket地址以及远端的socket地址:getsockname和getpeername
fcntl系统调用是控制文件描述符属性的通用POSIX方法。getsockopt和setsockopt是专门用来读取和设置socket文件描述符属性的方法。getsockopt和setsockopt通过设置socket选项的方法来读取和设置socket文件描述符属性
服务器端的有些soket选项必须在调用listen前进行设置才会有效,即三次握手前设置。客户端的有些soket选项必须在调用connnect前进行设置才会有效,即三次握手前设置。
listen监听队列中接受的连接至少已经完成了TCP三次握手的前两个步骤。connect调用后,TCP三次握手就已完成
对监听socket设置这些socket选项,那么accept返回的连接socket将自动继承这些选项。(这句话什么意思,什么叫监听socket选项)
SO_REUSEADDR选项:
服务器程序可以通过设置socket选项SO_REUSEADDR来强制使用被处于TIME_WAIT状态的连接所占用的socket地址。
我们也可以通过修改内核参数/proc/sys/net/ipv4/tcp_tw_recycle来快速回收被关闭的socket,从而使得TCP连接根本就不进入TIME_WAIT状态
SO_RCVBUF和SO_SNDBUF选项:
设置接受和发送缓存的大小,设置的大小不能低于系统设定的最小值。我们可以修改/proc/sys/net/ipv4/tcp_rmem和/proc/sys/net/ipv4/tcp_wmem来强制TCP接收缓冲区和发送缓冲区的大小没有最小值限制。
这里有一个通过tcpdump抓取的通信过程,很有意思可以看一看
SO_RCVLOWAT和SO_SNDLOWAT选项(一般设为1):大于SO_RCVLOWAT时可读,大于SO_SNDLOWAT可写
SO_LINGER选项:
用于控制close系统调用在关闭TCP连接时的行为。(如果通过close没有成功关闭,而是引用计数减一,此时这些行为还会发生吗??)
l_onoff等于0:代表此功能关闭
l_onoff不为0,l_linger等于0:丢弃TCP发送缓冲区中残留的数据,发送复位报文段,这种情况给服务器提供了异常终止一个连接的方法
l_onoff不为0,l_linger大于0:具体见书中内容
可以用主机明代替IP地址,用服务器名代替端口号。
gethostbyname和gethostbyaddr:
gethostbyname函数通常先在本地的/etc/hosts配置文件中查找主机,如果没有找到,再去访问DNS服务器。
gethostbyaddr函数根据IP地址获取主机的完整信息。
getservbyname和getservbyport:getservbyname函数根据名称获取某个服务的完整信息,getservbyport函数根据端口号获取某个服务的完整信息。它们实际上都是通过读取/etc/services文件来获取服务的信息的。
上面四个函数都是不可重入的,但有可重入的版本。
正如Linux下所有其他函数的可重入版本的命名规则那样,这些函数的函数名是在原函数名尾部加上_r(re-entrant)。
getaddrinfo:可通过主机名获取IP,通过服务器名获取端口号。
getnameinfo:通过socket地址同时获得以字符串表示的主机名和服务名
第六章 高级I/O函数:本章实际上本质是对文件描述符进行操作的函数进行介绍
用于创建文件描述符的函数,包括pipe、dup/dup2函数
用于读写数据的函数,包括readv/writev、sendfile、mmap/munmap、splice和tee函数。
用于控制I/O行为和属性的函数,包括fcntl函数
pipe函数创建的这两个文件描述符fd[0]和fd[1]分别构成管道的两端。用fd[1]写入的数据,用fd[0]读出。
写端文件描述符fd[1]的引用计数减少至0,则fd[0]的read操作将返回0,即读取到了文件结束标记(EOF)
读端文件描述符fd[0]的引用计数减少至0,则fd[1]的write操作将失败,并引发SIGPIPE信号
我们可以使用fcntl函数来修改管道容量
socketpair函数方便地创建双向管道。
dup和dup2:复制文件描述符。dup——取当前可用最小整数值作为文件描述符。dup——取第一个不小于file_descriptor_two的整数值作为文件描述符。
【注】通过dup和dup2创建的文件描述符并不继承原文件描述符的属性,比如close-on-exec和non-blocking等(这两个是啥??我怎么不记得见过??)
先关闭标准输出文件描述符STDOUT_FILENO(其值是1),然后通过dup()复制socket文件描述符connfd。此时由于1是最小可用的,c故onnfd就被复制到了值为1的文件描述符上。此时printf产生的输出会传送到客户端中。
readv函数将数据从文件描述符读到分散的内存块中,即分散读;writev函数则将多块分散的内存数据一并写入文件描述符中,即集中写。
Web服务器上实现集中写的程序
sendfile函数实现:两个文件描述符之间的传递数据完全在内核中操作,从而避免了内核缓冲区和用户缓冲区之间的数据拷贝,这种数据传递也叫零拷贝。
用sendfile函数传输文件的一个程序。和“Web服务器上实现集中写的程序”实现相同功能,但是简单多了。
mmap函数和munmap函数:用于申请和释放内存。申请的内存可以作为进程间通信的共享内存
POSIX是IEEE为要在各种UNIX操作系统上运行的软件而定义的一系列API标准的总称
上面提到的大多数函数都是成功时返回0或操作东西的数量,失败则返回-1并设置errno。
splice函数:用于在两个文件描述符之间移动数据,也是零拷贝操作。fd_in和fd_out必须至少有一个是管道文件描述符。
将客户端发送的数据原样返回给客户端的程序
tee:tee函数在两个管道文件描述符之间复制数据,也是零拷贝操作,它不消耗数据(啥叫不消耗数据??)。fd_in和fd_out必须都是管道文件描述符
6-5代码标注:从STDIN_FILENO就可以看出来,只要使用相应的文件描述符,就可以对系统内已经定义好的文件描述符进行使用
fcntl函数:提供了对文件描述符的各种控制操作,ioctl比fcntl能够执行更多的控制。
将文件描述符设置为非阻塞的(这会产生什么效果呢???)
当被关联的文件描述符可读或可写时,系统将触发SIGIO信号;当被关联的文件描述符(而且必须是一个socket)上有带外数据可读时,系统将触发SIGURG信号。将信号和文件描述符关联的方法,就是使用fcntl函数为目标文件描述符指定宿主进程或进程组。使用SIGIO时,还需要利用fcntl设置其O_ASYNC标志。SIGIO和SIGURG这两个信号与其他Linux信号不同
除了TIME_WAIT,去记录以下FIN_WAIT1等东西是啥,并解决accept那里运行与作者的结果不一致问题。
第九章 I/O复用
I/O复用:监听多个文件描述符上面的事件
先看我对I/O复用的总结
int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);
:将fd加入到想要监听的集合中,总共有三个集合,分别为readfds、writefds、exceptfds
。“发生了什么事件”也是通过readfds、writefds、exceptfds
这三个集合获取。
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
:将fd和需要监听的事件包装成struct pollfd
结构体,“发生了什么事件”是通过struct pollfd
结构体的revents获取。
EPOLLONESHOT事件:
select、poll和epoll都能同时监听多个文件描述符,通过某种结构体变量来告诉内核监听哪些文件描述符上的哪些事件,并使用该结构体类型的参数来获取内核处理的结果。
select:只能处理可读、可写及异常事件,需要用三个参数分别处理这三种事件
poll:任何事件都被统一处理,接口较为简洁。每次select和poll调用都返回整个用户注册的事件集合(其中包括就绪的和未就绪的)
epoll:它在内核中维护一个事件表,返回就绪的事件
select和poll都只能工作在相对低效的LT模式,而epoll则可以工作在ET高效模式。并且epoll还支EPOLLONESHOT事件。
EPOLLONESHOT事件:一个socket连接在任一时刻都只被一个线程处理,直到此线程处理完毕以后,其他线程才能处理此socket,实现过程如下:文件描述符fd注册了EPOLLONESHOT事件,则操作系统最多触发fd上注册的一个可读、可写或者异常事件,且只触发一次,触发以后,epoll就检测不到fd上面发生的事件。触发事件一般会开启一个线程去处理相应的事件。此线程最后要将fd上的EPOLLONESHOT事件重置,这样使得epoll有机会再次检测到fd上的事件,进而使得其他线程有机会为fd服务。
从上面可以看出,注册了EPOLLONESHOT事件以后,每一个时刻只有一个线程在对fd进行操作。EPOLLONESHOT事件解决的是同一个时刻,有多个线程操作同一个套接字的情况。
EPOLLONESHOT事件的示例代码、其他的示例代码。
linux内部实现了一个超级服务xinetd,用于处理telnet、echo等操作。
第十章 信号
信号:通知进程的信息
kill函数:一个进程给其他进程发送信号的API是kill函数
可以自定义一个处理信号的函数,也可以使用SIG_IGN(忽略信号)和SIG_DFL(使用信号的默认处理方式)
Linux信号
使用sigaction函数可以重启被信号中断的系统调用
信号掩码指定哪些信号不能发送给本进程
我有时看到复杂的数据类型的名字的时候我会有点懵,但是没有关系,我就把它理解成一个类(事实上也有可能是一个结构体等)
signal函数:传入信号处理函数的指针和要捕获的信号
sigaction比较高级的signal函数
Linux使用数据结构sigset_t来表示一组信号,以及对sigset_t的一些操作
sigprocmask用于设置或查看进程的信号掩码
被挂起的信号:收到被屏蔽的信号,信号会被挂起。等到屏蔽被取消以后,进程会立即获取该信号。多次获取到同一被屏蔽的信号,屏蔽在被取消以后,该信号的处理函数也只被触发一次
sigpending获得进程当前被挂起的信号集
fork调用产生的子进程将继承父进程的信号掩码,但具有一个空的挂起信号集。
信号处理函数往管道的写端写入信号值,主循环监听管道的读端文件描述符上的可读事件。信号事件就能和其他I/O事件一样被处理,即统一事件源
代码没看
网络编程相关信号
SIGHUP信号可以用来强制服务器重读配置文件
xinetd处理SIGHUP的流程
往一个读端关闭的管道或socket连接中写数据将引发SIGPIPE信号
用SIGURG检测带外数据是否到达。代码还没看
黑马程序员笔记:https://www.cnblogs.com/codingbigdog/p/16246557.html
第11章 定时器
定时器:用于计时,时间到了,就去处理相应的任务。如过一定时间就去检测一个客户连接的活动状态。
Linux提供了三种定时方法:socket选项SO_RCVTIMEO和SO_SNDTIMEO、SIGALRM信号、I/O复用系统调用的超时参数。
1.socket选项SO_RCVTIMEO和SO_SNDTIMEO:使用setsockopt函数和选项SO_RCVTIMEO/SO_SNDTIMEO给socket设置接收数据超时时间和发送数据超时时间。函数send、sendmsg、recv、recvmsg、accept和connect中使用此socket会产生不同的效果。
send、sendmsg、connect:在一定时间没有将数据发送成功,返回错误。
recv、recvmsg、accept:在一定时间没有接收到数据,返回错误。
代码11-1connect_timeout.cpp:connect中使用设置定时的套接字——十秒之内没有连接成功,返回错误。
2.SIGALRM信号:由alarm和setitimer函数设置的实时闹钟一旦超时,将触发SIGALRM信号(见C++ 信号处理)。因此,我们可以利用该信号的信号处理函数来处理定时任务。本书中介绍了两个具体的定时器实现代码:
- 基于升序链表:将定时器都放入一个链表中,链表按照超时时间做升序排序。
代码:util_timer结构体代表一个定时器,其中包含一个时间,包含一个回调函数
sort_timer_lst类:用于生成一个升序的定时器链表,并通过成员函数添加、删除链表中的定时器 - 升序定时器链表的实际应用——关闭非活动连接:将超过一定时间没有使用的套接字从epoll树上删除。
代码:每隔一段时间alarm发送一个SIGALRM信号,信号处理函数通过通道将此信号通知给主循环,主循环中通过tick()遍历定时器链表并删除超时套接字。
3.I/O复用系统调用的超时参数:将epoll_wait函数的形参timeout设为相应的值。经过timeout的时间后,epoll_wait会被触发并epoll_wait的返回值为0。
管理定时器的容器:时间轮和时间堆。容器指的是装一个一个定时器的地方。
-
时间轮:一个轮子上有N个槽,每个槽代表一个链表。每一个链表代表一个时间。比如一个槽代表一秒,一圈有六十槽,那么一圈就代表六十秒。当指针指向了第n个槽的时候,可能代表已经过了n秒(假设一个槽代表一秒),也可能代表过了N+n、2N+n、3N+n...秒。处理该槽上超时的节点,并删除此节点。
代码:使用数组tw_timer *slots[N]表示时间轮,slots中每个元素指向一个定时器链表,链表无序。
tw_timer类用于表示定时器,成员变量rotation表示圈数,time_slot表示定时器属于时间轮上哪个槽。在本代码中,一个槽代表一秒,一圈六十秒,所以rotation=x,time_slot=y代表x分t秒。 -
时间堆:使用最小堆来存储定时器,每一次都将堆顶的定时器的超时时间作为依据,当超时时间到了就执行堆顶定时器的任务,并删除堆顶元素。
代码:还没看。
问题:
1.wan和lan的区别??
2.如何根据IP数据报中的8位服务类型TOS,实现相应服务??
3.在5.1.2中的内存对齐是什么意思??
4.在5.4中的程序想要说明什么???我没看懂?、
5.“3.9 TCP超时重传”有个问题未解决???
6."P172内存对齐"是什么??
7.“p175void指针”
8."宏有什么用?直接定一个变量不就行了"
9.什么叫不可重入?
答:如果一个函数的执行期间被中断后,到重新恢复到断点进行执行的过程中,函数所依赖的环境没
有发生改变,那么这个函数就是可重入的
我们知道中断时确实保存一些上下文,但是仅限于返回地址,cpu寄存器等之类的少量上下文,而函
数内部使用的诸如全局或静态变量,buffer等并不在保护之列,所以如果这些值在函数被中断期间发
生了改变,那么当函数回到断点继续执行时,其结果就不可预料了。
10.CGI服务器啥??
11.人手一个的web服务器项目,我该如何脱颖而出?
答:C++基础基础较为牢固、算法题做得比较好、还有其他别的项目、我学的正是进去工作以后要用的。。。。。。。。