linux进程间通信——使用数据报套接字(摘抄自ljianhui)

一、简单回顾——什么是数据报套接字。

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的接口以及作用

1、创建套接字——socket系统调用

该函数用来创建一个套接字,并返回一个描述符,该描述符可以用来访问该套接字,它的原型如下:

int socket(int domain,int type,int protocol);

函数中三个参数分别为三个套接字属性。protocol参数设置为0表示使用默认协议。

2、命名(绑定)套接字——bind系统调用

该函数把通过socket调用创建的套接字命名,从而让它可以被其他进程使用。对于AF_UNIX,调用该函数后套接字就会关联到一个文件系统路径名,对于AF_INET,则会关联到一个IP端口号。该函数原型如下:

int bind(int socket,const struct sockaddr* address,size_t address_len);

成功返回0,失败返回-1;

3、发送数据——sendto系统调用

该函数把缓冲区buffer中的信息发送给指定的IP端口程序,原型如下:

int sendto(int socket,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端口,原型如下:

int recvfrom(int socket,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来进行通信。

socketserver2.c是一个服务器程序,它接收客户程序发来的数据,并创建一个子进程来处理客户发送过来的数据,处理过程非常简单,就是把大写字母改为小写。然后把处理后的数据(大写字母对应的小写字母)发送回客户端。

#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>

int main()
{
    int server_sockfd = -1;
    int server_len = 0;
    int client_len = 0;
    char buffer[512];
    int result = 0;
    struct sockaddr_in server_addr;
    struct sockaddr_in client_addr;
    //创建数据报套接字
    server_sockfd = socket(AF_INET,SOCK_DGRAM,0);
    //设置监听IP端口
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    server_addr.sin_port = htons(9739);
    server_len = sizeof(server_addr);
    //绑定(命名)套接字
    bind(server_sockfd,(struct sockaddr*)&server_addr,server_len);
    //忽略子进程停止或退出信号
    signal(SIGCHLD,SIG_IGN);
    
    while(1)
    {
        //接收数据,用client_addr来储存数据来源程序的IP端口
        result = recvfrom(server_sockfd,buffer,sizeof(buffer),0,(struct sockaddr*)&client_addr,&client_len);
        if(result > 0)
        {
            printf("char from client = %c-ip:0x%x\n",buffer[0],client_addr.sin_addr.s_addr);//,inet_ntoa(client_addr.sin_addr));
        }
        else if(result < 0)
        {
            perror("recvfrom\n");
            exit(EXIT_FAILURE);
        }
        else
        {
            printf("recvfrom return 0\n");
        }
            
        if(fork() == 0)
        {
            //利用子进程来处理数据
            buffer[0] += 'a' - 'A';
            //sleep(5);
            sendto(server_sockfd,buffer,sizeof(buffer),0,(struct sockaddr*)&client_addr,client_len);
            //注意,一定要关闭子进程,否则程序运行会不正常
            exit(0);
        }
    }
    //关闭套接字
    close(server_sockfd);
}

socketclient2.c是一个客户程序,它向服务器程序发送数据,并接收服务器发送过来的处理后的数据(即小写字母),然后把接收到的数据输出到屏幕上。在运行客户程序时,你可以为它提供一个字符作为参数,此时客户程序将把些字符作为要处理的数据发送给服务器,如果不提供一个参数,则默认发送字符A。

#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <stdio.h>

int main(int argc,char* argv[])
{
    struct sockaddr_in server_addr;
    int server_len = 0;
    int sockfd = -1;
    int result = 0;
    char c = 'A';
    //取一个参数的第一个字符
    if(argc > 1)
    {
        c = argv[1][0];
    }
    //创建数据报套接字
    sockfd = socket(AF_INET,SOCK_DGRAM,0);
    //设置服务器IP端口
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    server_addr.sin_port = htons(9739);
    server_len = sizeof(server_addr);
    //向服务器发送数据
    sendto(sockfd,&c,sizeof(char),0,(struct sockaddr*)&server_addr,server_len);
    //接收服务器处理后发送过来的数据,由于不关心数据来源,所以把后面两个参数设置为0
    recvfrom(sockfd,&c,sizeof(char),0,0,0);
    printf("char from server = %c\n",c);
    //关闭套接字
    close(sockfd);
    exit(0);
}

五、数据报套接字与流套接字的比较

1、从使用的便利和效率来讲

我们可以看到使用数据报套接字比使用流套接字简单,而且快速。

因为使用流套接字的程序,客户程序需要调用connect来创建一个到服务器程序的连接,并需要维持这个连接,服务器程序也需要调用listen来创建一个队列来保存未处理的请求,当有数据到达时,服务器还需要调用accept来接受连接并创建一个新的socket描述符来处理请求。

再来看看使用数据报套接字的程序,服务器程序与客户程序使用的系统调用接口大致相同,服务器程序只比客户程序多使用了一个bind调用。基于数据报套接字的程序,只需要使用sendto调用来向指定IP端口程序发送信息,使用recvfrom调用从指向的IP端口接收信息即可。因为它并不需要建立一个连接,接受连接等,所以省去了很多功夫。

2、从使用场合来讲

我们知道流套接字是基于TCP/IP协议的,它是一种安全的协议,提供的是一个有序、可靠、双向字节流的连接,发送的数据可以确保不会丢失、重复或乱序到达,而且它还有一定的出错后重新发送的机制。所以它比较适合用来发送信息量大的数据文件,或对数据完整性要求较高的文件,如压缩文件、视频文件等

而数据报套接字是基于UDP/IP协议实现的。它对可以发送的数据的长度有限制,数据报作为一个单独的网络消息被传输,它可能会丢失、复制或错乱到达,UDP不是一个可靠的协议,但是它的速度比较快。所以它比较适合发送一些对实时性要求较高,但是对安全性和完整性要求不太高的数据。如我们熟悉的聊天信息,即使有一点的丢失也不会造成理解上打的问题。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值