socket网络编程(上)
一、基本概念
OSI模型与TCP/IP模型:
OSI(开放系统互联参考模型)为计算机网络设计的一个通信模型,由于太过复杂而没有完全实现,仅仅是一套设计方案。
共分为七层:物理层、数据链路层、网络层、传输层、会话层、表示层、应用层
TCP/IP模型是OSI的具体实现,只实现出一部分,还没有完全实现,还有一些漏洞不太安全,是我们目前使用通信模型。
TCP/IP也叫协议簇,由一堆网络通信协议组成,其中TCP、IP是最重要的两个协议,因此简称TCP/IP。
共分为四层:
物理层:负责通过网络收发数据包
网络层:选择、流量控制,网络拥塞问题,IP协议是该层的核心协议
传输层:在机器之间建立用于会话的连接,UDP/TCP是该层的核心协议
应用层:主要为用户提供针对性的服务,该层的代表协议有:HTTP、SMTP、FTP、TELNET、POP、SNMP
对应关系:
OSI TCP/IP
会话层、表示层、应用层 应用层
传输层 传输层
网络层 网络层
物理层、数据链路层 物理层
UDP:面向无连接的通信,也叫用户数据报通信,就是发短信。
速度快、但不够安全,有数据丢失的可能性。
目标地址+数据包 直接发送,对方是否收到、数据有没有丢失,发送都是不知道的。
TCP:面向连接的通信,就是像是打开电话。
与UDP相比速度有些慢,但数据绝对不会丢失。
连接时的三次握手,数据包校验、重传、断开时的四次挥手来确保数据不会丢失。
IP地址:
在计算机网络有每计算机都必须有一个唯一的标识符(身份证号)IP地址,是IP协议在工作是需要的一个标识,在同一个作用域下是唯一的。
由4个0~255的整数组成,一般采用点分10进制表示,在计算机中用32位二进制存储。
公有IP和私有IP:
公有IP:在网络服务提供商登记过的IP地址叫公有IP,可以在互联网被访问到。
私有IP:公司、家庭的局域网中自己分配的IP地址,不能网络中被访问。
注意:私有IP主动访问公有IP,公有IP可以按原路返回数据,私有IP想与私有IP进行通信需要借助一个公有IP(服务器)
子网掩码:由4个0~255之间的整数组成,一般采用点分十进制表示。
子网掩码&IP地址 = 网络地址,网络地址相同说明在同一个子网内,这样就可以直接通信而不需要服务器。
网关地址:
负责出入口的计算机,一般由路由器担任(路由器就是一台具有路由功能的计算机)。
端口号:
IP地址代表通信的计算机,端口代表计算机的进程。
每个网络通信的进程都需要一个独一无二(当前系统中)编号,区分与哪个进程通信。
1~1024大多数已经被操作系统占用,我们在编程时一般使用1024以上的端口号。
二、套接字
socket是一种接口机制(ip、UDP/TCP、端口号),负责进程与进程间的连接通信。
相关函数:
int socket(int domain, int type, int protocol);
功能:创建一个socket对象
domain:
AF_INET 采用IPv4版本的ip地址通信。
AF_UNIX、AF_LOCAL 本地间的进程通信,还通过网络,仅仅是当前系统中的进程间通信。
type:
SOCK_STREAM 数据流协议 TCP
SOCK_DGRAM 数据报协议 UDP
protocol:特殊的通信协议,一般不使用,给0即可。
返回值:代表socket对象的描述符,失败返回-1。
准备通信地址:
// 基本地址 接口中使用的都是这种类型
struct sockaddr {
sa_family_t sa_family; //地址族
char sa_data[14]; //14字节,包含套接字中的目标地址和端口信息
}
// 网络通信地址
struct sockaddr_in {
__kernel_sa_family_t sin_family; 与socket中的domain参数保持一致即可
__be16 sin_port; 端口号
struct in_addr sin_addr; ip地址,是一个四字节整数
};
// 本地通信地址
struct sockaddr_un {
__kernel_sa_family_t sun_family; 与socket中的domain参数保持一致即可
char sun_path[UNIX_PATH_MAX]; socket文件的路径
};
】 注意:在使用时需要把网络通信地址、本地通信地址强制转换成基本地址
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
功能:将套接字与通信地址绑定。
sockfd:代表套接字的描述符。
addr:通信地址,需要转换。
addrlen:通信地址的字节数,bind函数就是依靠字节数来判断是本地通信地址还是网络通信地址。
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
功能:连接通信对方的socket对象。
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
功能:从socket接收消息,TCP专用,也可以使用read代替
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
功能:向socket发送消息,TCP专用,也可以使用write代替
flags:是否阻塞,一般写0,代表阻塞。
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);
功能:从socket接收消息,UDP专用
sockfd:socket描述符
buf:存储数据的缓冲区
len:缓冲区的大小
flags:是否阻塞,一般写0
src_addr:接收发送方的来时的地址
addrlen:地址结构体的字节数,用于区别是哪种地址
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);
功能:向socket发送消息,UDP专用
sockfd:socket描述符
buf:存储待发送的数据的首地址
len:要发送的数据字节数
flags:是否阻塞,一般写0
dest_addr:目标地址
addrlen:地址结构体的字节数,用于区别是哪种地址
大小端:一般的个人计算机采用的小端系统,而网络字节序是大端
uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
功能:把本地字节转换成网络字节序
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);
功能:把网络字节序转换成本地字节序
in_addr_t inet_addr(const char *cp);
功能:把点分十进制的字符串转换成网络字节序的32位整数
char *inet_ntoa(struct in_addr in);
功能:把网络字节序的32位整数转换成点分十进制的字符串
三、UDP通信的编程模型
本地通信 |
---|
进程A | 进程B |
---|---|
创建套接字 | 创建套接字 |
准备地址A | 准备地址B |
绑定地址A | 绑定地址B |
接收数据+返回地址 | 发送数据+地址A |
发送数据 | 接收数据 |
关闭套接字 | 关闭套接字 |
网络通信 |
---|
进程A | 进程B |
---|---|
创建套接字 | 创建套接字 |
准备地址 | 准备目标地址 |
绑定地址 | … |
接收数据 | 发送数据+地址 |
发送数据 | 接收数据 |
关闭套接字 | 关闭套接字 |
注意:一对一通信。
四、TCP通信编程模型
服务器 | 客户端 |
---|---|
创建套接字 | 创建套接字 |
准备地址 | 准备目标地址 |
绑定地址 | … |
监听 | 连接服务器 |
等待连接 | … |
分配进程线程进行服务 | … |
接收数据 | 发送数据 |
发送数据 | 接收数据 |
关闭套接字 | 关闭套接字 |
int listen(int sockfd, int backlog);
功能:设置多少个客户端可以处在排队状态
int accept(int sockfd, struct sockaddr *addr, socklen_t *);
功能:等待客户端连接
addr:输入型参数,获取客户端的地址
addrlen:获取地址的字节数
由于TCP是面向连接的通信,如果长时间没有数据发送,该连接可能会断掉。
解决方案:客户端定时向服务端发送无用的数据包(心跳包)。
在Linux、UNIX系统下recv、send可以使用read、write代替,但是windows不支持该函数。