一、OSI七层结构
应用层(http,FTP)
表示层(文件、数据加密)
会话层(会话管理)
传输层(tcp协议)
网络层(IP协议)
数据链路层(IP地址转到物理地址)
物理层(以太网)
二、tcp/ip协议
(1)常用协议
tcp、udp、ip
(2)ip地址
标识主机(ip地址头包含两个内容:源地址IP、目标地址IP)
IP地址的点分十进制形式:xxx.xxx.xxx.xxx(点代表一个字节的结束,一共四字节,32位)
A类:第一个字节以0开头(即第一个字节的第一位为0),范围: 1.0.0.0 ~ 126.255.255.255
0.0.0.0可代表任意IP,所以用户不能私用;
127.0.0.0 ~ 127.255.255.255属于本地测试地址,不能跨越主机;
子网掩码:为了区分网络号与主机号
A类地址的子网掩码一般为: 255.0.0.0
A类地址第一个字节为网络号,后三个字节为主机号
B类:第一个字节以10开头,范围: 128.0.0.0 ~ 191.255.255.255
子网掩码一般为: 255.255.0.0
前两个字节为网络号,后两个字节为主机号
C类:第一个字节以110开头,范围: 192.0.0.0 ~ 223.255.255.255
子网掩码一般为: 255.255.255.0
前三个字节为网络号,最后一个字节代表主机号
D类:第一个字节以1110开头,范围: 224.0.0.0 ~ 239.255.255.255
E类: 第一个字节以11110开头 范围: 240.0.0.0 ~ 255.255.255.255
(3)端口号
标识进程(Linux系统通过端口号来决定哪一个进程来服务)
众所周知端口: 1~1023(1~255之前为众所周知端口,255~1023端口通常由UNIX系统占用)
已登记端口: 1024~49151 程序可以使用的端口
动态或私有端口: 49152~65535
(4)字节序
主机字节序:如果是Intel的芯片,采用i386架构,字节序为小端序
小端序:低地址存放低字节。输出是倒过来输出
例如: int a =0x12345678; 存放的方式: 78、56、34、12
打印第一个字节的话是 78 打印两个字节是 5678
主机序转网络字节序: htons(); htonl();
网络字节序:基本都是大端序
大端序:低地址存放高字节。
例如: int 0x12345678; 存放方式: 12 34 56 78
打印第一个字节的话是 12
网络字节序转主机序: ntohs(); ntohl();
三、套接字
(1)socket
是一种编程接口
是一种特殊的文件描述符
并不仅限与TCP/IP协议
(2)socket类型
①流式套接字(tcp编程)
提供一个面向连接、可靠的数据传输服务,数据无误差、无重复的发送且按照发送顺序接收。
内置流量控制,避免数据流淹没慢的接收方。数据被看作是字节流,无长度限制。
②数据报套接字(udp编程)
提供无连接服务。数据包以独立数据包的形式发送,不提供无差错保证,数据可能丢失或重复,顺序发送,可能乱序接收。
③原始套接字
可以对较低层次协议如ip、icmp直接访问。
(3)tcp和udp协议
共同点:属于传输层协议
TCP:传输控制协议
面向连接,保证可靠(流式套接字SOCK_STREAM)
UDP :用户数据报协议
无连接,不可靠 (数据报套接字SOCK_DGRAM)
四、tcp三次握手
第一次握手:客户端发送SYN报文
操作:客户端向服务器发送一个SYN(Synchronize Sequence Numbers,同步序列编号)报文,该报文不包含应用层数据,其首部的同步位SYN被置为1。
报文中还包括客户端的初始序列号(ISN,Initial Sequence Number),用于后续的数据传输和确认。
状态:发送SYN报文后,客户端进入SYN_SENT(同步已发送)状态,等待服务器的确认。
第二次握手:服务器发送SYN/ACK报文
操作:服务器收到客户端的SYN报文后,会以自己的SYN报文作为应答,同时指定自己的初始序列号。
服务器还会在SYN报文中对客户端的初始序列号加1作为确认号(ACK,Acknowledgment),表示已经收到了客户端的SYN报文。
状态:发送SYN/ACK报文后,服务器进入SYN_RCVD(同步收到)状态,等待客户端的确认。
第三次握手:客户端发送ACK报文
操作:客户端收到服务器的SYN/ACK报文后,会发送一个ACK报文作为响应。该ACK报文中的确认号是服务器初始序列号加1,表示已经收到了服务器的SYN报文。
此时,客户端的ACK报文可以携带应用层数据。
状态:发送ACK报文后,客户端进入ESTABLISHED(已建立连接)状态,表示连接已经成功建立。服务器收到客户端的ACK报文后,也进入ESTABLISHED状态,双方可以进行正常的数据传输。
五、TCP服务器的搭建流程
/*
头文件: #include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
*/
1、 socket();//创建一个套接字(创建网络通信文件)
头文件: #include <sys/types.h>
#include <sys/socket.h>
函数原型: int socket(int domain, int type, int protocol);
参数: domain :用来指示通信文件的适用范围
AF_UNIX, AF_LOCAL Local communication unix(7)
AF_INET IPv4 Internet protocols ip(7)
AF_INET6 IPv6 Internet protocols ipv6(7)
type :用来指示套接字文件的类型
SOCK_STREAM :流式套接字(数据是以字节流的方式进行传输)TCP通信;
SOCK_DGRAM :数据报套接字(数据是以一个包的形式进行发送)UDP通信;
SOCK_RAW :原始套接字
protocol :指示采用的协议,一般采用默认值0
返回值: 成功返回套接字文件描述符;
失败返回-1
2、 设置服务器的地址以及端口信息,需要用到系统提供的结构体 struct sockaddr_in;
struct sockaddr_in //提供给编程用户使用
struct sockaddr_in {
sin_family_t sin_family;
sin_port_t sin_port;
struct in_addr sin_addr;
}
struct in_addr {
uint32_t s_addr;
};
通用结构体 struct sockaddr; //内核使用该结构体来操作
struct sockaddr {
sa_family_t sa_family;
char sa_data[14];
}
(1)端口号实现主机字节序到网络字节序的转换
#include <arpa/inet.h>
uint16_t htons(uint16_t hostshort);
(2)IP地址实现点分形式的IP到网络字节序的IP地址转换
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
in_addr_t inet_addr(const char *cp);
点分十进制--->32位二进制网络字节序: inet_addr("127.0.0.1"); inet_aton();
32位二进制网络字节序--->点分十进制: inet_ntoa();
3、 bind();//绑定套接字
头文件: #include <sys/types.h>
#include <sys/socket.h>
函数原型: int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
参数: sockfd :用来指示所要设置的服务器的文件描述符;
addr :用来存储IP地址和端口号
addrlen :表示地址空间的大小
返回值: 成功返回0
失败返回-1
4、 listen();//监听套接字
头文件: #include <sys/types.h>
#include <sys/socket.h>
函数原型: int listen(int sockfd, int backlog);
参数: sockfd :用来指示所要启动监听的服务器的文件描述符
backlog :用来指示服务器在某一个时间点所能处理连接数队列的大小
返回值: 成功返回0
失败返回-1
5、 accept();//接收客户端请求(服务器等待客户端的连接,并且与客户端建立连接)
头文件: #include <sys/types.h>
#include <sys/socket.h>
函数原型: int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
参数: sockfd :用来指示所要监听客户端连接请求的服务器的文件描述符;
addr :用来存储IP地址和端口号
存储所连接的客户端的IP地址和端口号,如果不关心客户端的IP地址和端口号可以用NULL来填充参数
addrlen :指示的客户端的IP地址和端口号结构体空间的大小;不关心的话用NULL填充
返回值: 成功返回客户端连接成功的通信套接字文件描述符,用于后续数据的接收和发送;
失败返回-1
6、 close();//关闭套接字
发送数据:send()或者write()
send(connfd, buf, sizeof(buf), 0);//阻塞
头文件: #include <sys/socket.h>
#include <sys/types.h>
函数原型: ssize_t send(int socket, const void *buffer, size_t length, int flags);
参数: buffer : 发送缓冲区首地址
length : 发送的字节数
flags : 发送方式(通常为0)
返回值: 成功返回实际发送的字节数
失败返回-1
接收数据:recv()或者read()
recv(connfd, buf, sizeof(buf), 0);
头文件: #include <sys/socket.h>
#include <sys/types.h>
函数原型: ssize_t recv(int socket, const void *buffer, size_t length, int flags);
参数: buffer : 缓冲区首地址
length : z最大可接收的字节数
flags : 接收方式(通常为0)
返回值: 成功返回实际接收的字节数
失败返回-1
六、TCP客户端创建流程
1、 socket();//创建一个套接字
2、 设置服务器的地址以及端口信息 struct sockaddr_in
3、 connect();//请求连接客户端(客户端向服务器发送连接请求,并建立连接)
头文件: #include <sys/types.h>
#include <sys/socket.h>
函数原型: int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
参数: sockfd :用来指示发出请求的客户端的文件描述符
addr :用来存储IP地址和端口号(服务器的IP地址和端口号)
addrlen :指示的服务器地址空间的大小
返回值: 成功返回0
失败返回-1
4、 close();//关闭套接字
七、UDP服务器的创建流程
1、创建数据报套接字 socket(); SOCK_DGRAM
2、设置地址以及端口信息 struct sockaddr_in;
sin_family;
sin_port;
sin_addr.s_addr;
3、绑定套接字 bind();
4、接收数据 recvfrom();
头文件: #include <sys/types.h>
#include <sys/socket.h>
函数原型: ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);
参数: sockfd :创建的套接字,是socket的返回值
buf : 接收数据的缓存区首地址
len : 接收的数据长度
flags :默认设置为0,阻塞模式。
src_addr :客户端的地址信息结构体,recvfrom函数内部获取
addrlen :地址长度指针
返回值: 成功返回接收的字节数
失败返回-1
发送数据 sendto();
头文件: #include <sys/types.h>
#include <sys/socket.h>
函数原型: ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);
参数: sockfd :创建的套接字,是socket的返回值
buf : 缓存区首地址
len : 发送的数据长度
flags :默认设置为0,阻塞模式。
dest_addr :客户端的地址信息结构体,recvfrom函数内部获取
addrlen :地址长度
返回值: 成功返回接收的字节数
失败返回-1
八、TCP与UDP服务器的区别
1、 tcp创建一个流式套接字(SOCK_STREAM) --- udp创建一个数据报套接字(SOCK_DGRAM)
2、 设置地址信息,struct sockaddr_in --- 一样
3、 绑定套接字(bind()); ---一样
4、 监听listen() ---无
5、 接受客户端的请求accept() ---无
recvfrom();接受客户端数据,并且得到客户端的地址信息
6、关闭套接字close() ---关闭套接字