3.9.1.linux网络编程框架
3.9.1.1、网络是分层的
(1)OSI 7层模型 : 理论指导,7层
(2)网络为什么要分层
网络太复杂
(3)网络分层的具体表现
我们只研究 APP+API
3.9.1.2、TCP/IP协议引入
(1)TCP/IP协议是用的最多的网络协议实现
(2)TCP/IP分为4层,对应OSI的7层
(3)我们编程时最关注应用层,了解传输层,网际互联层和网络接入层不用管
3.9.1.3、BS和CS
(1)CS架构介绍(client server,客户端(client ) 服务器(server)架构: QQ、迅雷、)
(2)BS架构介绍(broswer server,浏览器服务器架构 : 优酷网站(不是APP)、)
3.9.2.TCP协议的学习1
3.9.2.1、关于TCP理解的重点
(1)TCP协议工作在传输层,对上服务socket接口,对下调用IP层
(2)TCP协议面向连接,通信前必须先3次握手建立连接关系后才能开始通信。
QQ聊天是 非 面向连接!!
(3)TCP协议提供可靠传输,不怕丢包、乱序等。
3.9.2.2、TCP如何保证可靠传输
(1)TCP在传输有效信息前要求通信双方必须先握手,建立连接才能通信
(2)TCP的接收方收到数据包后会ack(ack回应)给发送方,若发送方未收到ack会丢包重传
(3)TCP的有效数据内容会附带校验,以防止内容在传递过程中损坏
(4)TCP会根据网络带宽来自动调节适配速率(滑动窗口技术)
(5)发送方会给各分割报文编号,接收方会校验编号,一旦顺序错误即会重传。
多个包 发送时,每个包都有编号,保证顺序不好出错
以上不用我们实现,我们只要 知道怎么使用 就行!!!
3.9.3.TCP协议的学习2
3.9.3.1、TCP的三次握手
参考:TCP协议中的三次握手和四次挥手(图解)_三次握手和四次挥手图_whuslei的博客-CSDN博客
(1)建立连接需要三次握手
(2)建立连接的条件:服务器listen时客户端主动发起connect
写 代码时,我们不会碰到这些 细节!!!
3.9.3.2、TCP的四次握手
(3)关闭连接需要四次握手
(4)服务器或者客户端都可以主动发起关闭
注:这些握手协议已经封装在TCP协议内部,socket编程接口平时不用管
3.9.3.3、基于TCP通信的服务模式
(1)具有公网IP地址的服务器(或者使用动态IP地址映射技术)
公网数量是有限的,你有钱也不一定能容易买到, 都是使用动态IP 地址 映射技术
(2)服务器端:3个函数 socket、bind、listen后处于监听状态
(3)客户端socket后,直接connect去发起连接。
(4)服务器收到并同意客户端接入后会建立TCP连接,然后双方开始收发数据,收发时是双向的,而且双方均可发起
(5)双方均可发起关闭连接
3.9.3.4、常见的使用了TCP协议的网络应用
(1)http 应用层协议、ftp 纯传文件
(2)QQ服务器
(3)mail服务器:邮件服务器
3.9.4.socket编程接口介绍
3.9.4.1、建立连接
(1)socket。socket函数类似于open,用来打开一个网络连接,如果成功则返回一个网络文件描述符(int类型),之后我们操作这个网络连接都通过这个网络文件描述符。
(2)bind
(3)listen
(4)connect
3.9.4.3、发送和接收
(1)send和write :都是发送
(2)recv和read : 都是接收
3.9.4.4、辅助性函数
(1)inet_aton、inet_addr、inet_ntoa : IP地址转换,这三函数 不支持 IPV6
(2)inet_ntop、inet_pton : IP地址转换 支持 IPV6
1.socket
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
socket 函数:打开一个网络连接,成功返回一个网络文件描述符(类似于open函数)
int domain : 域 网络套接字,表示 ipv4的网络,还是ipv6 的网络 ,AF_INET :IPV4 ;
AF_INET6: IPV6
int type :指定类型 SOCK_STREAM :TCP网络类型; SOCK_DGRAM :UDP网络类型;
int protocol : 协议, 0表示默认协议,
返回值 -1 是错误;
2. bind 读邦德
#include <sys/socket.h>
int bind(int socket, const struct sockaddr *address,
socklen_t address_len);
bind 函数:把本地的IP地址 和 socket(就是socket函数返回的网络文件描述符) 绑定起来 ,
int socket: socket 函数成功返回一个网络文件描述符(类似于open函数)
const struct sockaddr *address : 输入型参数
struct sockaddr,这个结构体是网络编程接口中用来表示一个IP地址的,注意这个IP
地址是不区分IPv4和IPv6的(或者说是兼容IPv4和IPv6的)
socklen_t address_len : 长度
返回值 : 正确返回 0;
3. listen 读雷深 ,监听
#include <sys/socket.h>
int listen(int socket, int backlog);
int socket :socket 函数成功返回一个网络文件描述符(类似于open函数)
int backlog : 表示可以同时 监听几个(监听队列),队列得有一个长度,设置监听队列的 长度!
返回值 : 正确返回 0;
3.1 accept 阻塞等待 客服端来连接服务器
#include <sys/socket.h>
int accept(int socket, struct sockaddr *restrict address,
socklen_t *restrict address_len);
4.服务器 经过 socket 、bind 、listen ; 客户端先调用socket 就可以 来进行 连接 connect
connect 读 壳耐克特
#include <sys/socket.h>
int connect(int socket, const struct sockaddr *address,
socklen_t address_len);
int socket : socket 函数成功返回一个网络文件描述符(类似于open函数)
const struct sockaddr *address :连接的 服务器 IP 地址
socklen_t address_len :
返回值 : 正确返回 0;
5. 发送数据 write
#include <unistd.h>
ssize_t write(int fildes, const void *buf, size_t nbyte);
int fildes : open函数返回的 文件描述符( socket 函数成功返回一个网络文件描述符)
const void *buf : 数据 buf
size_t nbyte :数据 长度
6. 发送数据 send
#include <sys/socket.h>
ssize_t send(int socket, const void *buffer, size_t length, int flags);
int flags :如果 flags 是 0 ,就和 write 一样!
MSG_EOR : 发一个结尾,信息的结尾(特殊协议)
MSG_OOB : 带外数据(特殊协议)
正常通讯 用不到 flags
7. 辅助性函数 : IP 地址 转换
7.1 inet_ntop :n 网络(32位2进制), p 字符串 点分十进制
#include <arpa/inet.h>
const char *inet_ntop(int af, const void *src,
char *dst, socklen_t size);
7.2 inet_addr
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
in_addr_t inet_addr(const char *cp);
const char *cp : 字符串 点分十进制
in_addr_t : 返回一个 typedef uint32_t in_addr_t; 网络内部用来表示IP地址的类型
7.3 inet_pton
#include <arpa/inet.h>
int inet_pton(int af, const char *src, void *dst);
int af : 网络IPV4 还是 IPV6 。 AF_INET :IPV4 ; AF_INET6: IPV6
const char *src :字符串 点分 十进制 的 IP 地址
void *dst : 输出型参数 。如果是 IPV4 :struct in_addr ; 如果是 IPV6: struct in6_addr
inet_pton 函数 如果正确 返回1。
7.4 inet_ntop
#include <arpa/inet.h>
const char *inet_ntop(int af, const void *src,
char *dst, socklen_t size);
int af : 网络IPV4 还是 IPV6 。 AF_INET :IPV4 ; AF_INET6: IPV6
8 端口号 解决 大小端 模式
#include <arpa/inet.h>
uint32_t htonl(uint32_t hostlong); 当前电脑 字节序 转成 网络 字节序 32位
uint16_t htons(uint16_t hostshort); 当前电脑 字节序 转成 网络 字节序 16位
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);
h : host 主机
to : 转到
n : net 网络
l : long 4 个字节
(3)typedef uint32_t in_addr_t; 网络内部用来表示IP地址的类型
(4)struct in_addr
{
in_addr_t s_addr;
};
(5)struct sockaddr_in
{
__SOCKADDR_COMMON (sin_);
in_port_t sin_port; /* Port number. */
struct in_addr sin_addr; /* Internet address. */
/* Pad to size of `struct sockaddr'. */
unsigned char sin_zero[sizeof (struct sockaddr) -
__SOCKADDR_COMMON_SIZE -
sizeof (in_port_t) -
sizeof (struct in_addr)];
};
(6)struct sockaddr 这个结构体是linux的网络编程接口中用来表示IP地址的标准结构体,bind、connect等函数中都需要这个结构体,这个结构体是兼容IPV4和IPV6的。在实际编程中这个结构体会被一个struct sockaddr_in或者一个struct sockaddr_in6所填充。
示例1 inet_addr 函数 点分十进制 IP 地址 转成 十六进制的 IP 地址
网络字节序: 其实就是 大端模式,不允许 小端模式
代码:
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define IPADDR "192.168.1.102"/* 字符串 点分十进制 的IP 地址 */
int main(void)
{
in_addr_t addr = 0; /* typedef uint32_t in_addr_t; 网络内部用来表示IP地址的类型 */
/* 字符串 点分十进制 的IP 地址 转成 uint32_t 类型的 IP 地址 */
addr = inet_addr(IPADDR); /* inet_addr函数返回 uint32_t 类型的 IP 地址 */
printf("addr = 0x%x \n",addr);
return 0;
}
运行结果:
示例2 inet_pton 函数 :点分十进制 IP 地址 转成 十六进制的 IP 地址
代码:
#include <stdio.h>
#include <arpa/inet.h>
#define IPADDR "192.168.1.102"/* 字符串 点分十进制 的IP 地址 */
int main(void)
{
int ret = 0; /* 定义 inet_pton 返回值 */
struct in_addr addr = {0};/* 定义 inet_pton 函数的 第三个参数 */
/* 字符串 点分十进制 的IP 地址 转成 uint32_t 类型的 IP 地址 */
ret = inet_pton(AF_INET, IPADDR, &addr); /* */
if(ret!= 1)
{
printf("inet_pton error \n");
return -1;
}
printf("addr.s_addr = 0x%x \n",addr.s_addr);
return 0;
}
/*************************
addr.s_addr 是从哪里来的???
答:
typedef uint32_t in_addr_t; 网络内部用来表示IP地址的类型
struct in_addr
{
in_addr_t s_addr;
};
*************************/
运行结果:
示例3 inet_ntop 函数 :十六进制的 IP 地址 转成 点分十进制 IP 地址
#include <stdio.h>
#include <arpa/inet.h>
int main(void)
{
const char* ret = NULL ; /* 定义 返回值 ,当一个函数的 返回值 是cost时, 我们定义的 返回值也要加 const */
char buf[50]= {0};
struct in_addr addr = {0};/* */
addr.s_addr = 0x6601a8c0 ;/* 定义 uint32_t 类型的 IP 地址 */
/* 把 uint32_t 类型的 IP 地址 转成 字符串 点分十进制 的IP 地址 */
ret = inet_ntop(AF_INET,&addr,buf,sizeof(buf)); /* */
if(ret == NULL)
{
printf("inet_ntop error \n");
return -1;
}
printf("buf = %s \n",buf);
return 0;
}
/*************************
addr.s_addr 是从哪里来的???
答:
typedef uint32_t in_addr_t; 网络内部用来表示IP地址的类型
struct in_addr
{
in_addr_t s_addr;
};
*************************/
运行结果: