网络编程
文章目录
1 网络基础知识
端口:用16位来表示,即一个主机共有65536个端口.序号小于256的端口称为通用端口,端口(port)就是传输层的应用程序接口。
1 TCP/IP协议
1.1 OSI模型、TCP/IP模型
OSI协议参考模型:从上到下共分为7层:应用层、表示层、会话层、传输层、网络层、数据链路层及物理层。
TCP/IP协议参考模型:一共分为4层,自上而下分别是:
应用层:负责应用程序的网络访问,这里通过端口号来识别各个不同的进程。
传输层:负责端对端之间的通信会话连接与建立。传输协议的选择根据数据传输方式而定。
网络层:负责将数据帧封装成IP数据包,并运行必要的路由算法。
网络接口层:负责将二进制流转换为数据帧,并进行数据帧的发送和接收。数据帧是独立的网络信息传输单元。
1.2 TCP/IP协议族
TCP/IP实际上是一个庞大的协议族,包括了各个层次上的众多协议。
应用层:talnet、fp
传输层:TCP、UDP
网络层:ICMP、IGMP、IPv4、IPv6
网络接口层:ARP、RARP、MPLS
网络编程中涉及传输层TCP和UDP协议。
1.3 TCP
TCP的上一层就是应用层,因此,TCP数据传输实现了从一个应用程序到另一个应用程序的数据传递。应用程序通过编程调用TCP并使用TCP服务,提供需要发送的数据、用来区分接收数据应用的目的地址和端口号。通常应用程序通过打开一个socket来使用TCP服务,TCP管理到其他socket的数据传递。通过IP的源/目的可以惟一地区分网络中两个设备的连接,通过socket的源/目的可以惟一地区分网络中两个应用程序的连接。
三次握手协议
TCP 对话通过三次握手来进行初始化。三次握手的目的是使数据段的发送和接收同步,告诉其他主机其一次可接收的数据量,并建立虚连接。
1)请求主机发送一个连接请求报文。(SYN J)
2)接收主机接收连接后回复ACK报文,并为此连接分配资源。(SYN K, ACK J+1)
3)请求主机回送ACK报文,并分配资源。(ACK K+1)
四次挥手协议
1)客户端发送一个FIN,用来关闭数据传送。(FIN M)
2)服务器收到FIN,发回一个ACK,确认序号加1。(ACK M+1)
3)服务器关闭此连接,并发送一个FIN给客户端。(FIN N)
4)客户端发回ACK报文确认,并将确认序号加1。(ACK N+1)
TCP 数据包头
1)源端口、目的端口:16位长。标识出远端和本地的端口号。
2)序号:32位长。标识发送的数据报的顺序。
3)确认号:32位长。希望收到的下一个数据包的序列号。
4)TCP头长:4位长。表明 TCP 头中包含多少个 32 位字。其后6位未用。
ACK:ACK位为1表明确认号是合法的。若ACK为0,则数据报不包含确认信息,确认字段被省略。
PSH:表示是带有PUSH标志的数据。接收方因此请求数据包一到便将其送往应用程序而不必等到缓冲区装满时才传送。
RST:用于复位由于主机崩溃或其他原因而出现的错误连接。还可以用于拒绝非法的数据包或拒绝连接请求。
SYN:用于建立连接。
FIN:用于释放连接。
窗口大小:16位长,表示在确认了字节之后还可以发送多少个字节。
5)校验和、紧急指针:16位长。
6)可选项:0或多个32位字。
1.4 UDP
即用户数据报协议,它是一种无连接协议,一个UDP应用可同时作为应用的客户或服务器方。。它比TCP协议更为高效,也能更好地解决实时性的问题。
(1)UDP 数据报头
源地址、目的地址:16位长。标识出远端和本地的端口号。
数据报的长度是指包括报头和数据部分在内的总的字节数。
1.5 协议的选择
(1)对数据可靠性的要求,高可靠性的应用需选择TCP协议。
(2)应用的实时性,高实时性的应用需选择UDP协议。
(3)网络的可靠性,网络状况不好时选用TCP协议,网络状况很好时选用UDP协议。
2 socket
定义:socket也是一种文件描述符,是一种常用的进程之间通信机制,不仅能实现本机进程之间的通信,而且通过网络能够在不同机器上的进程之间进行通信。
一个socket用一个半相关描述{协议、本地地址、本地端口}来表示,一个完整的套接字则用一个相关描述{协议、本地地址、本地端口、远程地址、远程端口}来表示。
类型:socket有3种类型。
(1)流式socket(SOCK_STREAM):提供可靠的、面向连接的通信流,使用TCP协议,保证了数据传输的正确性和顺序性。
(2)数据报socket(SOCK_DGRAM):定义了一种无连接的服务,使用UDP协议,数据通过相互独立的报文进行传输,是无序的,且不保证是可靠、无差错的。
(3)原始socket:允许对底层协议如IP或ICMP进行直接访问,它功能强大但使用较为不便,主要用于一些协议的开发。
3 地址及顺序处理
sockaddr和sockaddr_in结构:用来保存socket信息,二者等效,可以相互转化。
struct sockaddr
{
unsigned short sa_family; //地址族
char sa_data[14]; //14字节协议地址,包含该socket的IP地址和端口号
};
struct sockaddr_in
{
short int sa_family; //地址族
unsigned short int sin_port; //端口号
struct in_addr sin_addr; //IP地址
unsigned char sin_zero[8]; //填充0
}
数据存储优先顺序:计算机数据存储有两种字节优先顺序:高位字节优先(称为大端模式)和低位字节优先(称为小端模式,PC机通常采用小端模式)。
大端即最大字节地址出现在最低有效字节(LSB)上,反之称为小端。例如:现有地址0x00FA,数据0x8844,则88是MSB,44是LSB,
注意:不管字节如何排序,最高有效字节(MSB)总在左边,最低有效字节(LSB)总在右边。
(1)函数格式:htons()、ntohs()、htonl()和ntohl()4个函数分别实现网络字节序和主机字节序的转化, h代host,n代表network,s代表short,l代表long。
#include <netinet/in.h>
uint32_t htonl(uint32_t hostint32);
uint16_t htons(uint16_t hostint16);
//返回以网络字节序表示的32或16位整数
uint32_t ntohl(uint32_t netint32);
uint16_t ntohs(uint16_t netint16);
//返回以主机字节序表示的32或16位整数
地址格式转换:inet_pton()函数是将点分十进制地址映射为二进制地址,而inet_ntop()是将二进制地址映射为点分十进制地址。两者都能够同时兼容IPv4和IPv6。
#include <arpa/inet.h>
const char *inet_ntop(int familly, const void *addr, char *str, socklen_t size);
//成功返回地址字符串指针,出错返回NULL
int inet_pton(int family, const char *str, void *addr);
//成功返回1,格式无效返回0,出错返回-1
family参数可选值为:AF_INET(IPv4协议)、AF_INET6(IPv6协议)。
名字地址转换:函数gethostbyname()是将主机名转化为IP地址,gethostbyaddr()是将 IP 地址转化为主机名,getaddrinfo()将一个主机名和一个服务名映射到一个地址。
#include <netdb.h>
struct hostent
{
char *h_name; /*正式主机名*/
char **h_aliases; /*主机别名*/
int h_addrtype; /*地址类型*/
int h_length; /*地址字节长度*/
char **h_addr_list; /*指向 IPv4 或 IPv6 的地址指针数组*/
}
struct addrinfo
{
int ai_flags; /*AI_PASSIVE, AI_CANONNAME*/
int ai_family; /*地址族*/
int ai_socktype; /*socket类型*/
int ai_protocol; /*协议类型*/
size_t ai_addrlen; /*地址字节长度*/
char *ai_canonname; /*主机名*/
struct sockaddr *ai_addr; /*socket 结构体*/
struct addrinfo *ai_next; /*下一个指针链表*/
}
struct hostent *gethostbyname(const char *hostname);
char *gethostbyaddr(struct hostent *ptr);
int getaddrinfo(const char *host, const char *service, const struct addrinfo *hint,
struct addrinfo *res);
//成功返回0,出错返回-1
hint:用于过滤地址的模板,包括ai_family、ai_flags、ai_protocol和ai_socktype字段,剩余字段必须为0。
成员 | 选项值 | 含义 |
---|---|---|
ai_family | AF_INET | IPv4因特网域 |
AF_INET6 | IPv6因特网域 | |
AF_UPSPEC | 未指定 | |
ai_socktype | SOCK_DGRAM | 固定长度的、无连接的、不可靠的报文传递 |
SOCK_RAM | IP协议的数据报接口 | |
SOCK_STREAM | 有序的、可靠的、双向的、面向连接的字节流 | |
ai_protocol | IPPROTO_IP | IP协议 |
IPPROTO_IPV4 | IPv4网际协议 | |
IPPROTO_IPV6 | IPv6网际协议 | |
IPPROTO_ICMP | 因特网控制报文协议 | |
IPPROTO_RAM | 原始IP数据包协议 | |
IPPROTO_TCP | 传输控制协议 | |
IPPROTO_UDP | 用户数据报协议 |
注意:服务器端调用 getaddrinfo()时通常将ai_flags设置 AI_PASSIVE。
客户端调用getaddrinfo()时ai_flags一般不设置AI_PASSIVE,但是主机名 nodename 和服务名 servname(端口)则应该不为空。
4 网络基础编程
4.1 socket基本函数
socket:该函数用于建立一个 socket 连接。
bind:该函数是用于将本地 IP 地址绑定到端口号,主 要用于 TCP 的连接。
listen:在服务端程序成功建立套接字和与地址进行绑定之后,调用 listen函数来创建一个等待队列,在其中存放未处理的客户端连接请求。
accept:服务端程序调用 listen函数创建等待队列之后,调用 accept()函数等待并接收客户端的 连接请求。
connect:该函数在 TCP 中是用于 bind的之后的 client 端,用于与服务器端建立连接。在 UDP 中类似 bind()函数的作用。
send、recv:这两个函数分别用于发送和接收数据,必须在建立连接之后才可用。
sendto、recvfrom:用在TCP时,后面的几个与地址有关参数不起作用,函数作用等同于 send和 recv;用在 UDP时,可以用在之前没有使用connect的情况下自动寻找指定地址并进行连接。
#include <sys/socket.h>
int socket(int family, int type, int protocol);
//成功返回套接字描述符,出错返回-1
int bind(int sockfd, struct sockaddr *my_addr, int addrlen);
int listen(int sockfd, int backlog);
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
int connect(int sockfd, struct sockaddr *serv_addr, int addrlen);
//成功返回0,出错返回-1
int send(int sockfd, const void *msg, int len, int flags);
int sendto(int sockfd, const void *msg,int len, unsigned int flags,
const struct sockaddr *to, int tolen);
//成功返回发送的字节数,出错返回-1
int recv(int sockfd, void *buf,int len, unsigned int flags);
int recvfrom(int sockfd,void *buf, int len, unsigned int flags,
struct sockaddr *from, int *fromlen);
//成功返回接收的字节数,出错返回-1
4.2 使用TCP协议socket编程流程
4.3 使用UDP协议socket编程流程
5 网络高级编程
当出现多个客户端连接服务器端的情况时,由于之前函数都是阻塞性函数,如果资源没有准备好,则调用该函数的进程将进入睡眠状态,就无法处理 I/O 多路复用的情况。
5.1 fcntl函数
非阻塞 I/O:可将 cmd 设置为 F_SETFL,将 lock 设置为 O_NONBLOCK。
异步 I/O:可将 cmd 设置为 F_SETFL,将 lock 设置为 O_ASYNC。
5.2 select函数
fcntl函数在实际使用时往往会对资源是否准备完毕进行循环测试,大大增加了不必要的CPU资源的占用。可以使用 select函数来解决这个问题, select函数还可以设置等待的时间,功能更加强大。
6 HTTP协议
6.1 简介
HTTP(超文本传输协议)是一个属于应用层的面向对象的协议,主要特点可概括如下:
- 支持客户/服务器模式。
- 简单快速:客户向服务器请求服务时,只需传送请求方法和路径。
- 灵活:允许传输任意类型的数据对象。
- 无连接:无连接的含义是限制每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接。
- 无状态:是指协议对于事务处理没有记忆能力。
6.2 URL
HTTP URL (URL是一种特殊类型的URI,包含了用于查找某个资源的足够的信息)的格式如下:
http://host[":"port][abs_path]
http表示要通过HTTP协议来定位网络资源;host表示合法的Internet主机域名或者IP地址;port指定一个端口号,为空则使用缺省端口80;abs_path指定请求资源的URI;
6.3 请求
http请求由三部分组成,分别是:请求行、消息报头、请求正文。
请求行格式如下:Method Request-URI HTTP-Version CRLF
请求方法:
- GET:请求获取Request-URI所标识的资源
- POST:在Request-URI所标识的资源后附加新的数据
- HEAD:请求获取由Request-URI所标识的资源的响应消息报头
- PUT:请求服务器存储一个资源,并用Request-URI作为其标识
- DELETE:请求服务器删除Request-URI所标识的资源
- TRACE:请求服务器回送收到的请求信息,主要用于测试或诊断
- CONNECT:保留将来使用
- OPTIONS:请求查询服务器的性能,或者查询与资源相关的选项和需求
6.4 响应
HTTP响应也是由三个部分组成,分别是:状态行、消息报头、响应正文。
状态行格式如下:HTTP-Version Status-Code Reason-Phrase CRLF
HTTP-Version表示服务器HTTP协议的版本;Status-Code表示服务器发回的响应状态代码;Reason-Phrase表示状态代码的文本描述。
状态代码有三位数字组成,第一个数字定义了响应的类别,且有五种可能取值:
- 1xx:指示信息–表示请求已接收,继续处理
- 2xx:成功–表示请求已被成功接收、理解、接受
- 3xx:重定向–要完成请求必须进行更进一步的操作
- 4xx:客户端错误–请求有语法错误或请求无法实现
- 5xx:服务器端错误–服务器未能实现合法的请求。
6.5 消息报头
HTTP消息报头包括普通报头、请求报头、响应报头、实体报头。
每一个报头域都是由名字+“:”+空格+值 组成。
普通报头:
- Cache-Control:用于指定缓存指令。
- Date:普通报头域表示消息产生的日期和时间。
- Connection:普通报头域允许发送指定连接的选项。
请求报头:请求报头允许客户端向服务器端传递请求的附加信息以及客户端自身的信息。 - Accept:用于指定客户端接受哪些类型的信息。
- Accept-Charset:用于指定客户端接受的字符集。
- Accept-Encoding:用于指定可接受的内容编码。
- Accept-Language:用于指定一种自然语言。
- Authorization:用于证明客户端有权查看某个资源。
- Host:用于指定被请求资源的Internet主机和端口号。
- User-Agent:允许客户端将它的操作系统、浏览器和其它属性告诉服务器。
响应报头:响应报头允许服务器传递不能放在状态行中的附加响应信息。 - Location:用于重定向接受者到一个新的位置。
- Server:包含了服务器用来处理请求的软件信息。
实体报头:定义了关于实体正文和请求所标识的资源的元信息。 - Content-Encoding:用作媒体类型的修饰符,指示了已经被应用到实体正文的附加内容的编码。
- Content-Language:描述了资源所用的自然语言。
- Content-Length:用于指明实体正文的长度,以字节方式存储的十进制数字来表示。
- Content-Type:用于指明发送给接收者的实体正文的媒体类型。
- Last-Modified:用于指示资源的最后修改日期和时间。