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.套接字协议
只要底层的传输机制允许不⽌⼀个协议来提供要求的套接字类型,我们就可以为套接字选择⼀个特定的协议。
先上⼀个代码
服务端:
//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;
}
客户端:
//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 系统调⽤,其原型如下:
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 类型的变量。
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,下⾯⽤代码说明:
第⼀种⽅式:
//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;
第⼆种⽅式:
#define SERVER_NAME @socket_server
//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前添加⼀个占位字符串,例如:
#define SERVER_NAME @socket_server
前⾯的@符号就表⽰占位符,不算为实际名称。
提⽰:客户端连接服务器的时候,必须与服务端的命名⽅式相同,即如果服务端是普通命名⽅式,客户端的地址也必须是普通命名⽅式;如果服务端是抽象命名⽅式,客户端的地址也必须是抽象命名⽅式。
三、绑定
SOCK_STREAM 式本地套接字的通信双⽅均需要具有本地地址,其中服务器端的本地地址需要明确指定,指定⽅法是使⽤struct sockaddr_un 类型的变量,将相应字段赋值,再将其绑定在创建的服务器套接字上,绑定要使⽤ bind 系统调⽤,其原形如下:
int bind(int socket, const struct sockaddr *address, size_t address_len);
其中 socket表⽰服务器端的套接字描述符,address 表⽰需要绑定的本地地址,是⼀个 struct sockaddr_un 类型的变
量,address_len 表⽰该本地地址的字节长度。实现服务器端地址指定功能的代码如下(假设服务器端已经通过上⽂所述的socket 系统调⽤创建了套接字,server_sockfd 为其套接字描述符):
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 为其套接字描述符):
struct sockaddr_un client_address;
client_address.sun_family = AF_UNIX;
strcpy(client_address.sun_path, "Server Socket");
四、监听
服务器端套接字创建完毕并赋予本地地址值(名称,本例中为Server Socket)后,需要进⾏监听,等待客户端连接并处理请求,监听使⽤ listen 系统调⽤,接受客户端连接使⽤accept系统调⽤,它们的原形如下:
int listen(int socket, int backlog);
int accept(int socket, struct sockaddr *address, size_t *address_len);
其中 socket 表⽰服务器端的套接字描述符;backlog 表⽰排队连接队列的长度(若有多个客户端同时连接,则需要进⾏排队);address 表⽰当前连接客户端的本地地址,该参数为输出参数,是客户端传递过来的关于⾃⾝的信息;address_len 表⽰当前连接客户端本地地址的字节长度,这个参数既是输⼊参数,⼜是输出参数。实现监听、接受和处理的代码如下:
#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 系统调⽤,其原形为int connect(int socket, const struct sockaddr *address, size_t address_len);
其中socket为客户端的套接字描述符,address表⽰当前客户端的本地地址,是⼀个 struct sockaddr_un 类型的变
量,address_len 表⽰本地地址的字节长度。实现连接的代码如下:
connect(client_sockfd, (struct sockaddr*)&client_address, sizeof(client_address));
⽆论客户端还是服务器,都要和对⽅进⾏数据上的交互,这种交互也正是我们进程通信的主题。⼀个进程扮演客户端的⾓⾊,另外⼀个进程扮演服务器的⾓⾊,两个进程之间相互发送接收数据,这就是基于本地套接字的进程通信。发送和接收数据要使⽤ write 和 read 系统调⽤,它们的原形为:
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" 字符串给服务器,则代码如下:
char buffer[10] = "Hello";
write(client_sockfd, buffer, strlen(buffer));
交互完成后,需要将连接断开以节省资源,使⽤close系统调⽤,其原形为:
int close(int socket);
不多说了,直接使⽤,⼤家⼀定都会,呵呵!
上⾯所述的每个系统调⽤都有 -1 返回值,在调⽤不成功时,它们均会返回 -1,这个特性可以使得我们⽤ if - else 或异常处理语句来处理错误,为我们提供了很⼤的⽅便。
SOCK_DGRAM 数据报式本地套接字的应⽤场合很少,因为流式套接字在本地的连接时间可以忽略,所以效率并没有提⾼,⽽且发送接收都需要携带对⽅的本地地址,因此很少甚⾄⼏乎不使⽤。
与本地套接字相对应的是⽹络套接字,可以⽤于在⽹络上传送数据,换⾔之,可实现不同机器上的进程通信过程。在 TCP/IP 协议中,IP 地址的⾸字节为 127 即代表本地,因此本地套接字通信可以使⽤ IP 地址为 的⽹络套接字来实现。
总结
以上就是本⽂关于Linux进程间通信⽅式之socket使⽤实例的全部内容,希望对⼤家有所帮助。欢迎参阅:、、等,感谢朋友们对本站的⽀持。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论