TCP UDP 在传输层
Linux socket 本地进程间通信
socket起源于Unix,而Unix/Linux基本哲学之一就是“一切皆文件”,都可以用“打开open –> 读写write/read –> 关闭close”模式来操作。Socket就是该模式的一个实现, socket即是一种特殊的文件,一些socket函数就是对其进行的操作(读/写IO、打开、关闭).
说白了Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。
注意:其实socket也没有层的概念,它只是一个facade设计模式的应用,让编程变的更简单。是一个软件抽象层。在网络编程中,我们大量用的都是通过socket实现的。
使用套接字除了可以实现网络间不同主机间的通信外,还可以实现同一主机的不同进程间的通信,且建立的通信是双向的通信。socket进程通信与网络通信使用的是统一套接口,只是地址结构与某些参数不同。
其主要流程如下:
一. 创建socket
创建socket,类型为AF_LOCAL或AF_UNIX,表示用于进程通信:
调用函数socket(),其原型如下:
int socket(int domain, int type, int protocol);
参数:
domain:指定协议族,对于本地套接字来说,值必须设置为AF_UNIX枚举值;(hy:AF_INET 不同主机间)
type:指定套接字类型,可以被设置为SOCK_STREAM(流式套接字 hy:TCP)活SOCK_DGRAM(数据报式套接字 hy:UDP)
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]; // 路径名
}
三. 绑定
绑定要使用 bind 系统调用,其原形如下:
int bind(int socket, const struct sockaddr *address, size_t address_len);
参数
socket:服务端套接字描述符
address:需要绑定的服务端本地地址
address_len:本地地址的字节长度
四. 监听
服务器端套接字创建完毕并赋予本地地址值(名称,本例中为CAN_SERVICE)后,需要进行监听,等待客户端连接并处理请求,监听使用 listen 系统调用,接受客户端连接使用accept系统调用,它们的原形如下:
int listen(int socket, int backlog);
int accept(int socket, struct sockaddr *address, size_t *address_len);
参数
socket:表示服务器端的套接字描述符;
backlog 表示排队连接队列的长度(若有多个客户端同时连接,则需要进行排队);
address 表示当前连接客户端的本地地址,该参数为输出参数,是客户端传递过来的关于自身的信息;
address_len 表示当前连接客户端本地地址的字节长度,这个参数既是输入参数,又是输出参数。实现监听、接受和处理。
五. 连接
客户端需要socket系统调用connect()连接到服务端,其函数原型如下:
int connect(int socket, const struct sockaddr *address, size_t address_len);
参数
socket:客户端的套接字描述符
address:当前客户端的本地地址,是一个 struct sockaddr_un 类型的变量
address_len:表示本地地址的字节长度
五. 数据交互
无论客户端还是服务器,都要和对方进行数据上的交互。一个进程扮演客户端的角色,另外一个进程扮演服务器的角色,两个进程之间相互发送接收数据,这就是基于本地套接字的进程通信。
循环读取客户端发送的消息,当客户端没有发送数据时会阻塞直到有数据到来。如果想要多个连接并发处理,需要创建线程,将每个连接交给相应的线程并发处理。接收到数据后,进行相应的处理,将结果返回给客户端。发送和接收数据要使用 write 和 read 系统调用,它们的原形为:
int read(int socket, char *buffer, size_t len);
int write(int socket, char *buffer, size_t len);
以下为简单的,socket本地进程间通讯的例子:
1. 服务端
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#define CAN_SERVICE "CAN_SERVICE"
int main(void)
{
int ret;
int len;
int accept_fd;
int socket_fd;
static char recv_buf[1024];
socklen_t clt_addr_len;
struct sockaddr_un clt_addr;
struct sockaddr_un srv_addr;
socket_fd=socket(PF_UNIX,SOCK_STREAM,0);
if(socket_fd<0)
{
perror("cannot create communication socket");
return 1;
}
// 设置服务器参数
srv_addr.sun_family=AF_UNIX;
strncpy(srv_addr.sun_path,CAN_SERVICE,sizeof(srv_addr.sun_path)-1);
unlink(CAN_SERVICE);
// 绑定socket地址
ret=bind(socket_fd,(struct sockaddr*)&srv_addr,sizeof(srv_addr));
if(ret==-1)
{
perror("cannot bind server socket");
close(socket_fd);
unlink(CAN_SERVICE);
return 1;
}
// 监听
ret=listen(socket_fd,1);
if(ret==-1)
{
perror("cannot listen the client connect request");
close(socket_fd);
unlink(CAN_SERVICE);
return 1;
}
// 接受connect请求
len=sizeof(clt_addr);
accept_fd=accept(socket_fd,(struct sockaddr*)&clt_addr,&len);
if(accept_fd<0)
{
perror("cannot accept client connect request");
close(socket_fd);
unlink(CAN_SERVICE);
return 1;
}
// 读取和写入
memset(recv_buf,0,1024);
int num=read(accept_fd,recv_buf,sizeof(recv_buf));
printf("Message from client (%d)) :%s\n",num,recv_buf);
// 关闭socket
close(accept_fd);
close(socket_fd);
unlink(CAN_SERVICE);
return 0;
}
2. 客户端
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#define CAN_SERVICE "CAN_SERVICE"
int main(void)
{
int ret;
int socket_fd;
char snd_buf[1024];
static struct sockaddr_un srv_addr;
// 创建socket
socket_fd=socket(PF_UNIX,SOCK_STREAM,0);
if(socket_fd<0)
{
perror("cannot create communication socket");
return 1;
}
srv_addr.sun_family=AF_UNIX;
strcpy(srv_addr.sun_path,CAN_SERVICE);
// 连接到服务器
ret=connect(socket_fd,(struct sockaddr*)&srv_addr,sizeof(srv_addr));
if(ret==-1)
{
perror("cannot connect to the server");
close(socket_fd);
return 1;
}
memset(snd_buf,0,1024);
strcpy(snd_buf,"message from client");
// 读取和写入
write(socket_fd,snd_buf,sizeof(snd_buf));
close(socket_fd);
return 0;
}
gcc -o service service.c
gcc -o client client.c
运行
与本地套接字相对应的是网络套接字,可以用于在网络上传送数据,换言之,可实现不同机器上的进程通信过程。在 TCP/IP 协议中,IP 地址的首字节为 127 即代表本地,因此本地套接字通信可以使用 IP 地址为 127.x.x.x 的网络套接字来实现。
————————————————
版权声明:本文为CSDN博主「GXYandSXP」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/u010073981/article/details/50734484
*************************************************************************************************************************************************
sockaddr_in 主要用于不同主机之间的socket编程;
sockaddr_un 主要用于同一个主机中的本地Local socket
sockaddr
struct sockaddr {
unsigned short sa_family; /* address family, AF_xxx */
char sa_data[14]; /* 14 bytes of protocol address */
};
sa_family是地址家族,一般都是“AF_xxx”的形式。好像通常大多用的是都是AF_INET。
sa_data是14字节协议地址。
此数据结构用做bind、connect、recvfrom、sendto等函数的参数,指明地址信息。
但一般编程中并不直接针对此数据结构操作,而是使用另一个与sockaddr等价的数据结构
sockaddr_in (网络套接字使用)
sockaddr_in(在netinet/in.h中定义):
struct sockaddr_in {
short int sin_family; /* Address family */
unsigned short int sin_port; /* Port number */
struct in_addr sin_addr; /* Internet address */
unsigned char sin_zero[8]; /* Same size as struct sockaddr */
};
struct in_addr {
unsigned long s_addr;
};
typedef struct in_addr {
union {
struct{
unsigned char s_b1,
s_b2,
s_b3,
s_b4;
} S_un_b;
struct {
unsigned short s_w1,
s_w2;
} S_un_w;
unsigned long S_addr;
} S_un;
} IN_ADDR;
sin_family指代协议族,在socket编程中只能是AF_INET
sin_port存储端口号(使用网络字节顺序)
sin_addr存储IP地址,使用in_addr这个数据结构
sin_zero是为了让sockaddr与sockaddr_in两个数据结构保持大小相同而保留的空字节。
s_addr按照网络字节顺序存储IP地址
sockaddr_in和sockaddr是并列的结构,指向sockaddr_in的结构体的指针也可以指向
sockadd的结构体,并代替它。也就是说,你可以使用sockaddr_in建立你所需要的信息,
在最后用进行类型转换就可以了bzero((char*)&mysock,sizeof(mysock));//初始化
mysock结构体名
mysock.sa_family=AF_INET;
mysock.sin_addr.s_addr=inet_addr("192.168.0.1");
……
等到要做转换的时候用:
(struct sockaddr*)mysock
sockaddr_un(本地套接字使用)
进程间通信的一种方式是使用UNIX套接字,人们在使用这种方式时往往用的不是网络套接字,而是一种称为本地套接字的方式。这样做可以避免为黑客留下后门。
创建
使用套接字函数socket创建,不过传递的参数与网络套接字不同。域参数应该是PF_LOCAL或者PF_UNIX,而不能用PF_INET之类。本地套接字的通讯类型应该是SOCK_STREAM或SOCK_DGRAM,协议为默认协议。例如:
int sockfd;
sockfd = socket(PF_LOCAL, SOCK_STREAM, 0);
绑定
创建了套接字后,还必须进行绑定才能使用。不同于网络套接字的绑定,本地套接字的绑定的是struct sockaddr_un结构。struct sockaddr_un结构有两个参数:sun_family、sun_path。sun_family只能是AF_LOCAL或AF_UNIX,而sun_path是本地文件的路径。通常将文件放在/tmp目录下。例如:
struct sockaddr_un sun;
sun.sun_family = AF_LOCAL;
strcpy(sun.sun_path, filepath);
bind(sockfd, (struct sockaddr*)&sun, sizeof(sun));
监听
本地套接字的监听、接受连接操作与网络套接字类似。
连接
连接到一个正在监听的套接字之前,同样需要填充struct sockaddr_un结构,然后调用connect函数。
连接建立成功后,我们就可以像使用网络套接字一样进行发送和接受操作了。甚至还可以将连接设置为非阻塞模式,这里就不赘述了。
————————————————
版权声明:本文为CSDN博主「从此醉」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/gladyoucame/article/details/8768731