Linux 多进程通信开发(八): unix domain socket 之 TCP 通信

这会是一系列文章,讲解的内容也很简单,文章的目的是让自己的知识固话和文档化,以备自己不时的复习,同时也希望能够给予初学者一些帮助。

前面的文章一系列文章有介绍了 linux 下常见的 IPC 机制,如管道、消息队列、信号量、共享内存。

之前有讲到共享内存是最高效的 IPC 方式,但是在 linux 环境下,应用最广泛的可能是 Socket。

什么是 Unix Domain Socket ?

Socket 翻译过来就叫做套接字,一般我们指代的 Socket 其实是网络通信的 Socket,它用于在网络中不同的设备之间进行通信。

但 Socket 也可以用于同一台设备中不同的进程通信的,Android 系统开发中,它叫做 LocalSocket,我觉得 LocalSocket 更适合概括它。

那我们在 Linux 下开发,用于在本机不同进程通信的 Socket 就叫做 Unix Domain Socket。

本文就是讲解 Unix Domain Socket。

Socket 通信机制主要有 2 种,TCP 和 UDP.

TCP 是可靠的通信机制。
UDP 是不可靠的通信机制。

本文讲解 TCP 通信步骤,并将给出示例代码.

Socket 开发套路

我初学 Socket 开发时,很烦它,因为我是一个懒人,它的步骤太多了。

但后来克服恐惧后,细细想来,它也很简单,心平气和把他有条理的捋一捋,还是很简单的嘛,再复杂都是套路。

下面细细来讲,其实也不复杂。
在这里插入图片描述

服务器建立 Socket

#include <sys/types.h>
#include <sys/socket.h>

int socket (int domain, int type, int protocol)

  • domain 指定 Socket 类型,AF_IN,AF_UNIX 指的就是 Unix Domain Socket
  • type 指定通信机制类型 SOCK_STREAM 指定 TCP,SOCK_DGRAM 指定 UDP,SOCK_RAW 原始套接字
  • protocol 协议 一般为 0 就好了

所以,我们可以这样编码

int fd = socket(AF_UNIX,SOCK_STREAM,0);

服务器绑定地址

socket 是为了建立套接字,真正要工作的话,还需要进行绑定,在 TCP 中绑定的是一个文件。

int bind (int fd, CONST_SOCKADDR_ARG addr, socklen_t len)
  • fd 由 socket 返回来的套接字描述符
  • addr 指定了地址,它是 sockaddr_un 类型,在 sys/un.h 头文件中定义
  • len addr 的长度

用法如下:

char* server_file = "server.sock";

struct sockaddr_un addr;
memset(&addr,0,sizeof(addr));
addr.sun_family = AF_UNIX;
strcpy(addr.sun_path,server_file);

if (access(addr.sun_path,0) != -1)
{
    remove(addr.sun_path);
}

 if (bind(socketfd,(struct sockaddr*)&addr,sizeof(addr)) < 0)
{
    perror("bind");
    return -1;
}

这里有一个细节:如果绑定的文件已经存在,要删掉它,不然bind不会成功的

服务器监听连接

int listen (int fd, int n)
  • fd 就是 socket 的描述符
  • n 就是最大的连接数量

listen() 是服务器角色用的

服务器接受连接

int accept (int fd, SOCKADDR_ARG addr,
		   socklen_t *addr_len)

listen 是为连接做准备,而 accept 则会等待新的连接,如果有新的连接,那就会返回一个新的连接的文件描述符。

连接成功后,会将客户端的地址填充到 addr 的结构体上,字节大小填充到 addr_len。

服务器读信息

连接成功后,服务端和客户端双方就可以读写信息了。

#include <sys/types.h>
#include <sys/socket.h>

ssize_t recv(int sockfd, void *buf, size_t len, int flags);

读写的字节由 len 决定,读取成功后,将内容存放在 buf 当中。

如果读取成功则返回接收到的字节数量。

如果发生异常,则返回 -1,通常用返回值判断连接是否正常。

服务器写信息

#include <sys/types.h>
#include <sys/socket.h>

ssize_t send(int sockfd, const void *buf, size_t len, int flags);

要发送的信息存放在 buf 当中,本次要发送的字节数量由 len 决定。

服务器关闭 Socket


int close(int fd);

像关闭文件一样,可以关闭一个 Socket,其实在 linux 中,sokcet 本质也相当于一个文件。

上面是服务端的 TCP 编码流程,客户端的编码流程基本一致。

客户端建立 Socket

细节同服务端

客户端连接服务端

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

int connect(int sockfd, const struct sockaddr *addr,
                   socklen_t addrlen);

connect() 是客户端专用,用来连接server

connect 异常时返回 -1,否则返回 0.

地址在 addr 中指明。

用法如下:

struct sockaddr_un serveraddr;
socklen_t addrlen = sizeof(serveraddr);
memset(&serveraddr,0,addrlen);
serveraddr.sun_family = AF_UNIX;
strcpy(serveraddr.sun_path,server_file);

int newcon = -1;
newcon = connect(socketfd,(sockaddr*)&serveraddr,addrlen);

客户端读写数据

细节同服务端

客户端关闭 Socket

细节同服务端

代码示例

接下来,我们编写一个简单的 demo.

tcplocalserver.cpp

#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <memory.h>
#include <unistd.h>
#include <stdio.h>

using namespace std;


char* server_file = "/tmp/localserver.sock";

int main(int argc,char** argv)
{

    int socketfd = socket(AF_UNIX,SOCK_STREAM,0);
    if ( socketfd < 0)
    {
        perror("socket");
        return -1;
    }


    struct sockaddr_un addr;
    memset(&addr,0,sizeof(addr));
    addr.sun_family = AF_UNIX;
    strcpy(addr.sun_path,server_file);

    if (access(addr.sun_path,0) != -1)
    {
        remove(addr.sun_path);
    }

    if (bind(socketfd,(struct sockaddr*)&addr,sizeof(addr)) < 0)
    {
        perror("bind");
        return -1;
    }

    if (listen(socketfd,12) < 0)
    {
        perror("listen");
        return -1;
    }

    struct sockaddr_un clientaddr;
    socklen_t addrlen = sizeof(clientaddr);

    char buf[128];

    int newcon = -1;

    newcon = accept(socketfd,(sockaddr*)&clientaddr,&addrlen);

    if (newcon > 0)
    {

        int curindex = 0;
        int edgeindex = 0;
        while (1)
        {
            if (recv(newcon,buf,sizeof(buf),0) == -1)
            {
                perror("server recv:");
                break;
            }

            cout << "server recv a message:" << buf << endl;

            memset(buf,0,sizeof(buf));


            if (send(newcon,"ok",2,0) < 0)
            {
                perror("server send:");
                break;
            }
        }

    }

    close(newcon);
    close(socketfd);

    return 0;
}

代码很简单,读取客户端的消息,然后回复 OK.

tcplocalclient.cpp

#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <memory.h>
#include <unistd.h>
#include <stdio.h>

using namespace std;


char* server_file = "/tmp/localserver.sock";
char* client_file = "/tmp/localclient.sock";

int main(int argc,char** argv)
{

    int socketfd = socket(AF_UNIX,SOCK_STREAM,0);
    if ( socketfd < 0)
    {
        perror("client socket");
        return -1;
    }


    struct sockaddr_un addr;
    memset(&addr,0,sizeof(addr));
    addr.sun_family = AF_UNIX;
    strcpy(addr.sun_path,client_file);

    if (access(addr.sun_path,0) != -1)
    {
        remove(addr.sun_path);
    }

    if (bind(socketfd,(struct sockaddr*)&addr,sizeof(addr)) < 0)
    {
        perror("client bind");
        return -1;
    }


    struct sockaddr_un serveraddr;
    socklen_t addrlen = sizeof(serveraddr);
    memset(&serveraddr,0,addrlen);
    serveraddr.sun_family = AF_UNIX;
    strcpy(serveraddr.sun_path,server_file);

    char buf[128];

    int newcon = -1;


    newcon = connect(socketfd,(sockaddr*)&serveraddr,addrlen);

    if (newcon < 0)
    {
        perror("client connect");
    }


    int curindex = 0;
    int edgeindex = 0;
    int index = 0;
    
    while (index < 3)
    {
        
        string temp = "client for test:";
        temp.append(to_string(index));

        if (send(socketfd,temp.c_str(),temp.capacity(),0) < 0)
        {
            perror("client send:");
            break;
        }

        if (recv(socketfd,buf,sizeof(buf),0) == -1)
        {
            perror("client recv:");
            break;
        }

        cout << "client recv:" << buf << endl;

        memset(buf,0,sizeof(buf));

        index++;

        sleep(1);

    }


    close(socketfd);

    return 0;
}

客户端发送了 3 次信息,然后关闭了自己。

下面就可以测试了。

g++ tcplocalserver.cpp -o server
g++ tcplocalclient.cpp -o client -std=c++11

然后开 2 个终端 分别执行 server 和 client 两个可执行文件。

./server

./client

server 所在的终端打印如下:

server recv a message:client for test:0
server recv a message:client for test:1
server recv a message:client for test:2

client 所在的终端打印如下:

client recv:ok
client recv:ok
client recv:ok

需要注意的是,因为便于简单展示 Socket 的开发,本章用到的示例是基于最简单的单线程的,但一般开发时服务器都是 1 对多,需要多线程处理,读者可以了解基本的概念后自行做更复杂的代码编写工作。

发布了102 篇原创文章 · 获赞 3346 · 访问量 175万+

猜你喜欢

转载自blog.csdn.net/briblue/article/details/89435736