Linux进程间通信——使用数据报套接字

简介: 前一篇文章,Linux进程间通信——使用流套接字介绍了一些有关socket(套接字)的一些基本内容,并讲解了流套接字的使用,这篇文章将会给大家讲讲,数据报套接字的使用。   一、简单回顾——什么是数据报套接字。
前一篇文章, Linux进程间通信——使用流套接字介绍了一些有关socket(套接字)的一些基本内容,并讲解了流套接字的使用,这篇文章将会给大家讲讲,数据报套接字的使用。
 
一、简单回顾——什么是数据报套接字。
 
socket,即套接字是一种通信机制,凭借这种机制,客户/服务器(即要进行通信的进程)系统的开发工作既可以在本地单机上进行,也可以跨网络进行。也就是说它可以让不在同一台计算机但通过网络连接计算机上的进程进行通信。也因为这样,套接字明确地将客户端和服务器区分开来。
 
相对于流套接字,数据报套接字的使用更为简单,它是由类型SOCK_DGRAM指定的,它不需要建立连接和维持一个连接,它们在AF_INET中通常是通过UDP/IP协议实现的。它对可以发送的数据的长度有限制,数据报作为一个单独的网络消息被传输,它可能会丢失、复制或错乱到达,UDP不是一个可靠的协议,但是它的速度比较高,因为它并一需要总是要建立和维持一个连接。
 
二、基于流套接字的客户/服务器的工作流程
使用数据报socket进行进程通信的进程采用的客户/服务器系统是如何工作的呢?
 
1、服务器端
 
与使用流套接字一样,首先服务器应用程序用系统调用socket来创建一个套接安,它是系统分配给该服务器进程的类似文件描述符的资源,它不能与其他的进程共享。
 
接下来,服务器进程会给套接字起个名字(监听),我们使用系统调用bind来给套接字命名。然后服务器进程就开始等待客户连接到这个套接字。
 
不同的是,然后系统调用recvfrom来接收来自客户程序发送过来的数据。服务器程序对数据进行相应的处理,再通过系统调用sendto把处理后的数据发送回客户程序。
 
与流套接字程序相比:
 
1、在流套接字中的程序中,接收数据是通过系统调用read,而发送数据是通过系统调用write来实现,而在数据报套接字程序中,这是通过recvfrom和sendto调用来实现的。
 
2、使用数据报套接字的服务器程序并不需要listen调用来创建一个队列来存储连接,也不需要accept调用来接收连接并创建一个新的socket描述符
 
2、客户端
 
基于数据报socket的客户端比服务器端简单,同样,客户应用程序首先调用socket来创建一个未命名的套接字,与服务器一样,客户也是通过sendto和recvfrom来向服务器发送数据和从服务器程序接收数据。
 
与流套接字程序相比:
 
使用数据报套接字的客户程序并不需要使用connect系统调用来连接服务器程序,它只要在需要时向服务器所监听的IP端口发送信息和接收从服务器发送回来的数据即可。
 
三、数据报socket的接口及作用
socket的接口函数声明在头文件sys/types.h和sys/socket.h中。
 
1、创建套接字——socket系统调用
 
该函数用来创建一个套接字,并返回一个描述符,该描述符可以用来访问该套接字,它的原型如下:
[cpp]  view plain copy print ?
 
  1. int socket(int domain, int type, int protocol);  
函数中的三个参数分别对应前面所说的三个套接字属性。protocol参数设置为0表示使用默认协议。
 
2、命名(绑定)套接字——bind系统调用
 
该函数把通过socket调用创建的套接字命名,从而让它可以被其他进程使用。对于AF_UNIX,调用该函数后套接字就会关联到一个文件系统路径名,对于AF_INET,则会关联到一个IP端口号。函数原型如下:
[cpp]  view plain copy print ?
 
  1. int bind( int socket, const struct sockaddr *address, size_t address_len);  
成功时返回0,失败时返回-1;
 
3、发送数据——sendto系统调用
 
该函数把缓冲区buffer中的信息给送给指定的IP端口的程序,原型如下:
[cpp]  view plain copy print ?
 
  1. int sendto(int sockfd, void *buffer, size_t len, int flags, struct sockaddr *to, socklen_t tolen);  
buffer中储存着将要发送的数据,len是buffer的长度,而flags在应用中通常被设置为0,to是要发送数据到的程序的IP端口,tolen是to参数的长度。
 
成功时返回发送的数据的字节数,失败时返回-1.
 
4、接收数据——recvfrom系统调用
 
该函数把发送给程序的信息储存在缓冲区buffer中,并记录数据来源的程序IP端口,原型如下:
[cpp]  view plain copy print ?
 
  1. int recvfrom(int sockfd, void *buffer, size_t len,int flags, struct sockaddr *src_from, socklen_t *src_len);  
buffer用于储存接收到的数据,len指定buffer的长度,而flags在应用中通常被设置0,src_from若不为空,则记录数据来源程序的IP端口,若src_len不为空,则其长度信息记录在src_len所指向的变量中。
 
注意:默认情况下,recvfrom是一个阻塞的调用,即直到它接收到数据才会返回。
 
5、关闭socket——close系统调用
 
该系统调用用来终止服务器和客户上的套接字连接,我们应该总是在连接的两端(服务器和客户)关闭套接字。
 
四、进程使用数据报socket进行通信
 
下面用多个客户程序实例和一个服务器程序来演示多个进程如何通过使用数据报socket来进行通信。
 
sockserver2.c是一个服务器程序,它接收客户程序发来的数据,并创建一个子进程来处理客户发送过来的数据,处理过程非常简单,就是把大写字母改为小写。然后把处理后的数据(大写字母对应的小写字母)发送回给客户端。
 
sockclient2.c是一个客户程序,它向服务器程序发送数据,并接收服务器发送过来的处理后的数据(即小写字母),然后把接收到的数据输出到屏幕上。在运行客户程序时,你可以为它提供一个字符作为参数,此时客户程序将把些字符作为要处理的数据发送给服务器,如果不提供一个参数,则默认发送字符A。
 
sockserver2.c的源代码如下:
[cpp]  view plain copy print ?
 
  1. #include <unistd.h>  
  2. #include <sys/types.h>  
  3. #include <sys/socket.h>  
  4. #include <netinet/in.h>  
  5. #include <stdio.h>  
  6. #include <stdlib.h>  
  7. #include <signal.h>  
  8.   
  9.   
  10. int main()  
  11. {  
  12.     int server_sockfd = -1;  
  13.     int server_len = 0;  
  14.     int client_len = 0;  
  15.     char buffer[512];  
  16.     int result = 0;  
  17.     struct sockaddr_in server_addr;  
  18.     struct sockaddr_in client_addr;  
  19.     //创建数据报套接字  
  20.     server_sockfd = socket(AF_INET, SOCK_DGRAM, 0);  
  21.     //设置监听IP端口  
  22.     server_addr.sin_family = AF_INET;  
  23.     server_addr.sin_addr.s_addr = htonl(INADDR_ANY);  
  24.     server_addr.sin_port = htons(9739);  
  25.     server_len = sizeof(server_addr);  
  26.     //绑定(命名)套接字  
  27.     bind(server_sockfd, (struct sockaddr*)&server_addr, server_len);  
  28.     //忽略子进程停止或退出信号  
  29.     signal(SIGCHLD, SIG_IGN);  
  30.   
  31.   
  32.     while(1)  
  33.     {     
  34.         //接收数据,用client_addr来储存数据来源程序的IP端口  
  35.         result = recvfrom(server_sockfd, buffer, sizeof(buffer), 0,   
  36.                 (struct sockaddr*)&client_addr, &client_len);  
  37.         if(fork() == 0)  
  38.         {  
  39.             //利用子进程来处理数据  
  40.             buffer[0] += 'a' - 'A';  
  41.             sleep(5);  
  42.             //发送处理后的数据  
  43.             sendto(server_sockfd, buffer, sizeof(buffer),0 ,   
  44.                 (struct sockaddr*)&client_addr, client_len);  
  45.             printf("%c\n", buffer[0]);  
  46.             //注意,一定要关闭子进程,否则程序运行会不正常   
  47.             exit(0);  
  48.         }  
  49.     }  
  50.     //关闭套接字  
  51.     close(server_sockfd);  
  52. }  
sockclient2.c的源代码如下:
 
[cpp]  view plain copy print ?
 
  1. #include <unistd.h>  
  2. #include <sys/types.h>  
  3. #include <sys/socket.h>  
  4. #include <netinet/in.h>  
  5. #include <stdlib.h>  
  6. #include <stdio.h>  
  7.   
  8. int main(int agrc, char *argv[])  
  9. {  
  10.     struct sockaddr_in server_addr;  
  11.     int server_len = 0;  
  12.     int sockfd = -1;  
  13.     int result = 0;  
  14.     char c = 'A';  
  15.     //取第一个参数的第一个字符  
  16.     if(agrc > 1)  
  17.         c = argv[1][0];  
  18.     //创建数据报套接字  
  19.     sockfd = socket(AF_INET, SOCK_DGRAM, 0);  
  20.     //设置服务器IP端口  
  21.     server_addr.sin_family = AF_INET;  
  22.     server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");  
  23.     server_addr.sin_port = htons(9739);  
  24.     server_len = sizeof(server_addr);  
  25.     //向服务器发送数据  
  26.     sendto(sockfd, &c, sizeof(char), 0,   
  27.         (struct sockaddr*)&server_addr, server_len);  
  28.     //接收服务器处理后发送过来的数据,由于不关心数据来源,所以把后两个参数设为0  
  29.     recvfrom(sockfd, &c, sizeof(char), 0, 0, 0);  
  30.     printf("char from server = %c\n", c);  
  31.     //关闭套接字  
  32.     close(sockfd);  
  33.     exit(0);   
  34. }  
运行结果如下:
先运行服务器程序,如下:
 
 
再运行三个客户端:如下:
 
 
在本例子中,我们启动了一个服务器程序和三个客户程序,从运行的结果来看,客户端发送给服务器程序的所有请求都得到了处理,即把大写字母变成了小写。recvfrom调用是阻塞的调用,即只有当接收到数据才会返回。
 
五、数据报套接字与流套接字的比较
 
1、从使用的便利和效率来讲
我们可以看到使用数据报套接字的确是比使用流套接字简单,而且快速。
 
因为使用流套接字的程序,客户程序需要调用connect来创建一个到服务器程序的连接,并需要维持这个连接,服务器程序也需要调用listen来创建一个队列来保存未处理的请求,当有数据到达时,服务器也不需要调用accept来接受连接并创建一个新socket描述符来处理请求。
 
再来看看使用数据报套接字的程序,服务器程序与客户程序所使用的系统调用大致相同,服务器程序只比客户程序多使用了一个bind调用。基于数据报套接字的程序,只需要使用sendto调用来向指定IP端口的程序发送信息,使用recvfrom调用从指向的IP端口接收信息即可。因为它并不需要建立一个连接,接受连接等,所以省去了很多的功夫。
 
2、从使用场合来讲
 
我们知道流套接字是基于TCP/IP协议的,它是一种安全的协议,提供的是一个有序、可靠、双向字节流的连接,发送的数据可以确保不会丢失、重复或乱序到达,而且它还有一定的出错后重新发送的机制。所以它比较适合用来发送信息量大的数据文件,或对数据完整性要求较高的文件,如压缩文件、视频文件等
 
而数据报套接字是基于UDP/IP协议实现的。它对可以发送的数据的长度有限制,数据报作为一个单独的网络消息被传输,它可能会丢失、复制或错乱到达,UDP不是一个可靠的协议,但是它的速度比较高。所以它比较适合发送一些对实时性要求较高,但是对安全性和完整性要求不太高的数据。如我们熟悉的聊天信息,即使有一点的丢失也不会造成理解上的大的问题。
目录
相关文章
|
9月前
|
并行计算 Linux
Linux内核中的线程和进程实现详解
了解进程和线程如何工作,可以帮助我们更好地编写程序,充分利用多核CPU,实现并行计算,提高系统的响应速度和计算效能。记住,适当平衡进程和线程的使用,既要拥有独立空间的'兄弟',也需要在'家庭'中分享和并行的成员。对于这个世界,现在,你应该有一个全新的认识。
314 67
|
8月前
|
Web App开发 Linux 程序员
获取和理解Linux进程以及其PID的基础知识。
总的来说,理解Linux进程及其PID需要我们明白,进程就如同汽车,负责执行任务,而PID则是独特的车牌号,为我们提供了管理的便利。知道这个,我们就可以更好地理解和操作Linux系统,甚至通过对进程的有效管理,让系统运行得更加顺畅。
234 16
|
8月前
|
Unix Linux
对于Linux的进程概念以及进程状态的理解和解析
现在,我们已经了解了Linux进程的基础知识和进程状态的理解了。这就像我们理解了城市中行人的行走和行为模式!希望这个形象的例子能帮助我们更好地理解这个重要的概念,并在实际应用中发挥作用。
153 20
|
9月前
|
关系型数据库 MySQL Linux
在Linux环境下备份Docker中的MySQL数据并传输到其他服务器以实现数据级别的容灾
以上就是在Linux环境下备份Docker中的MySQL数据并传输到其他服务器以实现数据级别的容灾的步骤。这个过程就像是一场接力赛,数据从MySQL数据库中接力棒一样传递到备份文件,再从备份文件传递到其他服务器,最后再传递回MySQL数据库。这样,即使在灾难发生时,我们也可以快速恢复数据,保证业务的正常运行。
413 28
|
7月前
|
监控 Shell Linux
Linux进程控制(详细讲解)
进程等待是系统通过调用特定的接口(如waitwaitpid)来实现的。来进行对子进程状态检测与回收的功能。
150 0
|
7月前
|
存储 负载均衡 算法
Linux2.6内核进程调度队列
本篇文章是Linux进程系列中的最后一篇文章,本来是想放在上一篇文章的结尾的,但是想了想还是单独写一篇文章吧,虽然说这部分内容是比较难的,所有一般来说是简单的提及带过的,但是为了让大家对进程有更深的理解与认识,还是看了一些别人的文章,然后学习了学习,然后对此做了总结,尽可能详细的介绍明白。最后推荐一篇文章Linux的进程优先级 NI 和 PR - 简书。
218 0
|
7月前
|
存储 Linux Shell
Linux进程概念-详细版(二)
在Linux进程概念-详细版(一)中我们解释了什么是进程,以及进程的各种状态,已经对进程有了一定的认识,那么这篇文章将会继续补全上篇文章剩余没有说到的,进程优先级,环境变量,程序地址空间,进程地址空间,以及调度队列。
140 0
|
7月前
|
Linux 调度 C语言
Linux进程概念-详细版(一)
子进程与父进程代码共享,其子进程直接用父进程的代码,其自己本身无代码,所以子进程无法改动代码,平时所说的修改是修改的数据。为什么要创建子进程:为了让其父子进程执行不同的代码块。子进程的数据相对于父进程是会进行写时拷贝(COW)。
194 0
|
10月前
|
存储 Linux 调度
【Linux】进程概念和进程状态
本文详细介绍了Linux系统中进程的核心概念与管理机制。从进程的定义出发,阐述了其作为操作系统资源管理的基本单位的重要性,并深入解析了task_struct结构体的内容及其在进程管理中的作用。同时,文章讲解了进程的基本操作(如获取PID、查看进程信息等)、父进程与子进程的关系(重点分析fork函数)、以及进程的三种主要状态(运行、阻塞、挂起)。此外,还探讨了Linux特有的进程状态表示和孤儿进程的处理方式。通过学习这些内容,读者可以更好地理解Linux进程的运行原理并优化系统性能。
378 4
|
10月前
|
Linux Shell
Linux 进程前台后台切换与作业控制
进程前台/后台切换及作业控制简介: 在 Shell 中,启动的程序默认为前台进程,会占用终端直到执行完毕。例如,执行 `./shella.sh` 时,终端会被占用。为避免不便,可将命令放到后台运行,如 `./shella.sh &`,此时终端命令行立即返回,可继续输入其他命令。 常用作业控制命令: - `fg %1`:将后台作业切换到前台。 - `Ctrl + Z`:暂停前台作业并放到后台。 - `bg %1`:让暂停的后台作业继续执行。 - `kill %1`:终止后台作业。 优先级调整:
795 5