服务器之家:专注于服务器技术及软件下载分享
分类导航

Linux|Centos|Ubuntu|系统进程|Fedora|注册表|Bios|Solaris|Windows7|Windows10|Windows11|windows server|

服务器之家 - 服务器系统 - Linux - Linux进程间通信方式之socket使用实例

Linux进程间通信方式之socket使用实例

2022-02-21 17:31shanzhizi Linux

这篇文章主要介绍了Linux进程间通信方式之socket使用实例,具有一定参考价值,需要的朋友可以了解下。

套接字是一种通信机制,凭借这种机制,客户/服务器系统的开发工作既可以在本地单机上进行,也可以跨网络进行。
套接字的特性有三个属性确定,它们是:域(domain),类型(type),和协议(protocol)。套接字还用地址作为它的名字。地址的格式随域(又被称为协议族,protocol family)的不同而不同。每个协议族又可以使用一个或多个地址族定义地址格式。

1.套接字的域

域指定套接字通信中使用的网络介质。最常见的套接字域是AF_INET,它是指Internet网络,许多Linux局域网使用的都是该网络,当然,因特网自身用的也是它。其底层的协议——网际协议(IP)只有一个地址族,它使用一种特定的方式来指定网络中的计算机,即IP地址。

在计算机系统内部,端口通过分配一个唯一的16位的整数来表示,在系统外部,则需要通过IP地址和端口号的组合来确定。

2.套接字类型

流套接字(在某些方面类似域标准的输入/输出流)提供的是一个有序,可靠,双向字节流的连接。

流套接字由类型SOCK_STREAM指定,它们是在AF_INET域中通过TCP/IP连接实现的。他们也是AF_UNIX域中常见的套接字类型。

数据包套接字

与流套接字相反,由类型SOCK_DGRAM指定的数据包套接字不建立和维持一个连接。它对可以发送的数据包的长度有限制。数据报作为一个单独的网络消息被传输,它可能会丢失,复制或乱序到达。

数据报套接字实在AF_INET域中通过UDP/IP连接实现,它提供的是一种无需的不可靠服务。

3.套接字协议

只要底层的传输机制允许不止一个协议来提供要求的套接字类型,我们就可以为套接字选择一个特定的协议。

先上一个代码

服务端:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
//s_unix.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h> 
#define UNIX_DOMAIN "/tmp/UNIX.domain"
int main(void)
{
  socklen_t clt_addr_len;
  int listen_fd;
  int com_fd;
  int ret;
  int i;
  static char recv_buf[1024]; 
  int len;
  struct sockaddr_un clt_addr;
  struct sockaddr_un srv_addr;
  listen_fd=socket(PF_UNIX,SOCK_STREAM,0);
  if(listen_fd<0)
  {
    perror("cannot create communication socket");
    return 1;
  
  //set server addr_param
  srv_addr.sun_family=AF_UNIX;
  strncpy(srv_addr.sun_path,UNIX_DOMAIN,sizeof(srv_addr.sun_path)-1);
  unlink(UNIX_DOMAIN);
  //bind sockfd & addr
  ret=bind(listen_fd,(struct sockaddr*)&srv_addr,sizeof(srv_addr));
  if(ret==-1)
  {
    perror("cannot bind server socket");
    close(listen_fd);
    unlink(UNIX_DOMAIN);
    return 1;
  }
  //listen sockfd 
  ret=listen(listen_fd,1);
  if(ret==-1)
  {
    perror("cannot listen the client connect request");
    close(listen_fd);
    unlink(UNIX_DOMAIN);
    return 1;
  }
  //have connect request use accept
  len=sizeof(clt_addr);
  com_fd=accept(listen_fd,(struct sockaddr*)&clt_addr,&len);
  if(com_fd<0)
  {
    perror("cannot accept client connect request");
    close(listen_fd);
    unlink(UNIX_DOMAIN);
    return 1;
  }
  //read and printf sent client info
  printf("/n=====info=====/n");
  for(i=0;i<4;i++)
  {
    memset(recv_buf,0,1024);
    int num=read(com_fd,recv_buf,sizeof(recv_buf));
    printf("Message from client (%d)) :%s/n",num,recv_buf); 
  }
  close(com_fd);
  close(listen_fd);
  unlink(UNIX_DOMAIN);
  return 0;
}

客户端:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
//c_unix.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#define UNIX_DOMAIN "/tmp/UNIX.domain"
int main(void)
{
  int connect_fd;
  int ret;
  char snd_buf[1024];
  int i;
  static struct sockaddr_un srv_addr;
//creat unix socket
  connect_fd=socket(PF_UNIX,SOCK_STREAM,0);
  if(connect_fd<0)
  {
    perror("cannot create communication socket");
    return 1;
  
  srv_addr.sun_family=AF_UNIX;
  strcpy(srv_addr.sun_path,UNIX_DOMAIN);
//connect server
  ret=connect(connect_fd,(struct sockaddr*)&srv_addr,sizeof(srv_addr));
  if(ret==-1)
  {
    perror("cannot connect to the server");
    close(connect_fd);
    return 1;
  }
  memset(snd_buf,0,1024);
  strcpy(snd_buf,"message from client");
//send info server
  for(i=0;i<4;i++)
    write(connect_fd,snd_buf,sizeof(snd_buf));
  close(connect_fd);
  return 0;
}

使用套接字除了可以实现网络间不同主机间的通信外,还可以实现同一主机的不同进程间的通信,且建立的通信是双向的通信。socket进程通信与网络通信使用的是统一套接口,只是地址结构与某些参数不同。

一、创建socket流程

(1)创建socket,类型为AF_LOCAL或AF_UNIX,表示用于进程通信:

创建套接字需要使用 socket 系统调用,其原型如下:

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

其中,domain 参数指定协议族,对于本地套接字来说,其值须被置为 AF_UNIX 枚举值;type 参数指定套接字类型,protocol 参数指定具体协议;type 参数可被设置为 SOCK_STREAM(流式套接字)或 SOCK_DGRAM(数据报式套接字),protocol 字段应被设置为 0;其返回值为生成的套接字描述符。

对于本地套接字来说,流式套接字(SOCK_STREAM)是一个有顺序的、可靠的双向字节流,相当于在本地进程之间建立起一条数据通道;数据报式套接字(SOCK_DGRAM)相当于单纯的发送消息,在进程通信过程中,理论上可能会有信息丢失、复制或者不按先后次序到达的情况,但由于其在本地通信,不通过外界网络,这些情况出现的概率很小。

二、命名socket。

SOCK_STREAM 式本地套接字的通信双方均需要具有本地地址,其中服务器端的本地地址需要明确指定,指定方法是使用 struct sockaddr_un 类型的变量。

?
1
2
3
4
struct sockaddr_un {
  sa_family_t   sun_family;   /* AF_UNIX */
  char  sun_path[UNIX_PATH_MAX];    /* 路径名 */
};

这里面有一个很关键的东西,socket进程通信命名方式有两种。一是普通的命名,socket会根据此命名创建一个同名的socket文件,客户端连接的时候通过读取该socket文件连接到socket服务端。这种方式的弊端是服务端必须对socket文件的路径具备写权限,客户端必须知道socket文件路径,且必须对该路径有读权限。

另外一种命名方式是抽象命名空间,这种方式不需要创建socket文件,只需要命名一个全局名字,即可让客户端根据此名字进行连接。后者的实现过程与前者的差别是,后者在对地址结构成员sun_path数组赋值的时候,必须把第一个字节置0,即sun_path[0] = 0,下面用代码说明:

第一种方式:

?
1
2
3
4
5
//name the server socket
    server_addr.sun_family = AF_UNIX;
    strcpy(server_addr.sun_path,"/tmp/UNIX.domain");
    server_len = sizeof(struct sockaddr_un);
    client_len = server_len;

第二种方式:

?
1
#define SERVER_NAME @socket_server
?
1
2
3
4
5
6
//name the socket
  server_addr.sun_family = AF_UNIX;
  strcpy(server_addr.sun_path, SERVER_NAME);
  server_addr.sun_path[0]=0;
  //server_len = sizeof(server_addr);
  server_len = strlen(SERVER_NAME) + offsetof(struct sockaddr_un, sun_path);

其中,offsetof函数在#include <stddef.h>头文件中定义。因第二种方式的首字节置0,我们可以在命名字符串SERVER_NAME前添加一个占位字符串,例如:

?
1
#define SERVER_NAME @socket_server

前面的@符号就表示占位符,不算为实际名称。

提示:客户端连接服务器的时候,必须与服务端的命名方式相同,即如果服务端是普通命名方式,客户端的地址也必须是普通命名方式;如果服务端是抽象命名方式,客户端的地址也必须是抽象命名方式。

三、绑定

SOCK_STREAM 式本地套接字的通信双方均需要具有本地地址,其中服务器端的本地地址需要明确指定,指定方法是使用 struct sockaddr_un 类型的变量,将相应字段赋值,再将其绑定在创建的服务器套接字上,绑定要使用 bind 系统调用,其原形如下:

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

其中 socket表示服务器端的套接字描述符,address 表示需要绑定的本地地址,是一个 struct sockaddr_un 类型的变量,address_len 表示该本地地址的字节长度。实现服务器端地址指定功能的代码如下(假设服务器端已经通过上文所述的 socket 系统调用创建了套接字,server_sockfd 为其套接字描述符):

?
1
2
3
4
struct sockaddr_un server_address;
server_address.sun_family = AF_UNIX;
strcpy(server_address.sun_path, "Server Socket");
bind(server_sockfd, (struct sockaddr*)&server_address, sizeof(server_address));

客户端的本地地址不用显式指定,只需能连接到服务器端即可,因此,客户端的 struct sockaddr_un 类型变量需要根据服务器的设置情况来设置,代码如下(假设客户端已经通过上文所述的 socket 系统调用创建了套接字,client_sockfd 为其套接字描述符):

?
1
2
3
struct sockaddr_un client_address;
client_address.sun_family = AF_UNIX;
strcpy(client_address.sun_path, "Server Socket");

四、监听

服务器端套接字创建完毕并赋予本地地址值(名称,本例中为Server Socket)后,需要进行监听,等待客户端连接并处理请求,监听使用 listen 系统调用,接受客户端连接使用accept系统调用,它们的原形如下:

?
1
2
int listen(int socket, int backlog);
int accept(int socket, struct sockaddr *address, size_t *address_len);

其中 socket 表示服务器端的套接字描述符;backlog 表示排队连接队列的长度(若有多个客户端同时连接,则需要进行排队);address 表示当前连接客户端的本地地址,该参数为输出参数,是客户端传递过来的关于自身的信息;address_len 表示当前连接客户端本地地址的字节长度,这个参数既是输入参数,又是输出参数。实现监听、接受和处理的代码如下:

?
1
2
3
4
5
6
7
8
9
10
11
#define MAX_CONNECTION_NUMBER 10
int server_client_length, server_client_sockfd;
struct sockaddr_un server_client_address;
listen(server_sockfd, MAX_CONNECTION_NUMBER);
while(1)
{
  // ...... (some process code)
  server_client_length = sizeof(server_client_address);
  server_client_sockfd = accept(server_sockfd, (struct sockaddr*)&server_client_address, &server_client_length);
  // ...... (some process code)
}

这里使用死循环的原因是服务器是一个不断提供服务的实体,它需要不间断的进行监听、接受并处理连接,本例中,每个连接只能进行串行处理,即一个连接处理完后,才能进行后续连接的处理。如果想要多个连接并发处理,则需要创建线程,将每个连接交给相应的线程并发处理。

客户端套接字创建完毕并赋予本地地址值后,需要连接到服务器端进行通信,让服务器端为其提供处理服务。对于 SOCK_STREAM 类型的流式套接字,需要客户端与服务器之间进行连接方可使用。连接要使用 connect 系统调用,其原形为

?
1
int connect(int socket, const struct sockaddr *address, size_t address_len);

其中socket为客户端的套接字描述符,address表示当前客户端的本地地址,是一个 struct sockaddr_un 类型的变量,address_len 表示本地地址的字节长度。实现连接的代码如下:

?
1
connect(client_sockfd, (struct sockaddr*)&client_address, sizeof(client_address));

无论客户端还是服务器,都要和对方进行数据上的交互,这种交互也正是我们进程通信的主题。一个进程扮演客户端的角色,另外一个进程扮演服务器的角色,两个进程之间相互发送接收数据,这就是基于本地套接字的进程通信。发送和接收数据要使用 write 和 read 系统调用,它们的原形为:

?
1
2
int read(int socket, char *buffer, size_t len);
int write(int socket, char *buffer, size_t len);

其中 socket 为套接字描述符;len 为需要发送或需要接收的数据长度;对于 read 系统调用,buffer 是用来存放接收数据的缓冲区,即接收来的数据存入其中,是一个输出参数;对于 write 系统调用,buffer 用来存放需要发送出去的数据,即 buffer 内的数据被发送出去,是一个输入参数;返回值为已经发送或接收的数据长度。例如客户端要发送一个 "Hello" 字符串给服务器,则代码如下:

?
1
2
char buffer[10] = "Hello";
write(client_sockfd, buffer, strlen(buffer));

交互完成后,需要将连接断开以节省资源,使用close系统调用,其原形为:

?
1
int close(int socket);

不多说了,直接使用,大家一定都会,呵呵!

上面所述的每个系统调用都有 -1 返回值,在调用不成功时,它们均会返回 -1,这个特性可以使得我们用 if - else 或异常处理语句来处理错误,为我们提供了很大的方便。

SOCK_DGRAM 数据报式本地套接字的应用场合很少,因为流式套接字在本地的连接时间可以忽略,所以效率并没有提高,而且发送接收都需要携带对方的本地地址,因此很少甚至几乎不使用。

与本地套接字相对应的是网络套接字,可以用于在网络上传送数据,换言之,可实现不同机器上的进程通信过程。在 TCP/IP 协议中,IP 地址的首字节为 127 即代表本地,因此本地套接字通信可以使用 IP 地址为 127.x.x.x 的网络套接字来实现。

总结

以上就是本文关于Linux进程间通信方式之socket使用实例的全部内容,希望对大家有所帮助。感谢朋友们对本站的支持。

原文链接:http://blog.csdn.net/shanzhizi/article/details/16882087

延伸 · 阅读

精彩推荐
  • Linux详解Linux系统下PXE服务器的部署过程

    详解Linux系统下PXE服务器的部署过程

    这篇文章主要介绍了Linux系统下PXE服务器的部署过程,包括对PXE的API架构作了一个基本的简介,需要的朋友可以参考下...

    运维之道9812019-07-04
  • LinuxLinux中环境变量配置的步骤详解

    Linux中环境变量配置的步骤详解

    Linux中环境变量包括系统级和用户级,系统级的环境变量是每个登录到系统的用户都要读取的系统变量,而用户级的环境变量则是该用户使用系统时加载的...

    Myths7882022-02-10
  • LinuxLinux常用的日志文件和常用命令

    Linux常用的日志文件和常用命令

    成功地管理任何系统的关键之一,是要知道系统中正在发生什么事。 Linux 中提供了异常日志,并且日志的细节是可配置的。Linux 日志都以明文形式存储,所...

    Linux教程网2632020-04-18
  • Linux确保Linux系统安全的前提条件 漏洞防护

    确保Linux系统安全的前提条件 漏洞防护

    Linux 作为开放式的操作系统受到很多程序员的喜爱,很多高级程序员都喜欢编写Linux操作系统的相关软件。这使得Linux操作系统有着丰富的软件支持,还有无...

    Linux之家2642020-04-11
  • LinuxLinux lnmp下无法使用mail发邮件的两种解决方法

    Linux lnmp下无法使用mail发邮件的两种解决方法

    在配置了lnmp环境后,出现了mail函数不能发送邮件的问题,其实有两种方法,一是使用sendmail组件,而是使用postfix。下面为大家一一介绍下 ...

    Linux之家4042019-09-17
  • Linuxlinux中rmdir命令使用详解(删除空目录)

    linux中rmdir命令使用详解(删除空目录)

    今天学习一下linux中命令: rmdir命令。rmdir是常用的命令,该命令的功能是删除空目录,一个目录被删除之前必须是空的 ...

    linux命令大全5372019-11-19
  • Linux手把手教您在 Linux 上使用 GPG 加解密文件

    手把手教您在 Linux 上使用 GPG 加解密文件

    在本教程中,我将告诉你如何用 GPG 加密和解密文件。这是一个简单的教程,你可以在你的 Linux 系统上尝试所有的练习。这将帮助你练习 GPG 命令,并在你...

    Linux中国6962021-12-15
  • LinuxLinux上设置用户通过SFTP访问目录的权限的方法

    Linux上设置用户通过SFTP访问目录的权限的方法

    这篇文章主要介绍了Linux上设置用户通过SFTP访问目录的权限的方法,SFTP可以理解为使用SSH协议进行FTP传输的协议,因而同时要对OpenSSH进行相关设置,需要的朋...

    OSChina10022019-06-19