②Linux简明系统编程(嵌入式公众号的课)---总课时12h

本文详细介绍了Linux系统编程中的进程间通信(IPC)的7种方式,包括管道、共享内存、消息队列、信号量、互斥锁、信号和套接字,并给出了实例。此外,还深入讲解了TCP和UDP的网络编程,包括创建socket、bind、listen、connect、accept、write/read等关键函数的使用,以及并发服务器模型。文章最后总结了进程间通信的重要性和常见方式,指出Socket通信可用于跨网络的进程间通信。
摘要由CSDN通过智能技术生成

0529

Linux简明系统编程

〇、一、二、三、四

点这里

进程间通信(Inter Process Communication) IPC的7种方式

☆☆☆①任务间的通信 之 管道pipe

☆☆☆②任务间的通信 之 共享内存 shared memory

☆☆☆③任务间的通信 之 消息队列message queue

☆☆☆④任务间的同步 之 信号量semaphore

☆☆☆⑤任务间的同步 之 互斥锁mutex

☆☆☆⑥内核和应用进程间/进程和进程间 传递的控制命令 之 信号signal

☆☆☆⑦socket套接字

这部分内容属于网络编程的范围,具体就是TCP、UDP的实现,见下面的五、Linux网络编程

五、Linux网络编程

要用到的头文件

#include<sys/types.h> //socket()函数、
#include<sys/socket.h>//socket()函数、bind()函数、listen()函数、accept()函数、inet_addr()函数、inet_ntoa()
#include<arpa/inet.h> //htons()函数、inet_addr()函数、inet_ntoa()函数
#include<netinet/in.h>//inet_addr()函数、inet_ntoa()
#include<iostream>
using namespace std;
int main(){
   
	...
	return 0;
}

补充:几个函数的解释 htons()函数、inet_addr()函数、inet_ntoa()函数

在这里插入图片描述
在这里插入图片描述

第21节课:Linux网络编程简介

TCP/IP协议相当于是交通规则,如果要开车上路就要学习交通规则的内容,但并不是学习学习了交通规则就会开车了,还要实际去操作,去练习,才能真正去上路。

开放系统互联参考模型(OSI ReferenecModel),即我们通常所说的网络互联的七层框架。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

TCP编程 — 第22、23节课:TCP服务端、客户端编程

在这里插入图片描述

socket系统调用

在这里插入图片描述

在这里插入图片描述

socket()函数 — 创建一个新socket

man socket

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
socket()函数的功能:创建一个新socket,并返回新创建的socket的文件描述符sockfd。

头文件:

#include<sys/types.h>
#include<sys/socket.h>

第一个参数domain指定了套接字socket的通信domain,常用的参数有AF_INETAFINET6,分别表示IPv4协议和IPv6协议;
在这里插入图片描述

第二个参数type表示套接字socket的类型,每个socket实现都至少提供了两种socket:流socket数据报socket。常用的有SOCK_STREAMSOCK_DGRAM,分别标识TCP协议和UDP协议,分别称作 TCP socketUDP socket
在这里插入图片描述

第三个参数protocol表示协议,一般就写个0。

返回值:如果函数调用成功,就返回新创建的套接字socket的文件描述符sockfd(int类型),否认就返回-1。

bind()函数 — 给socket绑定一个地址

man bind

在这里插入图片描述
函数的功能:bind函数用来将一个socket绑定到一个地址上。

头文件:

#include<sys/types.h>
#include<sys/socket.h>

第一个参数sockfd就是函数socket()的返回值,表示套接字的文件描述符;
第二个参数addr是一个地址,它指向地址结构体const struct sockaddr*,bind()函数就是要把前两个参数绑定到一起;
第三个参数addrlen表示地址结构体的大小。

通用socket地址结构体

struct sockaddr地址结构体的通用格式:

struct sockaddr{
   
	sa_family sa_family;//address family地址族
	
	char sa_data[14];//socket address套接字地址
}

sockaddr结构体的用途就是将各种domain特定的地址结构体转换成单个类型以供socket系统调用中的各个参数使用,相当于是所有domain特定的地址结构体模板,其中每个地址结构均以 (与sockaddr结构体sa_family字段对应的) family字段打头。

Internet domain socket地址有两种:IPv4和IPv6。
IPv4 socket地址存储在一个sockaddr_in结构中,

struct in_addr{
   //IPv4地址(4字节,32位)
	in_addr_t s_addr;//32位无符号整型
}
struct sockaddr_in{
   //IPv4 socket地址
	sa_family sin_family;//地址族(AF_INET),对应sockaddr结构体中sa_family字段
	
	in_port_t sin_port;//端口号(2字节,16位)
	struct in_addr sin_addr;//IPv4地址(4字节,32位)
	unsigned char _pad[X];//补全到sockaddr结构体中char sa_data[14]的大小,即14个字节
}

在这里插入图片描述

sin_portsin_addr字段分别表示端口号和IP地址,它们都是网络字节序network byte order,所以需要借助htons函数进程转换。

man htons

头文件:#include<arpa/inet.h>
在这里插入图片描述

网络字节序和主机序(大端和小端)

参考链接:网络字节序和主机序(大端和小端)

什么是大端和小端?
小端:低位数据放在低地址
大端:高位数据放在低地址

比如 0x12 34 56 78,其中78是低位,12是高位。该数的右侧是低地址,左侧是高地址。也就是说低位对应低地址(小端字节序)
如果0x12 34 56 78是小端字节序,那么0x78 56 34 12就是大端字节序,它把高位12放在最右侧的低地址(大端字节序)

一般,
主机字节序 是 小端模式
网络字节序 是 大端模式
而一个字节中的比特序,是低位放在低地址的。

为什么要有大端小端之分?
小端: 在内存中通常低位放在低地址。比如说0x12 34 56 78,从右往左进行程序处理速度更快。
大端:网络字节序,0x78 56 32 12将字节序反了一下。
但是一般人们阅读习惯就是从左往右。举个例子,对于网络字节序0x78 56 34 12,网络通常以字节流的方式传输,先接受到的是低地址的12,接着是34,…这样就能接收到的顺序就是12345678,也就是正常的顺序。如果直接按照0x12 34 56 78进行传输,会先接受到78然后是56…这个样子显然是不方便人为的阅读。

补充:socket开发中"INADDR_ANY"的含义是什么?

参考链接:点这里

listen()函数 — 监听socket连接

man listen

在这里插入图片描述
listen函数用来监听套接字的连接

第一个参数是套接字文件描述符sockfd,也就是socket函数的返回值;
第二个参数定义(用来存放处于挂起pending状态的套接字连接的)队列queue的最大长度maximum length,当队列为满full的状态时来了一个连接请求,就会出错。

返回值:函数调用成功就返回0,否则返回-1。

connect()函数 — 发起socket连接

man connect

在这里插入图片描述
第一个参数是sockfd,表示socket的文件描述符;
第二参数是sockaddr *类型,表示要连接的服务器的地址结构体,这里的IP地址就需要手动输入了;
第三个参数是地址结构体的大小,sizeof(struct sockaddr);
返回值:函数调用成功就返回0,否则返回-1。

注意:第二个参数直接输入一个IP地址不可以,需要用inet_addr()函数进行转换,把IPv4点分十进制格式的字符串转换为网络字节序二进制格式的地址
在这里插入图片描述
在这里插入图片描述
头文件:

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

accept()函数 — 接受socket连接

man accept

在这里插入图片描述
在这里插入图片描述

第一个参数依然是sockfd,表示socket的文件描述符;
第二参数是sockaddr *类型,表示想要连接服务器的客户机的地址结构体,这是个输出类型的参数,即accept调用完成后,系统会给这个参数赋上想要连接服务器的客户机的地址结构体的地址;
第三个参数是地址结构体的大小,sizeof(struct sockaddr);

返回值:函数调用成功就返回一个非负int值,该值是所接受套接字的文件描述符socketfd

如果要打印想要连接服务器的客户机的地址,需要用到一个函数inet_ntoa(),将Internet主机的二进制格式的地址(网络字节序)转换为IPv4点分十进制格式的字符串
头文件:

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

write() / send()函数 — 通过socket发送信息

man send

在这里插入图片描述
send()函数可以通过一个socket发送消息。
第一个参数是sockfd,表示通过哪一个套接字来发送消息;
第二个参数表示要发送的信息;
第三个参数表示要发送的信息的长度/大小;
第四个参数,一般写0。

read() / recv()函数 — 通过socket接收信息

man recv

在这里插入图片描述
第一个参数是sockfd,表示通过哪个socket来接收信息;
第二个参数是用来存放接收信息的缓冲区;
第三个参数是缓冲区的大小;
第四个参数一般写0。

返回值:如果函数调用成功就返回0,否则返回-1。

close()函数 — 断开连接

关闭一个文件描述符,这里是指断开服务器和客户机之间的连接
在这里插入图片描述

示例1:

服务器一直监听,只要有客户机发起连接,就发送一条测试信息给客户机;客户机收到测试信息后打印出来。

server.cpp

#include<iostream>
#include<unistd.h>
#include<sys/types.h>
#include<arpa/inet.h>
#include<netinet/in.h>
using namespace std;
#define SIZE 100

int main(){
   

	struct sockaddr_in server_addr, client_addr;//sockaddr_in结构体变量
	int sockfd;//socket文件描述符
	socklen_t addrLen = sizeof(struct sockaddr);//地址结构体的大小
	int client_sockfd;//socket文件描述符
	char buf1[SIZE] = "这是服务器发来的测试信息...";//要发送的内容
//1.socket()
	sockfd = socket(2, SOCK_STREAM, 0);//创建一个socket PF_INET	
//2.bind()
	server_addr.sin_family = 2;//AF_INET;
	server_addr.sin_port = htons(8800);//端口号
	server_addr.sin_addr.s_addr = INADDR_ANY;//((in_addr_t) 0x00000000)
	bind(sockfd, (struct sockaddr *)&server_addr, addrLen);//给新创建的socket绑定一个地址
//3.listen()
	listen(sockfd, 10);//监听队列大小设置为10
	//服务器一般不关机,一直处在监听状态:
	while(1){
   
		cout << "服务器一直在等待客户机连接..." << endl;
//4.accept()
		client_sockfd = accept(sockfd, (struct sockaddr*)&client_addr, &addrLen);
		cout << "请求连接的客户机IP地址:" << inet_ntoa(client_addr.sin_addr) << endl;
//5.send()
		send(client_sockfd, buf1, SIZE, 0);
//6.close()
		cout << "断开socket连接..." << endl;
		close(client_sockfd);//断开socket连接
	}
	close(sockfd);

	return 0;
}

client.cpp

#include<iostream>
#include<unistd.h>
#include<sys/types.h>
#include<arpa/inet.h>
#include<netinet/in.h>
using namespace std;
#define SIZE 100

int main(int argc, char *argv[]){
   //char

	if(argc < 2){
   //参数个数argument count 小于2就提醒用户应该按下面的格式输入
		cout << "请输入 ./client [服务器IP地址]" << endl;
		return 0;
	}
	struct sockaddr_in server_addr;//sockaddr_in结构体变量
	int sockfd;//socket文件描述符
	socklen_t addrLen = sizeof(struct sockaddr);//地址结构体的大小
	char buf1[SIZE];//用来接收的buffer
//1.socket()
	sockfd = socket(2, SOCK_STREAM, 0);//创建一个socket PF_INET	
//2.connect()
	server_addr.sin_family = 2;//AF_INET;
	server_addr.sin_port = htons(8800);//端口号
	server_addr.sin_addr.s_addr = inet_addr((const char *)argv[1]);
	connect(sockfd, (sockaddr *)&server_addr, addrLen);
//3.recv()
	recv(sockfd, buf1, SIZE, 0);
	cout << "从服务器发来的内容:" << buf1 << endl;

//6.close()
	//cout << "断开socket连接..." << endl;
	close(sockfd);//断开socket连接

	return 0;
}

编译运行:
打开两个终端,分别运行 ./server和 ./client.cpp
在这里插入图片描述

示例2:

服务器处于监听状态,有客户机发来连接请求就accept,然后接收客户机发来的消息.
参考链接:网络编程之TCP简单示例
server.cpp

#include<iostream>
#include<unistd.h>
#include<sys/types.h>
#include<arpa/inet.h>
#include<netinet/in.h>
using namespace std;
#define SIZE 100

int main(){
   

	int sockfd;//socket文件描述符
//1.socket()
	sockfd = socket(2, SOCK_STREAM, 0);//创建一个socket PF_INET	
	if(sockfd < 0) {
   perror("socket"); cout << "socket创建失败!" << endl;}
	else cout << "socket创建成功,sockfd = " << sockfd << endl;

//2.bind()
	struct sockaddr_in server_addr;//sockaddr_in结构体变量
	server_addr.sin_family = 2;//AF_INET;
	server_addr.sin_port = htons(8800);//端口号
	server_addr.sin_addr.s_addr = INADDR_ANY;//((in_addr_t) 0x00000000)
	socklen_t addrLen = sizeof(struct sockaddr);//地址结构体的大小	
	int res = bind(sockfd, (struct sockaddr *)&server_addr, addrLen);//给新创建的socket绑定一个地址
	if(res < 0) {
   perror("bind"); cout << "bind失败!" << endl; }
	else cout << "bind成功!" << endl;

//3.listen()
	res = listen(sockfd, 10);//监听队列大小设置为10
	if(res < 0) perror("listen");  
	else cout << "listening..." << endl;

	//服务器一般不关机,一直处在监听状态://cout << "服务器一直在等待客户机连接..." << endl;
	struct sockaddr_in client_addr;
	int client_sockfd;//socket文件描述符
//4.accept()
	client_sockfd = accept(sockfd, (struct sockaddr*)&client_addr, &addrLen);//NULL, NULL);//
	if(client_sockfd == -1) {
   perror("accept"); cout << "accept失败!" << endl;}
	else cout << "accept成功,client_sockfd = " << client_sockfd << endl;
	cout << "请求连接的客户机IP地址:" << inet_ntoa(client_addr.sin_addr) << endl;

	char buf1[SIZE] = "这是服务器发来的测试信息...";//要发送的内容
	char buf2[SIZE];//接收内容的buffer

	for(int i = 0; i < 8; ++i){
   
		sleep(1);//1秒刷新一次		
//5.send()
		//send(client_sockfd, buf1, SIZE, 0);
//5.recv()
		res = recv(client_sockfd, buf2, SIZE, 0); 
		if(res < 0) {
   perror("bind"); cout << "recv失败!" << endl
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值