1. 网络中进程之间如何通信
一起学习的可以后台私信“资料”送相关学习资料可以一起学习交流大家也可以关注一下,记得后台私信“资料”送学习视频需要C/C++ Linux服务器架构师学习资料后台私信“资料”(资料包括C/C++,Linux,golang技术,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK,ffmpeg等等。。。),免费分享
进 程通信的概念最初来源于单机系统。由于每个进程都在自己的地址范围内运行,为保证两个相互通信的进
程之间既互不干扰又协调一致工作,操作系统为进程通信提供了相应设施,如
UNIX BSD有:管道(pipe)、命名管道(named pipe)软中断信号(signal)
UNIX system V有:消息(message)、共享存储区(shared memory)和信号量(semaphore)等.
他们都仅限于用在本机进程之间通信。网间进程通信要解决的是不同主机进程间的相互通信问题(可把同机进程通信看作是其中的特例)。为此,首先要解决的是网间进程标识问题。同一主机上,不同进程可用进程号(process ID)唯一标识。但在网络环境下,各主机独立分配的进程号不能唯一标识该进程。例如,主机A赋于某进程号5,在B机中也可以存在5号进程,因此,“5号进程”这句话就没有意义了。 其次,操作系统支持的网络协议众多,不同协议的工作方式不同,地址格式也不同。因此,网间进程通信还要解决多重协议的识别问题。
其实TCP/IP协议族已经帮我们解决了这个问题, 网络层的“ ip地址 ” 可以唯一标识网络中的主机,而 传输层的“ 协议+端口 ” 可以唯一标识主机中的应用程序(进程)。这样利用三元组(ip地址,协议,端口)就可以标识网络的进程了,网络中的进程通信就可以利用这个标志与其它进程进行交互。
使用TCP/IP协议的应用程序通常采用应用编程接口:UNIX BSD的套接字(socket)和UNIX System V的TLI(已经被淘汰),来实现网络进程之间的通信。就目前而言,几乎所有的应用程序都是采用socket,而现在又是网络时代,网络中进程通信是无处不在,这就是我为什么说“一切皆socket”。
2. 什么是TCP/IP、UDP
TCP/IP(Transmission Control Protocol/Internet Protocol)即传输控制协议/网间协议,是一个工业标准的协议集,它是为广域网(WANs)设计的。
TCP/IP协议存在于OS中,网络服务通过OS提供,在OS中增加支持TCP/IP的系统调用——Berkeley套接字,如Socket,Connect,Send,Recv等
UDP(User Data Protocol,用户数据报协议)是与TCP相对应的协议。它是属于TCP/IP协议族中的一种。如图:
TCP/IP协议族包括运输层、网络层、链路层,而socket所在位置如图,Socket是应用层与TCP/IP协议族通信的中间软件抽象层。
3. Socket是什么
1、 socket套接字:
socket起源于Unix,而Unix/Linux基本哲学之一就是“一切皆文件”,都可以用“打开open –> 读写write/read –> 关闭close”模式来操作。Socket就是该模式的一个实现, socket即是一种特殊的文件,一些socket函数就是对其进行的操作(读/写IO、打开、关闭).
说白了Socket是应用层与TCP/IP协议族通信的中间软件抽象层, 它是一组接口 。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。
使用 socket 进行 TCP 通信时,经常使用的函数有:将从 二、创建套接字讲起
一、简述
流式Socket:SOCK_STREAM , 提供面向连接的Socket
数据报式Socket:SOCK_DGRAM , 提供面向无连接的Socket
2. 字节序
1)概念:是指多字节数据的存储顺序
2)分类:
小端格式:将低位字节数据存储在低地址
大端格式:将高位字节数据存储在低地址
3)特点: 网络协议指定了通讯字节序–大端
只有在多字节数据处理时才需要考虑字节序
运行在同一台计算机上的进程相互通信时,一般不用考虑字节序
异构计算机之间通讯,需要转换自己的字节序为网络字节序
4)确定主机字节序:
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
void main()
{
printf("程序开始\n");
union{
short data;
char c[sizeof(short)];
}un;
un.data = 0x0102;
if( un.c[0] == 1 && un.c[1] == 2){
printf("大端格式\n");
}else if( un.c[0] == 2 && un.c[1] == 1){
printf("小端格式\n");
}
printf("程序结束\n");
return;
}
5)字节序转换函数
1)主机字节序数据转换成网络字节序数据
int16_t htons(uint16_t host16bit) 把16位值从主机字节序转到网络字节序
uint32_t htonl(uint32_t host32bit) 把32位值从主机字节序转到网络字节序
功能:
将32或16位主机字节序数据转换成网络字节序数据
参数:
uint32_t: unsigned int
hostint32:待转换的32位主机字节序数据
返回值:
成功:返回网络字节序的值
2)网络字节序数据转换成主机字节序数据
uint16_t ntohs(uint16_t net16bit) 把16位值从网络字节序转到主机字节序
uint32_t ntohs(uint32_t net32bit) 把32位值从网络字节序转到主机字节序
功能:
将32或16位网络字节序数据转换成主机字节序数据
参数:
uint32_t: unsignedint
netint32: 待转换的32位网络字节序数据
返回值:
成功:返回主机字节序的值
3. 通用套接字地址结构sockaddr
套接字数据结构用于保存套接字信息,与使用该结构的网络协议有关,每一种网络协议都有其本身的网络地址数据结构,都是以sockaddr_开头的,不同的网络协议有不同的后缀,如IPv4对应的是skocaddr_in
1)地址标识了特定通信域中的套接字端点,
地址格式与特定通信域相关,
为使不同格式地址能被传入套接字函数,地址被强制转换成通用套接字地址结构
通用套接字地址结构如下:
struct sockaddr
{
sa_family_t sa_family; //2 字节,地址族 AF_xxx
char sa_data[14]; //14 字节的协议地 址,包含套接字IP和端口号
};
头文件:#include <netinet/in.h>
4. 套接字地址结构sockaddr_in
1)在IPv4因特网域(AF_INET)中,套接字地址结构用sockaddr_in命名
struct sockaddr_in
{
sa_family_t sin_family; //2字节,地址族
in_port_t sin_port; //2字节,端口号
struct in_addr sin_addr; //4字节,IP地址
unsigned char sin_zero[8]; //8字节,
};
struct in_addr
{
in_addr_t s_addr; //4字节
};
sin_zero说明:用来将sockaddr_in结构填充到与sockaddr同样长度,可用bzero()或memset()函数将其置为0
头文件:#include <netinet/in.h
5. 地址转换函数
1)int inet_pton(int family, const char* strptr,void *addrptr);
功能:
将点分十进制数串转换成32位无符号整数
参数:
family 协议族
strptr 点分十进制数串
addrptr 32位无符号整数的地址
返回值:
成功:1
失败:其它
头文件:#include <arpa/inet.h>
2)const char *inet_ntop(int family, const void* addrptr, char *strptr, size_t len);
功能:
将32位无符号整数转换成点分十进制数串
参数:
family 协议族
addrptr 32位无符号整数
strptr 点分十进制数串
len strptr缓存区长度
len的宏定义
#define INET_ADDRSTRLEN 16
#define INET6_ADDRSTRLEN 46 //for ipv6
返回值:
成功:则返回字符串的首地址
失败:返回NULL
头文件:#include <arpa/inet.h>
6.服务模型
TCP:
Udp:
二、创建套接字
创建套接字是进行任何网络通信时必须做的第一步
1.int socket(int family, int type,int protocol);
功能:
创建一个用于网络通信的I/O描述符(套接字)
参数:
family:协议族
AF_INET,AF_INET6,AF_LOCAL,AF_ROUTE,AF_KEY
常用值 AF_INET 互联网协议族
type:套接字类型
SOCK_STREAM(流式套接字)
SOCK_DGRAM(数据包套接字)
SOCK_RAW (原始套接字)
SOCK_SEQPACKET
protocol:协议类别
0,IPPROTO_TCP,IPPROTO_UDP,IPPROTO_SCTP
常用值 0
返回值:套接字
socket创建的套接字特点
使用socket创建套接字时,系统不会分配端口
使用socket创建的是主动套接字,但作为服务器,
需要被动等待别人的连接
头文件:#include<sys/socket.h>
Socket编程实例
服务器端: 一直监听本机的8000号端口,如果收到连接请求,将接收请求并接收客户端发来的消息,并向客户端返回消息。
/* File Name: server.c */#include<stdio.h>#include<stdlib.h>#include<string.h>#include<errno.h>#include<sys/types.h>#include<sys/socket.h>#include<netinet/in.h>#define DEFAULT_PORT 8000#define MAXLINE 4096int main(int argc, char** argv){ int socket_fd, connect_fd; struct sockaddr_in servaddr; char buff[4096]; int n; //初始化Socket if( (socket_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1 ){ printf("create socket error: %s(errno: %d)\n",strerror(errno),errno); exit(0); } //初始化 memset(&servaddr, 0, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY);//IP地址设置成INADDR_ANY,让系统自动获取本机的IP地址。 servaddr.sin_port = htons(DEFAULT_PORT);//设置的端口为DEFAULT_PORT //将本地地址绑定到所创建的套接字上 if( bind(socket_fd, (struct sockaddr*)&servaddr, sizeof(servaddr)) == -1){ printf("bind socket error: %s(errno: %d)\n",strerror(errno),errno); exit(0); } //开始监听是否有客户端连接 if( listen(socket_fd, 10) == -1){ printf("listen socket error: %s(errno: %d)\n",strerror(errno),errno); exit(0); } printf("======waiting for client's request======\n"); while(1){//阻塞直到有客户端连接,不然多浪费CPU资源。 if( (connect_fd = accept(socket_fd, (struct sockaddr*)NULL, NULL)) == -1){ printf("accept socket error: %s(errno: %d)",strerror(errno),errno); continue; }//接受客户端传过来的数据 n = recv(connect_fd, buff, MAXLINE, 0);//向客户端发送回应数据 if(!fork()){ /*紫禁城*/ if(send(connect_fd, "Hello,you are connected!\n", 26,0) == -1) perror("send error"); close(connect_fd); exit(0); } buff[n] = '\0'; printf("recv msg from client: %s\n", buff); close(connect_fd); } close(socket_fd);}
客户端:
/* File Name: client.c */ #include<stdio.h>#include<stdlib.h>#include<string.h>#include<errno.h>#include<sys/types.h>#include<sys/socket.h>#include<netinet/in.h> #define MAXLINE 4096 int main(int argc, char** argv){ int sockfd, n,rec_len; char recvline[4096], sendline[4096]; char buf[MAXLINE]; struct sockaddr_in servaddr; if( argc != 2){ printf("usage: ./client <ipaddress>\n"); exit(0); } if( (sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0){ printf("create socket error: %s(errno: %d)\n", strerror(errno),errno); exit(0); } memset(&servaddr, 0, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(8000); if( inet_pton(AF_INET, argv[1], &servaddr.sin_addr) <= 0){ printf("inet_pton error for %s\n",argv[1]); exit(0); } if( connect(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0){ printf("connect error: %s(errno: %d)\n",strerror(errno),errno); exit(0); } printf("send msg to server: \n"); fgets(sendline, 4096, stdin); if( send(sockfd, sendline, strlen(sendline), 0) < 0) { printf("send msg error: %s(errno: %d)\n", strerror(errno), errno); exit(0); } if((rec_len = recv(sockfd, buf, MAXLINE,0)) == -1) { perror("recv error"); exit(1); } buf[rec_len] = '\0'; printf("Received : %s ",buf); close(sockfd); exit(0);}
inet_pton 是Linux下IP地址转换函数,可以在将IP地址在“点分十进制”和“整数”之间转换 ,是inet_addr的扩展。
一起学习的可以后台私信“资料”送相关学习资料可以一起学习交流大家也可以关注一下,记得后台私信“资料”送学习视频 需要C/C++ Linux服务器架构师学习资料后台私信“资料”(资料包括C/C++,Linux,golang技术,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK,ffmpeg等等。。。),免费分享