网络编程学习(二)
套接字(套接接口):对网络中不同主机上应用进程之间进行双向通信的端点的抽象,从效果上来说,一个套接口就是网络上进程通信的一端,是应用程序通过网络协议进行通信的接口,是应用程序与网络协议进行交互的接口。
通信域:套接字存在于通信域中,通信域是为了处理一般的进程通过套接字通信而引入的一种抽象概念,套接字通常只与同一域中的套接字交换数据。
套接字类型:
数据包套接字:提供无连接的、不保证可靠的、独立的数据包传输服务(可能有数据丢失,可能有数据顺序错乱)UDP
主要用于记录型数据通信,同时具有向多个目标地址发送广播数据的能力。
流式套接字:提供双向、有序的、无重复的、无记录边界的、可靠的数据流传输服务TCP
原始套接字:允许直接访问较低层的协议,用于检验新协议的实现
在网络的协议中,存放多字节值的顺序是不同的,在具体计算机中的多字节数据的存储顺序,称为本机字节顺序。
在网络的协议中,对多字节数据的存储,有自己的规定。多字节的数据在网络协议报头中的存储顺序,称为网络字节顺序。套接字中必须使用网络字节顺序。
Linux下网络IP地址的转换,主机字节顺序和网络字节顺序的转换。
网络IP地址本是用32位二进制来表示的,为了记忆的方便可以用点分十进制来表示IP地址,同时,网络IP地址在网络传输和计算机内部的存储方式也不同,需要用函数来进行转换。
1.将点分十进制字符串转换成十进制长整型数:in_addr_t inet_addr(const char *cp); in_addr_t 即long型,参数cp表示一个点分十进制字符串,返回值是十进制长整型数。
2.将长整型IP地址转换成点分十进制:char *inet_ntoa(struct in_addr in); 参数in是一个in_addr类型的结构体,这个结构体在man 7 ip中查得到:struct in_addr{uint32_t s_addr};ine_ntoa返回的是点分十进制的IP地址字符串。
3.主机字符顺序和网络字符顺序的转换:计算机中的字符和网络中的字符的存储顺序是不同的,计算机中的整型数和网络中的整型数进行交换时,需要相关的函数进行转换。如果将计算机中的长整型IP地址转换成网络字符顺序的整型IP地址,使用htonl函数。这些函数如下:
uint32_t htonl(uint32_t hostlong); 将计算机中的32位长整型数转换成网络字符顺序的32位长整型数。(用于IP的转换)
uint16_t htons(uint16_t hostshort); 将计算机中的16位整型数转换成网络字符顺序的16位整型数。(用于port的转换)
uint32_t ntohl(uint32_t netlong); 将网络中的32位常整型数转换成计算机中的32位长整型数。(用于IP的转换)
uint16_t ntons(uint16_t netshort); 将网络中的16位整型数转换成计算机中的16位整型数。(用于port的转换)
\--------------------------------------------------------------------------------------
inet_pton 和 inet_ntop
Linux下这两个IP地址转换函数,可以在将IP地址在“点分十进制”和“整数”之间转换。而且,inet_pton 和inet_ntop 这两个函数能够处理ipv4和ipv6,算是比较新的函数了。
inet_pton函数,“点分十进制” -> “整数”,原型如下:
int inet_pton(int af, const char *src, void *dst);
这个函数转换字符串到网络地址,第一个参数af是地址族,转换后存在dst中。
inet_pton 是 inet_addr 的扩展,支持的多地址族有下列:
af = AF_INET
src为指向字符型的地址,即ASCII的地址的首地址(ddd.ddd.ddd.ddd格式的),函数将该地址转换为in_addr的结构体,并复制在dst中。
af =AF_INET6
src为指向IPV6的地址,,函数将该地址转换为in6_addr的结构体,并复制在dst中。
如果函数出错将返回一个负值,并将errno设置为EAFNOSUPPORT,如果参数af指定的地址族和src格式不对,函数将返回0。
inet_ntop函数,“点分十进制” -> “整数”,原型如下:
const char *inet_ntop(int af, const void *src, char *dst, socklen_t cnt);
这个函数转换网络二进制结构到ASCII类型的地址,参数cnt是所指向缓存区dst的大小(以字节为单位),如果缓存区太小无法存储地址的值,则返回一个空指针,并将errno置为ENOSPC
下面是一个示例:
\#include <stdio.h>
\#include <arpa/inet.h>
int main (void)
{
char szip1[20]; //存放点分十进制IP地址
char szip2[20]; //存放点分十进制IP地址
struct in_addr s; // IPv4地址结构体
// 输入IP地址
printf("Please input IP address: ");
scanf("%s", &szip1);
// 转换
inet_pton(AF_INET, szip1, (void *)&s);
printf("inet_pton: 0x%x\n", s.s_addr); // 注意得到的字节序
// 反转换
inet_ntop(AF_INET, (void *)&s, szip2, sizeof(szip2));
printf("inet_ntop: %s\n", szip2);
return 0;
}
TCP(传输控制协议)
TCP建立在网络IP之上,为应用层进程提供 了一个面向连接的、端到端的、完全可靠的(无差错,无丢失,无重复或失序)全双工的流传输服务,允许网络中的两个应用程序建立一个虚拟连接并在任何一个方向上发送数据,把数据当作一个双向字节流进行交换。
服务器端
1、socket():服务器端首先创建一个流式套接字,相当于准备了一个插座
2、bind():将这个套接字与特定的网络地址联系在一起,这一步又被称为套接字的绑定,相当于安装操作。对于Internet,网络地址=IP地址+传输层端口号。由于这个套接字是专门用来监听来自客户机端的连接请求的,所以称它为监听套接字。监听套接字一般使用特定保留的端口号,必须经过绑定这一步。
3、listen():启动监听套接字做好准备,进入监听状态。规定监听套接字所能接受的最多的客户机端的连接请求数,一旦客户机端的连接请求到来,就将该请求先接纳到请求缓冲区队列中等待。如果一段时间内到达的连接请求大于缓冲区队列的长度,则拒绝后来的请求
4、accept():接受客户机端的连接请求,如果监听套接字的请求缓冲队列中已经有客户端机的连接请求在等待,就从中取出一个连接
请求,并接受。具体过程是:服务器端口立即建立一个新的套接字,称为响应套接字。系统赋给这个响应套接字一个服务器端的自由端口,并通过响应套接字向客户机端发送链接应答,客户机端收到这个应答,按照TCP连接规范,向服务器发送连接确认,并同时向服务器端发送来数据,这就完成了TCP的三次握手的连接过程
如果此时监听套接字的请求缓冲区队列中没有任何客户端的连接请求在等待,执行此命令就会使服务器端进程处于阻塞等待的状态,使她时刻准备接受来自客户机端的连接请求。
5、read()读取客户端发送来的请求/命令数据,并按照应用层协议做相应的处理
6、write()向客户机端发送响应数据
7、close()会话结束,关闭套接字
客户机端
1、socket()创建套接字,这时,客户机端的操作系统已经将计算机默认的IP地址和一个客户端的自由端口号付给了这个套接字,因此客户机端不需要进行绑定
2、connect()客户机端向服务器端发出连接请求,他的目的端口号是服务器端用作监听的套接字使用的保留端口号,执行此命令后,客户机端进入阻塞状态,等待服务器的连接应答,一旦收到来自服务器套接字的应答,客户机端就向服务器的响应套接字发送连接确认
3、write()客户端按照应用层协议向服务器端发送请求或者命令数据
4、read()客户机端接受来自服务器端响应套接字发送来的数据
5、close()会话结束关闭套接字