Bootstrap

网络编程 Linux环境 C语言实现

进程间通信的延续

跨电脑进程间通信


一、远程通信方式

  1. 电路交换------老式有线电话通信

  2. 报文交换

  3. 分组交换

    支持分时机制的(分片机制)报文交换

    ​现行网络大部分都是采用分组交换形式


二、网络&互联网&因特网

网络Network:多台计算机通过某种传输介质连接在一起形成的整体 计算机与计算机相连形成的整体 局域网

互联网internet:网络与网络相连形成的整体

全球拥有很多的互联网,其中一个用户最多、使用范围最广的民用互联网叫因特网(Internet)。

我们主要学习基于因特网这个特殊互联网的通信编程


三、因特网架构

因特网总体有两大部分组成:

  1. 核心网络

    很多个局域网(也被称为子网)通过路由器(分组交换机)交叉相连构成

  2. 边缘网络

    一个边缘网络也是由多个子网通过路由器相连构成,但只有一个路由器与核心网络中的某个路由器相连

核心网上两台主机(计算机)间的传输过程大体如下:

  1. 发送主机的网口 到达 当前子网路由器的某个网口
  2. 源路由器 经过 一系列中间路由器的转发到达 目标子网的路由器
  3. 目标子网路由器的某个网口 到达 目标主机

四、IP地址 & 端口号 & MAC地址

为了完成主机到主机传输,因特网需要给核心网络上的每台主机和路由器指定一个唯一的身份标识,这个身份标识被称为IP地址

为了完成局域网内网口到网口的传输,需要给每张网卡指定一个唯一的身份标识,这个身份标识被称为MAC地址 网卡地址 物理地址

为了完成进程到进程的传输,需要给每台主机上的不同进程一个唯一的身份标识:

  1. 进程身份标识只需确保同一台主机上是不同的即可
  2. 网络中不同主机上运行的操作系统可能相同也可能不同,而不同的操作系统自身对进程进行身份标识的手段是不同的,因此不能使用任何一个操作系统设计的身份标识手段,为了统一起见,因特网使用一套新的标识手段来对进程进行标识,这个标识被称为端口号

MAC地址作为一个常识了解即可,与后续学习密切相关的是IP地址和端口号

因特网中:一个IP地址+一个端口号 = 一个socket地址,用来标识哪台主机上的哪个进程


4.1 IP地址的表示方法

支持三种方式:

  1. 4字节整型 ------ 实际传输过程中使用

  2. 点分十进制字符串形式 例如:"192.168.6.56"

    点分隔的每个数字的取值范围:[0,255]

  3. 域名 例如:"www.baidu.com"

显然域名比点分十进制字符串形式更容易记忆,点分十进制字符串形式比4字节整型更容易记忆

Linux操作系统提供了如下两个函数完成4字节整型 与 点分十进制字符串形式IP地址的相互转换:

域名字符串由三个或四个部分组成,依次为:

  1. 服务名:www----网页服务 ftp----文件传输服务 mailto-----电子邮件服务等等
  2. 组织名:提供服务的公司 或 组织机构名
  3. 组织性质:com-----商业 edu----教育 org-----非盈利性国际组织
  4. 地区名:可选 cn----中国大陆 tw----中国台湾 hk----中国香港

因特网给核心网上的每台主机指定一个或多个域名来代表其对应的IP地址

因特网上有些主机专门提供了域名服务,这些主机管理着一个数据库,数据库中记录了全球所有域名及其对应的ip地址

任何想要根据域名获得IP地址程序,都可以向提供域名服务的主机发送域名解析请求,这些主机会查表向这些程序回馈查询结果

​Linux系统将发送域名解析请求和接收域名解析请求的回应封装在一个函数里:gethostbyname

显然,gethostbyname能够成功完成一次调用的前提:运行该程序的主机必须是处于联网状态

​实际项目中一般只需要使用多个IP地址中的第一个,则对gethostbyname的返回值做如下访问即可:

struct hostent *pret = gethostbyname(.....);

第一个4字节整型IP地址所占空间的名字:**(struct in_addr **)pret->h_addr_list

4.2 端口号

2字节的无符号整型

小于1024的:知名端口号,已与著名的网络服务进程相对应,例如:网页服务进程的默认端口号为80,ftp服务进程的默认端口号为23

大于等于1024的:动态端口号,作为客户端进程、或非著名网络服务进程的端口号

4.3 socket地址

ip地址、端口号是因特网这种互联网对主机和进程的标识手段,其它互联网有着不同的标识手段

因此IP地址、端口号两个概念对其它互联网无效

Linux系统设计的一套用于网络通讯接口,期望能够适用于更多的互联网场合,因此在这些接口中涉及到socket地址类型的参数时设计一个通用的socket地址结构体:struct sockaddr

这些接口的实现代码中会根据第一个成员值而使用与当前互联网对应的专用socket地址结构体,应用程序自身代码中也使用与当前互联网对应的专用socket地址结构体,例如:因特网用 struct sockaddr_in​


五、字节序

所谓字节序endian就是整数在内存中的存放次序,有两种字节序:

  1. 小端字节序little-endian:或 低端字节序 整数的低位从内存的低地址开始存放
  2. 大端字节序big-endian:或 高端字节序 整数的高位从内存的低地址开始存放

一台计算机采用哪种字节序,由组成这台计算机的CPU设计商决定:

  1. Intel X86系列的CPU 采用小端字节序
  2. IBM PowerPC系列的CPU 采用大端字节序
  3. ARM系列处理器默认采用小端,可以通过特殊指令配置成大端

练习:编写一个函数,通过返回值判断当前计算机是小端还是大端

代码:

#include <stdio.h>

int is_little_endian_v1(){
	unsigned int x = 0x12345678;
	unsigned char *puc = (unsigned char *)&x;

	return *puc == 0x78 ? 1 : 0;
}

int is_little_endian_v2(){
	union U{
		unsigned int v1;
		unsigned char v2;
	};
	
	union U u = {0x12345678};
	return u.v2 == 0x78 ? 1 : 0;
}

int main(int argc,char *argv[]){
	if(is_little_endian_v1())    printf("This computer is little endian!!!\n");
	else    printf("This computer is big endian!!!\n");

	return 0;
}

输出:



inet_aton ----- 得到4字节整型IP地址已是网络字节序

测试代码:

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include <stdio.h>
int main(int argc,char *argv[]){
	struct in_addr addr;
	int ret = 1;
	char *pip = NULL;

	if(argc < 2){
		printf("The argument is too few\n");
		return 1;
	}

	ret = inet_aton(argv[1],&addr);
	if(!ret){
		printf("The IP:%s is invalid\n",argv[1]);
		return 2;
	}

	pip = inet_ntoa(addr);
	printf("The IP is %s\n",pip);

	return 0;
}

输出:


gethostbyname ---- 得到4字节整型IP地址是调用该函数的主机字节序

示例代码:

#include <netdb.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>

int main(int argc,char *argv[]){
	struct hostent *pret = NULL;
	if(argc < 2){
		printf("The argument is too few\n");
		return 1;
	}
	pret = gethostbyname(argv[1]);
	if(NULL == pret){
		printf("get host-%s info failed\n",argv[1]);
		return 2;
	}

	printf("The office-name is %s\n",pret->h_name);

	{
		char **ppalias = pret->h_aliases;

		while(*ppalias != NULL){
			printf("alias-name:%s\n",*ppalias);
			ppalias++;
		}

	}

	{
		struct in_addr **ppaddr = (struct in_addr **)pret->h_addr_list;

		while(*ppaddr != NULL){	
			printf("IP Address:%s\n",inet_ntoa(**ppaddr));
			ppaddr++;
		}
	}
	
	/*如果只想得到第一个4字节整型的IP地址,可以采用如下表达式:**(struct in_addr **)pret->h_addr_list
	 * */
	return 0;
}

输出:


Linux系统为发送方提供如下两个函数完成需要的字节序转换(发送方主机字节序 转换成 因特网网络字节序):

htonl: uint32_t htonl(uint32_t v)

htons: uint16_t htons(uint16_t v)

Linux系统为接收方提供如下两个函数完成需要的字节序转换(因特网网络字节序 转换成 接收方主机字节序):

ntohl: uint32_t ntohl(uint32_t v)

ntohs:uint16_t ntohs(uint16_t v)

四个函数的实现过程:
1. 判断当前主机(调用函数的程序运行用主机)的字节序是大端还是小端
2. 如果是大端,直接返回参数值
3. 如果是小端,地址值最低的字节单元 与 地址值最高的字节单元 进行交换   
             地址值次低的字节单元 与 地址值次高的字节单元 进行交换
   返回交换后组成的值  

六、因特网协议栈

一种互联网能够让不同主机上的进程进行通信,必须制定一套通信协议

网络上传输一种业务数据(与程序网络功能相关的数据,也称为应用层协议数据),不能仅传输业务数据。还需要一些其它辅助型控制数据的配合,例如:源IP地址、目的IP地址、源端口号、目的端口号等等

因特网这种互联网制定的一套协议叫TCP/IP协议栈​,采用对辅助控制数据进行分类分层的思想设计而成,分为五层:

  1. 应用层:这部分的协议内容负责业务数据的二进制位组织

    严格意义上说,这层协议内容不属于TCPIP协议栈本身,有很多种应用层协议,每一种对应一种形式网络服务

    例如常用的应用层协议:http ftp pop3 smtp 。。。。

  2. 传输层:这部分的协议内容负责进程间通信涉及的辅助控制数据的二进制位组织

    进程间通信涉及的辅助控制数据的二进制位组织方案也可以有多种,每一种称为传输层的某个协议

    常用的是以下两种:

    1> TCP ---- Transfer Control Protocol传输控制协议 ------- 使用最为频繁

    2> UDP ----- User DataGram Protocol用户数据报协议

    传输层协议还会决定应用层数据的传输方式,这也是本层名称的由来

  3. 网络层:这部分的协议内容负责主机(包括路由器)间通信涉及的辅助控制数据的二进制位组织

    主机间通信涉及的辅助控制数据的二进制位组织方案也可以有多种,每一种称为网络层的某个协议

    常用的是以下几种:

    1> IP ----- Internet Protocol ------------------使用最为频繁,也是最重要的网络层

    2> ARP

    3> RARP

    .......

  4. 数据链路层:这部分的协议内容负责统一局域网网口间通信涉及的辅助控制数据的二进制位组织

    常用的是:PPP协议

  5. 物理层:负责采用哪种传输介质传输二进制位

    严格意义上讲也不属于TCPIP协议栈的内容​

​OSI七层协议模型:在TCP/IP协议栈模型的基础上将应用层分为三个子层:应用层 表示层 会话层 ------- 一种常识,了解即可

因此对于应用程序编程而言只需学会:1. 按应用层协议组织好应用层PDU 2.发送应用层PDU 3.接收应用层PDU 4.处理应用层PDU


七、传输层传输方式

传输层除了负责进程间通信用的辅助控制数据的组织外,还会决定如何传输(发送和接收操作)应用层PDU

TCP/IP协议栈的传输层提供三种传输协议:

  1. TCP:面向连接的 可靠的 有序的 流式传输 不支持组播和广播
  2. UDP:非面向连接的 不可靠的 无序的 报式传输 可以直接组播和广播
  3. SCTP ---- 了解

面向连接:通信双方在第一次传输应用层PDU之前,需要建立好一个逻辑上的连接

可靠传输:每次发送方发送出去数据,都可以知道对方有没有接收到 ------ TCP协议代码中基于接收对方收到数据的确认信息 + 重发机制来实现的

有序传输:多个应用层PDU的发送次序,与对方接收到的次序相同

无序传输:多个应用层PDU的发送次序,与对方接收到的次序可能相同,也可能不同

流式传输:已接收数据不能再次被接收,未接收完整的数据,下次还能继续接收

报式传输:已接收数据不能再次被接收,未接收完整的剩余数据会被丢弃

组播:可以同时向处于同一组的主机发送数据

广播:可以同时向一个子网中的所有主机发送数据

;