libev
这一类的。开始首先是万物根源的协议信息
TCP/IP
协议了
TCP/IP
协议指的并不是一个协议,往往在生活中听见的术语如:IP地址, TCP连接 等,总会被误导,以为就是一个东西TCP/IP
说的是一个 协议族 ,也就是说是一堆协议的统称OSI | TCP/IP |
---|---|
应用层 表示层 会话层 | 应用层 |
传输层 | 传输层 |
网络层 | 网络层 |
链路层 物理层 | 网络接口层 |
IP地址
就是由它进行封装并传往下一层*nix
: 在 Terminal 中输入 ifconfig -a
Windows
: 在 PowerShell 中输入 ipconfig
*nix
和 Windows
都可以通过 ping <domain name>
命令进行查询一个完整的应用程序传输数据时候 封装 的过程(从右二向左依次封装):
以太网首部 | IP | TCP/UDP | 真实数据 | 尾部 |
---|---|---|---|---|
MAC地址 | IP地址 | TCP或者UDP协议 | 应用程序数据 | 效验码 |
源和目的MAC地址以及 | 及前层协议类型 | 源和目的端口号及前层应用程序首部信息 | 应用软件信息和真正的数据 |
其中端口号实际上就是 应用程序的信息
接收数据时的 拆解 顺序与 封装 正好相反。
其中在传输过程中,作为接收方最开始使用的是 网络接口层/数据链路层 的驱动程序(即操作系统自带或另行安装,总之不用使用的程序员写就对了),来判断这个包是否属于我,判断的依据就是 MAC地址,如果是再判断什么协议
48bit
的, 前24bit
由 IEEE 分配, 后24bit
由厂商分配。原则上是唯一的。MAC地址 和 IP地址
实际上,在进行网络编程的时候,以上细节几乎都被隐藏起来,留给我们的只是可供使用的接口。
也许,许多大学计算机基础课程,会讲到 IP地址 有种类,分为 A,B,C...类,老师还介绍了各种类型的地址范围。
但是在现代,这种分类早已经失效,或者说正在逐渐消失,因为当下的 IP 地址的 子网掩码 可以是任意位,并以反斜杠跟在 IP地址后方。
比较现代的 IP地址 表示形式一般如此 1.185.223.1/24 代表着子网掩码是由 24个 从左至右连续的的二进制1 组合而成,其余位为0。称为CIDR分类
事实上有一些实用且挺炫酷的函数,可以先提一下
gethostbyname
用于域名查找 IP信息及各类信息
struct hostent * gethostbyname(const char * hostname)
struct hostent
是存储查找到的各类型信息,后方会有介绍hostname
即要查询的域名gethostbyaddr
用于IP地址查找 域名及各类信息
struct hostent * gethostbyaddr(const char * addr, socklen_t len, int family)
addr
是要查询的 IP地址,之所以是 const char *
是因为C语言历史遗留的原因,实际上其类型应为 struct in_addr *
(IPv4)len
地址的长度,即 IPv4 为4, IPv6 为16family
即协议的种类, IPv4 为 AF_INET
, IPv6 为 AF_INET6
struct hostent 的成员 | . | 类型 | . | 解释 |
---|---|---|---|---|
h_name | char * | 官方名称 | ||
h_aliases | char ** | 域名集合,以NULL结尾 | ||
h_addrtype | int | 地址族的类型 AF_INET 或 AF_INET6 | ||
h_length | int | 地址的长度 4 或 16 | ||
h_addr_list | char *IP的集合,以NULL结尾, 实际上每个元素的类型为 struct in_addr |
实际上,这并不是一个好的方法,在后方将记录 现代人的我们 该如何做到这些事情,以上只是以前的TCP/IP 编程
只适用于 IPv4
选择使用 C 语言进行编程
TCP
和 UDP
POSIX 标准->*nix平台标准
和 Windows 平台标准
对比两种不同连接方式的不同地位的创建,使用
TCP服务器 | TCP客户端 | UDP服务器 | UDP客户端 | 注释 |
---|---|---|---|---|
socket() | socket() | socket() | socket() | 创建套接字 |
bind() | bind() | bind() | 绑定所分配IP地址和端口号 | |
listen() | connect() | 客户端则绑定IP地址和端口号,并等待连接;服务器则是等待连接 | ||
accept() | 服务器接受连接 | |||
... | ... | sendto/recvfrom() | sendto/recvfrom() | 对于UDP即是连接也是操作 |
close() | close() | close() | close | 双向直接关闭连接 |
shutdown() | shutdown() | shutdown() | shutdown() | 可选择方向的关闭连接,即更加灵活 |
如此对比虽然有一些小瑕疵,但是能够大体上反映出真个网络编程上不同方式的区别
注1: 对于 sendto recvfrom 这两个接口函数,并不一定是只能用在 UDP类型的 套接字上,同样 TCP类型的 套接字也能使用,但是这么做并没有什么意义。
注2: 实际上 UDP 没有所谓的 服务器和和护短,因为本来就是单纯的互相发来发去。客户端端口 一般是随机的
以上是 *nix平台下的标准, Windows下的操作方式和 API有细微不同,但大部分是一致的。
Windows | *nix |
---|---|
socket() | socket() |
bind() | bind() |
connect() | connect() |
listen() | listen() |
accept() | accept() |
closesocket() | close() |
send() | send() |
read() | read() |
sendto() | sendto() |
recvfrom() | recvfrom() |
不仅仅是接口名字相同,参数个数以及功能也是一致,即使有一个例外,其参数以及使用方法也相同。
那岂不是可以直接移植了?
并不!
在 ** Windows 套接字编程时** , 由于 Windows
将其实现为动态库,所以在使用时需要将其加载进程序。
故而多加了加载操作。
int WSAStartup(
WORD wVersionRequested,
LPWSADATA lpWSAData /* 这是一个结构体, 传入类型为WSADATA* */
);
int WSACleanup(void);
每当在 Windows 上进行套接字编程时,总要指定某个版本的套接字库:
WSADATA wsaData;
int err_code;
/*
* MAKEWORD()的作用在于将版本号转为指定格式传入
* 当下(2015-10)套接字库的版本号最高是 2.2
*/
err_code = WSAStartup(MAKEWORD(2, 2), &wsaData);
/* TODO Something */
WSACleanup();
这是最基本的在 Windows 上使用 套接字 编程的流程,但是如果本平台的套接字库最高版本并不符合当前要求呢?
那么首先会将套接字版本库尽可能设置到平台的 最高版本 ,可以通过结构体 WSADATA
进行查询
if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2)
{
printf("Could not find a usable version of Winsock.dll\n");
WSACleanup();
return 1;
}
总体而言,
Windows平台
和*uix平台
的区别在于,前者使用时需要 加载和清除 套接字库 其余逻辑流程一致,毕竟只有统一才能越利于编程世界的发展。