C++ advance(六)Linux 高并发网络编程开发 --1. 网络编程基础 socket

一、网络基础

1. 网络

(1) C/S-client/server

优点: (a) 协议选用灵活 (b) 可以缓存数据
缺点: (a) 对用户安全构成威胁 (b) 开发工作量大,调试困难 (多线程实时数据同步、实时数据同步)

(2) B/S-browser/server

优点:跨平台
缺点:只能使用 http
跨平台:使用能够跨平台的语言,例如:QT 和 java
给程序员一套接口,在不同平台下,接口调用的 API 是不一样的
java 是跑在 java 虚拟机上的,执行在不同平台都是跑在 java 虚拟机上的
不同语言实现跨平台的方式是不一样的

2. 协议的概念

规则:数据传输和数据解释的规则
原始协议 ———>(改进、完善)———> 标准协议
典型协议:TCP/UDP HTTP FTP IP ARP

例如:原始的 ftp 协议
A 依次发送文件名、文件大小、文件内容给 B
B 就可以依据这些收到的信息,复制一个文件在自己的机器上
发送的数据包不会太大,因为内存是栈空间,栈空间不大
linux 默认的栈空间是 8M

3. 分层模式

(1) 7 层模型 -OSI:

物 — 双绞线,光纤
数 — 数据的传输和错误检测
网 — 为数据包选择路由
传 — 提供端对端的接口 tcp/udp
会 — 解除或建立与别的节点的联系
表 — 数据格式化,代码转换,数据加密
应 — 文件传输,电子邮件,文件服务,虚拟终端

(2) TCP/IP:4 层模型

层级 - 常见协议
网络接口层 - 以太网帧协议
网络层 - IP
传输层 - TCP/UDP
应用层 - FTP,HTTP,SSH,TELNET
一般说的传输模型都是 4 层模型
软件程序员主要负责应用层的协议,上层协议由操作系统完成。

例如:两个用户在使用腾讯的聊天软件
A 用户发送 hello 给 B;
腾讯开发的应用层协议会在 hello 字符串外面加上一层应用层协议数据;
然后操作系统会再在外面加上传输层和网络层和数据链路层的数据;
B 收到数据包之后,操作系统拆开传输层和网络层和数据链路层的数据;
然后腾讯的应用在拆应用层的数据;
最后返回 hello 给 B。

4. 协议格式

数据包的封装思想

(1) 以太网帧格式 — 借助 mac 地址完成数据传送


arp 数据报 — 根据 IP 获取 mac 地址


mac 地址其实就是网卡编号
全球的网卡编号都是唯一的,生产网卡的厂家就那几间,有一个组织专门管理
虚拟机的 MAC 地址分配与修改

(2) IP 格式

重要参数
4 位版本:ipv4、ipv6
8 位生存时间 (TTL): 最多能经过多少跳
32 位源 IP 地址:数据发送端地址
32 位目的 IP 地址:数据接收端地址

ipv6 主要是中国在推动,因为
(a) 中国的网络较晚发展,公网 IP 是不够用的
192.168.XXX.XXX 这是局域网 IP,所以要通过路由器
在 socket 编程的时候就会发现不同局域网的电脑不同直接通信,就是因为没有公网 IP
(b) DNS 域名解析服务器
全世界只有 13 台根服务器,10 台在美国,1 台在日本,1 台在英国,1 台在瑞典
(3) UDP 数据包格式

16 位源端口
16 位目的端口

(4) TCP 数据报格式数据

16 位源端口
16 位目的端口
32 位序号:TCP 要经过三次握手
32 位确认序号:TCP 要经过三次握手

TCP 三向交握 (Three-way Handshake)
6 个标志位
存储空间
16 位滑动窗口:双方收发数据速度不协调的时候,控制其中一方阻塞
A 给 B 发数据,A 发得快,B 收得慢,则 B 可以告诉 A 它得缓存空间大小是多少,如果 A 发得数据使得缓存空间满了,则 A 就不会再发数据了

5. 数据的发送和接收


可以传输的字节数并非全部都是实际数据,其中包含一部分的协议头。

6.tcp,udp 传输层协议

(1) tcp: 面向连接的安全的流式传输协议

连接的时候,进行三次握手。
数据发送的时候,会进行数据确认,若数据丢失,会进行数据重传。
流式传输

流式传输
(2) udp: 面向无连接的不安全的报式传输

连接的时候不会握手。
数据发送出去之后就不管了。
报式传输

报式传输

如果数据包丢失会全丢,不存在丢失一半的情况。
如果需要安全的传输模式,需要在应用层再做封装。
一般而言,在聊天室开发,小公司会使用 TCP 协议,大公司会在 UDP 协议上在应用层自己开发封装。

二、Socket 编程

1.socket 编程

什么是 socket?网络通信的函数接口
封装了传输层协议,例如:TCP 和 UDP

2. 套接字概念

套接字 = IP+Port
回顾 read/write 操作,操作的是文件描述符;创建一个套接字,得到的也是文件描述符。

进程间通信,匿名管道
1) 由 pipe 系统调用,管道由父进程建立
2) 单工通信的
3) 在关系进程中进行 (父进程和子进程、同一个父进程的两个子进程之间)
4) 管道位于内核空间,其实是一块缓存

比较:
管道位于内核缓冲区 (内存中一块存储空间,由环形队列实现),管道的读写两端分别对应一个文件描述符
套接字:创建成功,得到的是一个文件描述符 fd,fd 操作的也是一个内核缓冲区。
套接字默认也是阻塞的(是不同文件描述符对应不同文件的性质)。

3. 网络字节序(大端、小端)

内存中的多字节数据相对于内存地址有大端和小端之分,磁盘文件中的多字节数据相对于文件中的偏移地址也有大端小端之分。网络数据流同样有大端小端之分,那么如何定义网络数据流的地址呢?

发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出,接收主机把从网络上接到的字节依次保存在接收缓冲区中,也是按内存地址从低到高的顺序保存,因此,网络数据流的地址应这样规定:先发出的数据是低地址,后发出的数据是高地址。

TCP/IP 协议规定,网络数据流应采用大端字节序,即低地址高字节。如果主机是大端字节序的,发送和接收都不需要做转换,反之则需要。

为使网络程序具有可移植性,使同样的 C 代码在大端和小端计算机上编译后都能正常运行,可以调用以下库函数做网络字节序和主机字节序的转换。

1
2
3
4
5
6
#include <arpa/inet.h>

uint32_t htonl (uint32_t hostlong);
uint16_t htons (uint16_t hostshort);
uint32_t ntohl (uint32_t netlong);
uint16_t ntohs (uint16_t netshort);
h 表示 host,n 表示 network,l 表示 32 位长整数,s 表示 16 位短整数。

4.IP 地址转换函数

inet_pton 函数和 inet_ntop 函数的用法及简单实现

1
2
3
4
5
#include<arpa/inet.h>
int inet_pton (int family, const char \*strptr, void *addrptr);
// 返回:若成功则为 1, 若输入不是有效的表达格式则为 0, 若出错则为 - 1
const char \*inet_ntop (int family, const void \*addrptr, char *strptr, size_t len);
// 返回:若成功则为指向结果的指针, 若出错则为 NULL

函数名中的 p 和 n 非别代表表达(presentation)和数值(numeric)。地址的表达格式通常是 ASCII 字符串,数值格式则是存放到套接字地址结构中的二进制值。这两个函数的 family 参数既可以是 AF_INET,也可以是 AF_INET6。如果以不被支持的地址族作为 family 的参数,这两个函数就都返回一个错误,并将 errno 置为 EAFNOSUPPORT。

(1) 本地 IP 转网络字节序(字符串 ->int)
inet_pton 函数尝试转换由 strptr 指针所指的字符串,并通过 addrptr 指针存放二进制结果。若成功则返回 1, 否则如果对所指定的 family 而言输入的字符串不是有效的表达式,那么值为 0。

(2) 网络字节序转本地 IP(int-> 字符串)
inet_ntop 函数进行相反的转换,从数值格式(addrptr)转换到表达格式(strptr)。len 参数是目标存储单元的大小,以免该函数溢出其调用者的缓冲区。为有助于指定这个大小,在 < netinet/in.h > 头文件中有如下定义:

1
2
#define INET_ADDRSTRLEN   16
#define INET6_ADDRSTRLEN 46
如果 len 太小,不足以容纳表达式结果(包括结尾的空字符),那么返回一个空指针,并置 errno 为 ENOSPC。
inet_ntop 函数的 strptr 参数不可以是一个空指针。调用者必须为目标存储单元分配内存并指定其大小。调用成功时,这个指针就是该函数的返回值。

5.socketaddr 数据结构

(1) 历史
很多网络编程 API 诞生早于 IPv4 协议,那时候都使用的是 sockaddr 结构体,为了向前兼容,现在 sockaddr 退化成了 (void *) 的作用,传递一个地址给函数,至于这个函数是 sockaddr_in 还是其他的,由地址族确定,然后函数内部再强制转化为所需的地址类型。
(2) sockaddr 结构体

1
2
3
4
struct sockaddr {
unsigned short sa_family; //address family, AF_xxx
char sa_data [14]; //14 bytes of protocol address
};
此数据结构用做 bind、connect、recvfrom、sendto 等函数的参数,指明地址信息。
但一般编程中并不直接针对此数据结构操作,而是使用另一个与 sockaddr 等价的数据结构 sockaddr_in(在 netinet/in.h 中定义):
1
2
3
4
5
6
struct sockaddr_in {
short int sin_family; //Address family
unsigned short int sin_port; //Port number
struct in_addr sin_addr; //Internet address
unsigned char sin_zero [8]; //Same size as struct sockaddr
};
在编程中大多数是使用 sockaddr_in 这个结构来设置 / 获取地址信息。
sin_family 指代协议族,在 socket 编程中只能是 AF_INET
sin_port 存储端口号(使用网络字节顺序)
sin_addr 存储 IP 地址,使用 in_addr 这个数据结构
1
2
3
struct in_addr {
unsigned long s_addr;
};
这个数据结构是由于历史原因保留下来的,主要用作与以前的格式兼容。
s_addr 按照网络字节顺序存储 IP 地址
sin_zero 是为了让 sockaddr 与 sockaddr_in 两个数据结构保持大小相同而保留的空字节。
设置地址信息的示例:
1
2
3
4
5
struct sockaddr_in sa;
sa.sin_family = AF_INET;
sa.sin_port = htons (3490); //short, NBO
sa.sin_addr.s_addr = inet_addr ("132.241.5.10");
bzero (&(sa.sin_zero), 8);
注意:如果 sa.sin_addr.s_addr = INADDR_ANY,则不指定 IP 地址(用于 Server 程序)
地址转换函数 inet_addr (), inet_aton (), inet_ntoa () 和 inet_ntop (), inet_pton ()

C++ advance 文章总览

C++ advance(四)Linux 命令基础 —1.Linux 常用命令
C++ advance(四)Linux 命令基础 —2.vim 和 gcc 和 library
C++ advance(四)Linux 命令基础 —3.makefile 和 gdb 和 IO
C++ advance(四)Linux 命令基础 —4.stat 和 readdir 和 dup2
C++ advance(五)Linux 进程和线程 —1. 进程控制
C++ advance(五)Linux 进程和线程 —2. 进程间通信
C++ advance(五)Linux 进程和线程 —3. 信号
C++ advance(五)Linux 进程和线程 —4. 进程和线程
C++ advance(五)Linux 进程和线程 —5. 线程同步
C++ advance(六)Linux 高并发网络编程开发 —1. 网络编程基础 socket
C++ advance(六)Linux 高并发网络编程开发 —2.tcp 三次握手 - 并发
C++ advance(六)Linux 高并发网络编程开发 —3.tcp 状态转换 - selcet poll
C++ advance(六)Linux 高并发网络编程开发 —4.epoll udp
C++ advance(六)Linux 高并发网络编程开发 —5. 广播 - 组播 - 本地套接字
C++ advance(六)Linux 高并发网络编程开发 —6.libevent
C++ advance(六)Linux 高并发网络编程开发 —7.xml json
C++ advance(七)Linux 高并发 web 服务器开发 —1.
C++ advance(七)Linux 高并发 web 服务器开发 —2.
C++ advance(七)Linux 高并发 web 服务器开发 —3.