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

先上一个代码

服务端:

//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 {undefined
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)
{undefined
// … (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 地址为 127.x.x.x 的网络套接字来实现。

进程通信(IPC)之QLocalSocket用法

原文链接:https://blog.csdn.net/fangye945a/article/details/106006819

进程通信(IPC)的方法有很多,项目开发中,需要根据业务需求来选择适合的IPC方式。所谓LocalSocket,其实就是在socket的基础上衍生出来的一种IPC通信机制。其旨在解决同一台主机上不同进程间互相通信的问题,不能像网络通信使用的socket一样实现不同主机间通信。但正因为这一点,它不需要经过网络协议栈,不需要打包拆包、计算校验,所以执行效率要更高。

而Qt对LocalSocket进行了封装(QLocalSocket),使其用起来更方便,下面介绍QLocalSocket在本地进程间进行通信的简单用法:

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

https://blog.csdn.net/qq_40989769/article/details/112392736?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522164731546516780269836988%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=164731546516780269836988&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2allfirst_rank_ecpm_v1~rank_v31_ecpm-4-112392736.142v2pc_search_result_cache,143v4control&utm_term=%E8%BF%9B%E7%A8%8B%E9%97%B4%E9%80%9A%E4%BF%A1+socket&spm=1018.2226.3001.4187

基于 QLocalSocket 、QLocalServer 的进程间通信

原文链接:https://blog.csdn.net/u011546766/article/details/86516339

基础

QLocalSocket 是本地 socket client 端,在 windows 里是用有名管道实现的,在 unix 里是本地域名socket实现的。
QLocalSocket 是事件驱动的,但也可以通过 waitFor… 系列方法阻塞使用。

QLocalServer 提供了本地 socket server 端。跟一般的 socket 使用方式类似:创建 对象,listen,wait connect。
QLocalServer 是事件驱动的,但也可以通过 waitFor… 系列方法阻塞使用。

原理

进程启动后,先创建 QLocalSocket client 端尝试连接 server 端。
如果 server 端存在,说明有一个进程在运行了,然后就解析 command line,将 命令通过 socket 发送给已经连接的 server ,待 server 端收到后,本进程退出。
如果 server 端不存在,说明本进程是唯一进程。那么就自己处理 command line,创建 QLocalServer 作为 server 端,等待 client 连接。

进程运行时,有一个 QLocalServer listen client,当有 QLocalSocket client 接入时,监听 readyRead() 信号,然后读取 client 发来的 data,做对应的 处理。

协议对接

协议格式:【operate】:【data】;
CMD,发送操作指令,主要用来将最小化的进程弹出且前置显示。格式为:“CMD:show”
SEND, 发送文件路径,主要是 鼠标 右键菜单里的 发送 功能。格式为:“SEND:【文件路径】;”
OPEN,
ToPhone,自动加好友,老实现,准备废弃。格式为:“ToPhone:【手机号】;”
RES,server 端收到 client 端 data 后,写给 client 的回执。格式为:“RES:【已运行的进程 id】”;

核心点

1、QLocalSocket client 端可以一次性写入所有 data,但 QLocalServer server 端未必能一次性全收到。
2、QLocalSocket client 端写入之后,什么时候本进程可以退出。
3、特殊字符如何处理。

实现

1、client 端不用管。server 端在 client 端接入后,把 client 保存起来,然后将所有从 client 端读入的数据 append 到一起,每次读入都按照协议格式检查一下是否收到了完整的命令,如果收到了则将命令从 数据里移除掉,避免重复处理。
2、client 在每次写入命令后 都会加上 CMD:show 用来把进程前置显示,server 端在收到后 CMD:show 后,给 client 写入 respones,client 收到 response 后说明 server 已经接收了命令,就可以退出了。
client 的链接如果断开,也可以退出了。
3、每个完整指令都是通过 “;” 分割,只要保证 operate 和 data 不会包含 “;” 就行了, operate 为协议指令、事先写死硬编码的、不用特殊处理,data 可能包含 “;”,但只要 base64 处理一下就行了。

伪码

client 代码:

QString serverName = ""; // 唯一固定值 用来标志 server 端。
	QLocalSocket client;
bool closing  = false;
QString socketRead;  // server 端发来的数据。
connect(&client, SIGNAL(connected()), this, SLOT(socketConnected()));
connect(&client, SIGNAL(disconnected()), this, SLOT(socketDisconnected()));
connect(&client, SIGNAL(error(QLocalSocket::LocalSocketError)), this, SLOT(socketError(QLocalSocket::LocalSocketError)));
connect(&client, SIGNAL(readyRead()), this, SLOT(socketReading()));

socket.connectToServer(serverName);

void Application::socketConnected() {
// 连上了 server 端,那么发送指令。
	closing = true;
	QString commands;
	const QStringList &lst(cSendPaths());
	for (QStringList::const_iterator i = lst.cbegin(), e = lst.cend(); i != e; ++i) {
// 编码
		commands += qsl("SEND:") + _escapeTo7bit(*i) + ';'; 
	}
	if (!cStartUrl().isEmpty()) {
// 编码
		commands += qsl("OPEN:") + _escapeTo7bit(cStartUrl()) + ';';
	}
	if ( cStartFromUrl()) {
		QString toPhoneNumber = cToPhoneNumber();
		if ( toPhoneNumber.count() > 0 ) {
// 编码
			commands += qsl("ToPhone:") + _escapeTo7bit(toPhoneNumber) + ';';
		}

	}
// 最后加上 CMD:show; 指令
	commands += qsl("CMD:show;");
	socket.write(commands.toLocal8Bit());
}

void Application::socketDisconnected() {
// closing 是个标志,如果连上了 server, 那么收到断开等信号的时候,就退出 app
	if (closing) {
		DEBUG_LOG(("Application Error: socket disconnected before command response received, quiting.."));
		return App::quit();
	}
}

void Application::socketError(QLocalSocket::LocalSocketError e) {
	if (closing) {
		DEBUG_LOG(("Application Error: could not write show command, error %1, quiting..").arg(e));
		return App::quit();
	}

	if (e != QLocalSocket::ServerNotFoundError) {
return App::quit();
	} 
	socket.close();

       // 只有在连不上 server 的时候,才当成 app 启动。
	startApp();
}
void Application::socketReading() {
	if (socket.state() != QLocalSocket::ConnectedState) {
		DEBUG_LOG(("Application Error: socket is not connected %1").arg(socket.state()));
		return;
	}
	socketRead.append(socket.readAll());
	if (QRegularExpression("RES:(\\d+);").match(socketRead).hasMatch()) {
/// 收到了 回执 指令,则退出
		uint64 pid = socketRead.mid(4, socketRead.length() - 5).toULongLong();
		psActivateProcess(pid);
		DEBUG_LOG(("Application Info: show command response received, pid = %1, activating and quiting..").arg(pid));
		return App::quit();
	}
}

server 端

QVector<QPair<QLocalSocket*, QByteArray>> clients; // 保存已经连接上来的 client
QString serverName = ""; // 唯一固定值 用来标志 server 端。
QLocalServer server;

connect(&server, SIGNAL(newConnection()), this, SLOT(newInstanceConnected()));

// 监听 serverName
if (!server.listen(serverName)) {
	DEBUG_LOG(("Application Error: failed to start listening to %1 server, error %2").arg(serverName).arg(int(server.serverError())));
	return App::quit();
}

void Application::newInstanceConnected() {
// 新来一个 client,保存起来
	for (QLocalSocket *client = server.nextPendingConnection(); client; client = server.nextPendingConnection()) {
		clients.push_back(ClientSocket(client, QByteArray()));
		connect(client, SIGNAL(readyRead()), this, SLOT(readClients()));
		connect(client, SIGNAL(disconnected()), this, SLOT(removeClients()));
	}
}

void Application::removeClients() {
// client 断开了,移除掉
	DEBUG_LOG(("Application Info: remove clients slot called, clients %1").arg(clients.size()));
	for (ClientSockets::iterator i = clients.begin(), e = clients.end(); i != e;) {
		if (i->first->state() != QLocalSocket::ConnectedState) {
			DEBUG_LOG(("Application Info: removing client"));
			i = clients.erase(i);
			e = clients.end();
		} else {
			++i;
		}
	}
}

void Application::readClients() {
// 客户端有数据发来
	for (ClientSockets::iterator i = clients.begin(), e = clients.end(); i != e; ++i) {
// 把发来的数据 统一保存起来
		i->second.append(i->first->readAll());
		if (i->second.size()) {
			QString cmds(QString::fromLocal8Bit(i->second));
			int32 from = 0, l = cmds.length();
// 检查收到的数据里,有没有 协议指令
			for (int32 to = cmds.indexOf(QChar(';'), from); to >= from; to = (from < l) ? cmds.indexOf(QChar(';'), from) : -1) {
				QStringRef cmd(&cmds, from, to - from);
				if (cmd.startsWith(qsl("CMD:"))) {
// 当收到 CMD: 指令,那么就写 RES: 回执
					execExternal(cmds.mid(from + 4, to - from - 4));
					QByteArray response(qsl("RES:%1;").arg(QCoreApplication::applicationPid()).toUtf8());
					i->first->write(response.data(), response.size());
				} else if (cmd.startsWith(qsl("SEND:"))) {
					if (cSendPaths().isEmpty()) {
						QStringList paths(cSendPaths());
// 因为有特殊字符,所以要解码
		paths.append(_escapeFrom7bit(cmds.mid(from + 5, to - from - 5)));
		cSetSendPaths(paths);
					}
				} else if (cmd.startsWith(qsl("OPEN:"))) {
					if (cStartUrl().isEmpty()) {
						startUrl = _escapeFrom7bit(cmds.mid(from + 5, to - from - 5));
					}
				} else if (cmd.startsWith(qsl("ToPhone:"))) {
					qint32 len = qsl("ToPhone:").count();
					QString toPhoneNumber = cmds.mid(from + len, to - from - len);
					if (toPhoneNumber.count() > 0) {
						QString toUsername = "u" + toPhoneNumber;
						if ( Xmpp::peerData()->isContact(toUsername) ) {
							App::histories().findOrInsert(toUsername, 0)->setPosInDialogsDate(QDateTime::currentDateTime());// addNewMessage(msg, NewMessageUnread);
							App::main()->showPeerHistory(toUsername, QString());
						}
						else {
							cSetToPhoneNumber(toPhoneNumber);
							Xmpp::manager()->autoSubscribeUser(toUsername);
						}
					}
				} else {
					LOG(("Application Error: unknown command %1 passed in local socket").arg(QString(cmd.constData(), cmd.length())));
				}
// to 是完整指令的 index, 每次处理完之后,要把 client 读出的 string 里已处理的字符串去掉,避免重复处理。
				from = to + 1;
			}
			if (from > 0) {
// 截断
				i->second = i->second.mid(from);
			}
		}
	}

	if (!cSendPaths().isEmpty()) {
		if (App::wnd()) {
			App::wnd()->sendPaths();
		}
	}
	if (!startUrl.isEmpty()) {
		cSetStartUrl(startUrl);
	}
	if (!cStartUrl().isEmpty() && App::main()) {
		App::main()->openLocalUrl(cStartUrl());
		cSetStartUrl(QString());
	}
}

void Application::execExternal(const QString &cmd) {
	if (cmd == "show") {
		window->activate();
	}
}

操作介绍

client 端

四个信号:
connected() : 已连接到 server 端
disconnected() : 从已连接的 server 端断开
error(QLocalSocket::LocalSocketError) : 发生 error
stateChanged(QLocalSocket::LocalSocketState socketState) : connect 状态改变

readyRead() : 有数据从 server 端发来
bytesWritten(qint64) : 成功发送数据 给 server 端

操作
connectToServer(OpenMode openMode = ReadWrite)、需要事先设置了 serverName、 setServerName(),然后直接连接到 serverName
connectToServer(const QString &name, OpenMode openMode = ReadWrite):同上一个方法。一旦调用,clinet 的状态 就变为 connectingState,连接上了就变为 connectedState,没连上就变为 unconnectedState,如果之前连上了,调用 close()后就变为 closingState、关掉之后就变为 unconnectedState

close() :断开连接,调用 close()后就变为 closingState、关掉之后就变为 unconnectedState

write(const char *data, qint64 maxSize)、
write(const QByteArray &byteArray)、
write(const char *data): 给 server 端发送 data

read(char *data, qint64 maxSize)、
read(qint64 maxSize)、
readAll() : 从 server 端读发来的 data

state() : 查询 connect state

server 端

1个信号:
newConnection() : 有新的 client 端连接。

操作:
listen(const QString &name)、
listen(qintptr socketDescriptor) : 启动 server 监听,

close() : 停止接收新连接。但不会影响以及连接的 client,新的 connect 请求会被 refuse。

nextPendingConnection() : 下一个新连接的 client,没有的话,返回 0, 一般是接收到 newConnection 信号调用。

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐