常用网络测试工具:
ifconfig //查看主机上,网卡网络信息
ping //测试 两台主机之间是否连通
telnet //远程登录工具
ssh // 硬件 (开发板)的远程登陆,加密登录
wireshark //网络抓包工具,抓取网络上的数据
一、 TCP首部
1.序号
占4个字节,在一个 TCP 连接中传送的字节流中给每一个字节都按顺序编号。整个要传送的字节流的起始序号必须在连接建立时设置。首部中的序号字段值则指的是本报文段所发送的数据的第一个字节的序号。
2.确认号
占4个字节,期望收到对方下一个报文段的第一个数据字节的序号。
3.数据偏移
指出TCP报文段的首部长度。TCP首部最小长度为20字节,最大长度为60字节。
4.确认ACK和同步SYN
仅当ACK=1,确认号有效。TCP规定,在连接建立后所有传送的报文段都必须把ACK置1。
在建立连接时用来同步信号。SYN置为1表示这是一个连接请求或连接接受报文。
TCP三次握手:
- 客户端向服务器发送SYN
- 服务器回复SYN+ACK
- 客户端回复ACK
5.终止FIN
用来释放一个连接。当FIN=1时,表明此报文段的发送方的数据已发送完毕,并要求释放运输连接。
TCP四次挥手:
- 客户端结束给服务器发FIN
- 服务器回复ACK
- 服务器结束给客户端发FIN
- 客户端回复ACK
二、 UDP首部

用户数据报UDP有两个字段:数据字段和首部字段。首部字段很简单,只有8个字节(图5-5),由四个字段组成,每个字段的长度都是两个字节。各字段意义如下:
- (1)源端口 ---- 源端口号。在需要对方回信时选用。不需要时可用全0。
- (2)目的端口 ---- 目的端口号。这在终点交付报文时必须要使用到。
- (3)长度 ---- UDP用户数据报的长度,其最小值是8(仅有首部)。
- (4)检验和 ---- 检测UDP用户数据报在传输中是否有错。有错就丢失。
三、 HTTP协议报文
HTTP有两类报文:请求报文和响应报文。
HTTP 协议的请求报文和响应报文的结构基本相同,由三大部分组成:
- 开始行(start line):描述请求或响应的基本信息;
- 首部行(header):使用键值对形式更详细地说明报文;
- 实体主体(entity):实际传输的数据,它不一定是纯文本,可以是图片、视频等二进制数据。
1. 请求报文

- 请求报文里的开始行,叫作请求行,由三部分构成:
- 请求方法 :是一个动词,如 GET/POST,表示对资源的操作;
- CRL (请求目标):通常是一个 URL,标记了请求方法要操作的资源;
- 版本号 :表示报文使用的 HTTP 协议版本。
这三个部分通常使用空格(space)来分隔,最后要用 CRLF 表示结束。CRLF表示"\r\n"标志,每一行末尾都要有CRLF,首部行全部结束也需要一个CRLF标志。
2. 响应报文

响应报文里的开始行,在这里叫"状态行"(status line),意思是服务器响应的状态。
比起请求行来说,状态行要简单一些,同样也是由三部分构成:
- 版本号:表示报文使用的HTTP协议版本;
- 状态码:一个三位数,用代码的形式表示处理的结果,例如200是服务器成功。
- 短语:数字状态码的补充,更详细的解释文字。
四、服务器模型
服务器可以实现与客户端的连接通信,现在所见的服务器,都是可以同时支持多个用户访问。在前面学习网络编程时,自己编写的服务器都只能实现连接一个服务器,那么让多个用户同时访问的服务器怎么做到的。
1. 迭代服务器:
可以线性处理多个客户端,但不能同时与客户端通信。就像排队一样,通信完一个再通信下一个。
cs
while(1)
{
accept;
while(1)
{
recv;
send;
}
}
2. 并发服务器:
支持同时多个用户进行访问,称作并发服务器,这种服务器一般需要用到多进程或线程来实现多用户的同时访问。
多进程的特点:
- 资源开销大。
- 进程的退出,需要考虑僵尸态资源的回收。
多线程特点:
- 相比进程,线程的创建和调度开销小。
- 共享资源方便。
- 存在线程间资源的竞争和同步问题。
cs
while(1)
{
accept;
pid_t pid=fork;
if(pid==0)
{
while(1)
{
recv;
send;
}
}
}
3. IO多路复用:
在一个进程中,有多个进程或线程,每个进程可以管理多个客户端的输入输出。
目的:提高并发的程度。
特点:
- 宏观并行,微观串行。
- 系统开销小。
- 系统不必创建进程/线程,也不必维护进程/线程。
IO操作模型有四种:阻塞IO,非阻塞IO,信号驱动IO,IO多路复用。
3.1 阻塞IO
在读取数据时,内核没有数据,程序会阻塞等待,直到有数据的输入。
例如:scanf、getchar、recv的默认方式。
特点:实现简单,效率低。
3.2 非阻塞IO
当区内核读取数据时,如果内核无数据,会立即返回一个EWOULDBLOCK的错误码。非阻塞方式如果想获得数据,必须配合轮询操作,一直不断读取数据,直到有数据。
特点:耗CPU资源。
非阻塞IO是在阻塞IO的基础上调整其为不再阻塞等待,有两种方式:
- 在程序开始阶段调整文件间的打开方式为非阻塞。
csfd=open(filename,O_RDONLY|O_NONBLOCK);
- 在程序执行阶段调整文件的执行方式为非阻塞。
csint flag=fcntl(fd,F_GETFL,0); flag=flag|O_NONBLOCK; fcntl(fd,F_SETFL,flag);
3.3 信号驱动IO
建立SIGIO信号处理程序,有数据来时,内核会给进程发信号,进程收到信号后,调用信号处理函数去做读写操作。
特点:异步处理,效率高,只能处理一路IO。
使用流程:
- 1.设置信号处理函数。
- 2.给文件描述符绑定调用进程作为属主。
- 3.设置标志。O_NONBLOCK使能非阻塞IO。O_ASYNC使能信号驱动IO。
cs
signal(SIGIO,do_handler);
fcntl(fd,F_SETOWN,pid);
fcntl(fd,F_SETLF,flag|O_ASYNC|O_NONBLOCK);
3.4 IO多路复用
用一个函数(select/epoll)监控多路IO,如果哪一路IO有读到数据,则对应的函数返回,告诉用户进程有就绪的IO,用户进程就去读数据。
3.4.1 select实现IO多路复用:
使用流程:
-
- 准备监控表。
-
- 添加文件到集合中。
-
- 准备nfds。
-
- 调用selec监听函数。
-
- 检测到select返回,用FD_ISSET确认哪路IO,然后读取。
缺点:
- 文件描述符数量受限。(在linux中限制1024个)
- 底层通过遍历实现,效率不高。
- 返回的是"就绪个数",应用层还要进一步筛选。
cs
1. socket;
2. bind;
3. listen;
4. 创建监控表
fd_set readfds;
FD_ZERO(&readfds);
FD_SET(fd,&readfds);
nfds=fd+1;
while(1)
{
fd_set backfds=readfds;
int ret=select(nfds,&backfds,NULL,NULL,NULL);
if(ret>0)
{
for(i=0;i<nfds;i++)
{
if(FD_ISSET(i,&readfds))
{
if(i==fd)//如果是监听表里的则建设连接
{
connfd=accept;
if(connet>nfds)
nfds=connet+1;
}
}else//不是说明已连接直接通信
{
recv;
}
}
}
}
}
3.4.1 epoll实现IO多路复用:
相比 select epoll 主要解决这些性能问题:
-
- 突破文件描述符数量限制
-
- 返回结果就是触发事件列表,不需要再遍历整个输入集合查找
-
- 使用共享内存思路,减少用户态与内核态之间反复传递大块监听信息
使用流程:
- 1.创建一个epoll实例
- 2.控制fd再epoll中的监听行为
- 3.等待epoll监听到的IO事件,返回一个已触发的事件列表
cs
1.socket
2.bind
3.listen
//1.epoll_create
创建监控用的epoll对象
//2.添加监控的fd --epoll_ctl
listenfd
while (1)
{
ret = epoll_wait();
if (ret > 0)
{
for (i = 0; i < ret; ++i)
{
if (rev[i].data.fd == listenfd)
{
connfd = accept();
}else //通信
{
recv;
}
}
}
}
4. 函数扩展
1). fcntl函数
语法:int fcntl(int fd, int cmd, ... /* arg */ );
功能:修改指定文件的属性信息。
参数:
- @fd --- 指定的文件描述符
- @cmd --- 要调整的文件属性宏名
- @ ... --- 可变长的属性值参数
2). select函数
头文件:#include <sys/select.h> #include <sys/time.h> #include <sys/types.h> #include <unistd.h>
语法 :int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
功能:监控多路IO,看是否有就绪的IO。
参数:
-
@nfds --- 表示要监控的集合中最大的文件描述符加1
-
@readfds --- 是否可读的集合
-
@writefds --- 是否可写的集合
-
@exceptfds --- 是否异常的集合
-
@timeout --- 0表示select是一个非阻塞调用;大于0表示select阻塞对应的一段时间;NULL表示默认阻塞调用。
csstruct timeval { long tv_sec; long tv_uses; }; //使用: struct timeval tv={3,0}; select(nfds,&readfds,NULL,NULL,&tv);
返回值:成功返回就绪的文件描述符的数量,失败返回-1且errno被设置。
注意:select函数调用完之后,会将监控结果的文件描述符集合带回给用户端,原先的集合不再是需要监控的文件描述符集合,而是就绪的文件描述符集合。如果需要循环监控,则需要中间变量保存原先的集合。
配套函数:
void FD_CLR(int fd, fd_set *set); --- 从set集合中清除fd
int FD_ISSET(int fd, fd_set *set); --- 判断fd是否在set集合中
void FD_SET(int fd, fd_set *set); --- 将fd添加到set集合中
void FD_ZERO(fd_set *set); --- 将set集合清零
3). epoll_create函数
头文件:#include<sys/epoll.h>
语法:int epoll_create(int size);
功能:创建一个 epoll 实例。
参数:
- @size --- size 不是最大监听数量,只是对内核初始分配的建议。
返回值:成功返回epoll对象的描述符,失败返回-1。
4). epoll_ctl函数
语法:int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
参数:
- @epfd --- epoll对象
- @op --- 对描述符执行的操作
EPOLL_CTL_ADD :添加监听
EPOLL_CTL_DEL :删除监听
EPOLL_CTL_MOD :修改监听 - @fd --- 要操作的fd
- @event --- 事件的结构体
struct epoll_event{
uint32_t events; -------epoll事件
epoll_data_t data;-------用户的数据
}
typedef union epolldata{
void* ptr;
int fd; ---------关心的文件
uint32_t u32;
uint64_t u64;
}epoll_data_t;
events事件的宏定义:
csEPOLLIN 文件可读 EPOLLOUT 文件可写 EPOLLERR 异常条件 EPOLLET 边沿触发,描述符跳变时上报
5). epoll_wait函数
语法:int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
功能:等待epoll监听到的IO事件,最多返回 maxevents 个事件。
参数:
- @epfd --- 监听的epoll对象
- @events --- 获得就绪时间的一个结构体指针(可以看作一个数组名)
- @maxevents --- 数组长度
- @timeout --- 设置超时时间,单位ms。0表非阻塞,-1表阻塞,大于0表阻塞多少毫秒
返回值:成功返回就绪的文件描述符数量,失败返回-1且errno被设置。
