目录
1. 网络基础
1.1 TCP/IP协议
- 用来检测网络传输中差错的传输控制协议 TCP
- 专门负责对不同网络进行互联的互联网协议 IP
1.2 网络的体系结构
- 网络采用分而治之的方法设计,将网络的功能划分为不同的模块,以分层的形式有机组合在一起
- 每层实现不同的功能,其内部实现方法对外部其他层次来说是透明的。每层向上层提供服务,同时使用下层提供的服务。
- 网络体系结构即指网络的层次结构和每层所使用的协议的集合。
- 两类非常重要的体系结构:OSI和 TCP/IP
- OSI模型相关的协议已经很少使用,但模型本身非常通用
- OSI模型是一个理想化的模型,尚未有完整的实现
- OSI模型共有七层
1.3 TCP/IP协议族体系
- TCP/IP协议族体系是Internet事实上的工业标准。
- 一共有四层
应用层 | Relnet,FTP,HTTP,DNS,SMTP等 |
传输层 | TCP和UDP |
网络层 | IP,ICMP和IGMP,端到端传输 |
网络接口和物理层 | 以太网,令牌环网,FDDI,wifi,gps/2G/3G/4G,驱动(屏蔽硬件差异) |
1.4 网络预备知识
1.4.1 socket
是一个编程接口,是一个特殊的文件描述符(对他执行IO的操作函数,比如read,write,close等),并不仅限于TCP/IP协议,面向连接TCP,无连接UDP。
socket代表网络编程的一种资源
分类:
- 1.流式套接字(SOCK_STREAM)。唯一对应 TCP 提供了一个面向连接,可靠的数据传输服务,数据无差错,无重复的发送顺序接收。内射击流量控制,避免数据流淹没慢的接收方。数据被看作式字节流,无长度限制。
- 2.数据包套接字(SOCK_DGRAM)。唯一对应UDP提供无连接服务器,数据包以独立数据包的形式被发送,不提供无差错保证,数据可能丢失或重复,顺序发送,可能乱序接收。
- 3.原始套接字(SOCK_RAW)。对应多个协议,发送穿透了传输层可以对较低层次协议如IP,ICMP直接访问。
1.4.2 IP地址
IP地址是Internet中主机的标识,Internet中的主机要与别的机器通信必须具有一个IP地址,IP地址为32为(Ipv4)或者128位(Ipv6),每个数据包都必须携带目的IP地址和源IP地址,路由器依靠此信息为数据包选择路由
表示的形式:常用点分形式,如202.38.64.10,最后都会转化成一个32位的无符号整数
1.4.3 端口号
- 16位数字,1-65535,
- 为了区分一台主机接收到的数据包应该转交给哪个任务进程处理,使用端口号来区别
- 预留端口,1-1023(FTP:24, SSH:22, HTTP: 80 ,HTTPS :469)
- 保留端口:1024-5000(不建议使用)
- 可以使用的端口: 5000~65535
- TCP端口号于UDP端口号独立
- 网络里的通信是由 IP地址+端口号 来决定的
1.4.4 字节序
小端模式:将低序字节存储在起始地址
大端模式:将高序字节存储在起始地址
- 字节序是指不同的CPU访问内存中的多字节数据时候,存在大小端的问题
- 如果CPU访问的是字符串,则不存在大小端问题
- 一般来说X86/ARM : 小端模式
- power/miop:arm作为路由时,大端模式
- 网络传输的时候采用大端模式
1.4.5 字节转换函数
#include <netinet/in.h>
uint16_t htons(uint16_t host16bitvalue); //返回网络字节序的值
uint32_t htonl(uint32_t host32bitvalue); //返回网络字节序的值
uint16_t ntohs(uint16_t net16bitvalue); //返回主机字节序的值
uint32_t ntohl(uint32_t net32bitvalue); //返回主机字节序的值
h代表host,n代表net,s代表short(两个字节),l代表long(4个字节),通过上面的4个函数可以实现主机字节序和网络字节序之间的转换。有时可以用INADDR_ANY,INADDR_ANY指定地址让操作系统自己获取
1.4.6 TCP/UDP对比
2. TCP 编程 API
2.1 socket 函数
#include include
int socket(int domain,int type,int protocol);
参数
domain
AF_INET
AF_INET6
AF_UNIX,AF_LOCAL
AF_NETLINK
AF_PACKET
type
SOCK_STREAM: 流式套接字,唯一对应于TCP
SOCK_DGRAM:数据报套接字,唯一对应着UDP
SOCK_RAW:原始套接字
protocol
一般填0,原始套接字编程时需填充
返回值
成功返回文件描述符
出错返回-1
如果是IPV6编程,要使用struct sockddr_in6结构体(man 7 IPV6),通常使用struct sockaddr_storage来编程。
2.2 bind 函数
int bind(int sockfd,const struct sockaddr *addr,socklen_t addrlen)
参数
sockfd:通过socket()函数拿到的fd
addr:采用struct sockaddr的结构体地址,通用结构体
struct sockaddr{
sa_family_t sa_family;
char sa_data[4];}
struct sockaddr_in{ 基于Internel通信结构体
as_family_t sin_family;
in_port_t sin_port;
struct in_addr sin_addr;
sin_zero , //填充字节,需清零
}
struct in_addr{
uint32_t s_addr;
}
addrlen:地址长度
一般情况下都是对 sockaddr_in 结构体进行初始化赋值,然后强制转换为 sockaddr 类型传入 bind 函数进行绑定。
2.3 listen 函数
int listen(int sockfd,int backlog);
参数:
sockfd: 通过socket()函数拿到的fd;
backLog:同时允许几路客户端和服务器进行正在连接的过程(正在三次握手),一般填5。
内核中服务器的套接字fd会维护2个链表
1.正在三次握手的客户端链表(数量=2*backlog+1)
2.已经建立好连接的客户端链表(已经完成三次握手分配好了的newfd)
返回值:
成功返回0
出错返回-1
listen(fd,5);//表示系统允许11(2*5+1)个客户端同时进行三次握手
2.4 accept函数
阻塞等待客户端连接请求
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
参数
sockfd:经过前面socket()创建并通过bind(),listen()设置过的fd
addr:指向存放地址信息的结构体的首地址,获取客户端IP地址和端口号
addrlen:存放地址信息的结构体的大小
返回值
成功,返回返回已经建立连接的新的newfd
出错,返回-1
2.5 客户端连接函数 connect
int connect (int sockfd, struct sockaddr * serv_addr, int addrlen)
参数:
sockfd:通过socket()函数拿到的fd
addr:struct sockaddr的结构体变量地址
addrlen:地址长度
返回值:
成功,返回0
失败,返回-1
3. UDP 编程
bind:绑定服务器:TCP地址和端口号
receivefrom:阻塞等待客户端数据
sendto:指定服务器的IP地址和端口号,要发送的数据
3.1 send 函数
ssize_t send(int sockfd,const void *buf,size_t len,int flags);
参数:
sockfd:socket函数返回的fd
buffer:发送缓冲区首地址
length:发送的字节
flags:发送方式(通常为0),作用和write一样
MSG_DONTWAIT,非阻塞
MSG_OOB:用于TCP类型的带外数据(out of band)
返回值:
成功:实际发送的字节数
失败:-1,并设置errno
3.2 recv 函数
int recv( SOCKET s, char FAR *buf, int len, int flags);
flag:一般填0,和read作用一样
特殊的标志:
MSG_DONTWAIT
MSG_OOB:读取带外数据
MSG_PEEK:流
4. 并发服务器
- 1.TCP多线程服务器
- 2.TCP多进程并发服务器