进程间通讯 (IPC) 方法总结(三)

进程间通讯 (IPC) 方法总结(三)

信号量(SEMAPHORE)

信号量是一个计数器,用于多进程对共享数据的访问,信号量的意图在于进程间同步。
为了得到共享资源,进程须要执行下列操做:html

  1. 建立一个信号量:这要求调用者指定初始值,对于二值信号量来讲,它一般是1,也但是0。
  2. 等待一个信号量:该操做会测试这个信号量的值,若是小于0,就阻塞。也称为P操做。
  3. 挂出一个信号量:该操做将信号量的值加1,也称为V操做。

为了正确地实现信号量,信号量值的测试及减1操做应当是原子操做。为此,信号量一般是在内核中实现的。Linux环境中,有三种类型:Posix(可移植性操做系统接口)有名信号量(使用Posix IPC名字标识)、Posix基于内存的信号量(存放在共享内存区中)、System V信号量(在内核中维护)。这三种信号量均可用于进程间或线程间的同步。linux

Posix有名信号量服务器

Posix基于内存的信号量网络

System V信号量多线程

信号量与普通整型变量的区别

  1. 信号量是非负整型变量,除了初始化以外,它只能经过两个标准原子操做:wait(semap) , signal(semap) ; 来进行访问;
  2. 操做也被成为PV原语(P来源于荷兰语proberen"测试",V来源于荷兰语verhogen"增长",P表示经过的意思,V表示释放的意思),而普通整型变量则能够在任何语句块中被访问;socket

    信号量与互斥量之间的区别

  3. 互斥量用于线程的互斥,信号量用于线程的同步。这是互斥量和信号量的根本区别,也就是互斥和同步之间的区别。tcp

    互斥:是指某一资源同时只容许一个访问者对其进行访问,具备惟一性和排它性。但互斥没法限制访问者对资源的访问顺序,即访问是无序的。函数

    同步:是指在互斥的基础上(大多数状况),经过其它机制实现访问者对资源的有序访问。测试

    在大多数状况下,同步已经实现了互斥,特别是全部写入资源的状况一定是互斥的。少数状况是指能够容许多个访问者同时访问资源ui

  4. 互斥量值只能为0/1,信号量值能够为非负整数。

    也就是说,一个互斥量只能用于一个资源的互斥访问,它不能实现多个资源的多线程互斥问题。信号量能够实现多个同类资源的多线程互斥和同步。当信号量为单值信号量是,也能够完成一个资源的互斥访问。

  5. 互斥量的加锁和解锁必须由同一线程分别对应使用,信号量能够由一个线程释放,另外一个线程获得。

套接字(SOCKET)

套接字是一种通讯机制,凭借这种机制,客户/服务器(即要进行通讯的进程)系统的开发工做既能够在本地单机上进行,也能够跨网络进行。也就是说它可让不在同一台计算机但经过网络链接计算机上的进程进行通讯。
套接字示意图

套接字是支持TCP/IP的网络通讯的基本操做单元,能够看作是不一样主机之间的进程进行双向通讯的端点,简单的说就是通讯的两方的一种约定,用套接字中的相关函数来完成通讯过程。

套接字特性

套接字的特性由3个属性肯定,它们分别是:域、端口号、协议类型。

套接字的域

它指定套接字通讯中使用的网络介质,最多见的套接字域有两种:

  1. AF_INET,它指的是Internet网络。当客户使用套接字进行跨网络的链接时,它就须要用到服务器计算机的IP地址和端口来指定一台联网机器上的某个特定服务,因此在使用socket做为通讯的终点,服务器应用程序必须在开始通讯以前绑定一个端口,服务器在指定的端口等待客户的链接。
  2. AF_UNIX,表示UNIX文件系统,它就是文件输入/输出,而它的地址就是文件名。

    套接字的端口号

    每个基于TCP/IP网络通信的程序(进程)都被赋予了惟一的端口和端口号,端口是一个信息缓冲区,用于保留Socket中的输入/输出信息,端口号是一个16位无符号整数,范围是0-65535,以区别主机上的每个程序(端口号就像房屋中的房间号),低于256的端口号保留给标准应用程序,好比pop3的端口号就是110,每个套接字都组合进了IP地址、端口,这样造成的总体就能够区别每个套接字。

    套接字协议类型
  3. 流套接字
    流套接字在域中经过TCP/IP链接实现,同时也是AF_UNIX中经常使用的套接字类型。流套接字提供的是一个有序、可靠、双向字节流的链接,所以发送的数据能够确保不会丢失、重复或乱序到达,并且它还有必定的出错后从新发送的机制。
  4. 数据报套接字
    它不须要创建链接和维持一个链接,它们在域中一般是经过UDP/IP协议实现的。它对能够发送的数据的长度有限制,数据报做为一个单独的网络消息被传输,它可能会丢失、复制或错乱到达,UDP不是一个可靠的协议,可是它的速度比较高,由于它并一须要老是要创建和维持一个链接。
  5. 原始套接字
    原始套接字容许对较低层次的协议直接访问,好比IP、 ICMP协议,它经常使用于检验新的协议实现,或者访问现有服务中配置的新设备,由于RAW SOCKET能够自如地控制Windows下的多种协议,可以对网络底层的传输机制进行控制,因此能够应用原始套接字来操纵网络层和传输层应用。好比,咱们能够经过RAW SOCKET来接收发向本机的ICMP、IGMP协议包,或者接收TCP/IP栈不可以处理的IP包,也能够用来发送一些自定包头或自定协议的IP包。网络监听技术很大程度上依赖于SOCKET_RAW。

原始套接字与标准套接字的区别

原始套接字能够读写内核没有处理的IP数据包,而流套接字只能读取TCP协议的数据,数据报套接字只能读取UDP协议的数据。所以,若是要访问其余协议发送数据必须使用原始套接字。

套接字通讯的创建

套接字通讯的创建

  • 服务端
    1. 首先服务器应用程序用系统调用socket来建立一个套接字,它是系统分配给该服务器进程的相似文件描述符的资源,它不能与其余的进程共享。(socket)
    2. 服务器进程会给套接字起个名字,咱们使用系统调用bind来给套接字命名。而后服务器进程就开始等待客户链接到这个套接字。(bind)
    3. 系统调用listen来建立一个队列并将其用于存放来自客户的进入链接。(listen)
    4. 服务器经过系统调用accept来接受客户的链接。它会建立一个与原有的命名套接不一样的新套接字,这个套接字只用于与这个特定客户端进行通讯,而命名套接字(即原先的套接字)则被保留下来继续处理来自其余客户的链接(创建客户端和服务端的用于通讯的流,进行通讯)。(accept--read/write)
  • 客户端
    1. 客户应用程序首先调用socket来建立一个未命名的套接字。(socket)
    2. 将服务器的命名套接字做为一个地址来调用connect与服务器创建链接。(connect)
    3. 一旦链接创建,咱们就能够像使用底层的文件描述符那样用套接字来实现双向数据的通讯(经过流进行数据传输)(read/write)

eg.

服务端代码

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h> //socket listen bind
#include <sys/socket.h>//socket listen bind 
#include <unistd.h>//unlink
#include <sys/un.h>//struct sockaddr_un
  
int main()  
{  
  /* delete the socket file */  
  unlink("server_socket");  
    
  /* create a socket */  
  int server_sockfd = socket(AF_UNIX, SOCK_STREAM, 0);  
    
  struct sockaddr_un server_addr;  
  server_addr.sun_family = AF_UNIX;  
  strcpy(server_addr.sun_path, "server_socket");  
    
  /* bind with the local file */  
  bind(server_sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr));  
    
  /* listen */  
  listen(server_sockfd, 5);  
    
  char ch;  
  int client_sockfd;  
  struct sockaddr_un client_addr;  
  socklen_t len = sizeof(client_addr);  
  while(1)  
  {  
    printf("server waiting:\n");  
      
    /* accept a connection */  
    client_sockfd = accept(server_sockfd, (struct sockaddr *)&client_addr, &len);  
      
    /* exchange data */  
    read(client_sockfd, &ch, 1);  
    printf("get char from client: %c\n", ch);  
    ++ch;  
    write(client_sockfd, &ch, 1);  
      
    /* close the socket */  
    close(client_sockfd);  
  }  
    
  return 0;  
}

客户端代码

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h> //socket listen bind
#include <sys/socket.h>//socket listen bind 
#include <unistd.h>//unlink
#include <sys/un.h>//struct sockaddr_un  
  
int main()  
{  
  /* create a socket */  
  int sockfd = socket(AF_UNIX, SOCK_STREAM, 0);  
    
  struct sockaddr_un address;  
  address.sun_family = AF_UNIX;  
  strcpy(address.sun_path, "server_socket");  
    
  /* connect to the server */  
  int result = connect(sockfd, (struct sockaddr *)&address, sizeof(address));  
  if(result == -1)  
  {  
    perror("connect failed: ");  
    exit(1);  
  }  
    
  /* exchange data */  
  char ch = 'A';  
  write(sockfd, &ch, 1);  
  read(sockfd, &ch, 1);  
  printf("get char from server: %c\n", ch);  
    
  /* close the socket */  
  close(sockfd);  
    
  return 0;  
}

若是咱们首先运行tcp_client,会提示没有这个文件:

由于咱们是以AF_UNIX方式进行通讯的,这种方式是经过文件来将服务器和客户端链接起来的,所以咱们应该先运行tcp_server,建立这个文件,默认状况下,这个文件会建立在当前目录下,而且第一个s表示它是一个socket文件:

程序运行的结果以下图:


参考文章:

  1. 进程间通讯IPC (InterProcess Communication)

  2. 进程间通讯--管道

  3. UNIX/Linux进程间通讯IPC系列(四)消息队列

  4. Linux进程间通讯(四) - 共享内存

  5. 本地socket通信

相关文章
相关标签/搜索