IPC通信--socket

1.windows环境

在C++中,Windows环境下实现socket通信的客户端与服务端的流程如下:

  1. 创建套接字:使用socket()函数创建一个套接字。
  2. 绑定套接字:使用bind()函数将套接字与一个地址(IP和端口)绑定在一起。
  3. 监听连接:使用listen()函数让服务器开始监听客户端的连接请求。
  4. 主动连接:使用connect()函数建立与指定IP地址和端口号的服务器的连接
  5. 接受连接:使用accept()函数接受客户端的连接请求。
  6. 发送和接收数据:使用send()和recv()函数进行数据的发送和接收。
  7. 关闭套接字:使用closesocket()函数关闭套接字。
  8. select函数:是一个用于I/O多路复用的函数。它可以同时监控多个socket,当某个socket上有数据可读、可写或者出现异常时,select()函数会返回。这样可以避免阻塞等待某个socket上的事件,提高程序的效率。

 socket()函数 

用于创建套接字的函数

#include 

SOCKET socket(int af, int type, int protocol);

参数说明:

  • af:地址族(Address Family),表示所使用的协议族。常用的值有AF_INET(IPv4协议)和AF_INET6(IPv6协议)。
  • type:套接字类型,表示套接字的功能。常用的值有SOCK_STREAM(面向连接的流式套接字,如TCP)和SOCK_DGRAM(无连接的数据报套接字,如UDP)。
  • protocol:协议类型,通常设置为0,让系统自动选择合适的协议。

返回值:

  • 如果成功,返回一个套接字描述符(非负整数)。
  • 如果失败,返回INVALID_SOCKET(-1)。

可能遇到的问题:

  1. 在使用socket()函数之前,需要先调用WSAStartup()函数初始化Winsock库。
  2. 在使用完套接字后,需要调用closesocket()函数关闭套接字。
  3. 在使用socket()函数时,可能会遇到错误,可以通过调用WSAGetLastError()函数获取错误代码。

 bind()函数

用于将套接字与特定的IP地址和端口号绑定。具体定义如下: 

#include 
#include 

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

参数说明:

  • sockfd:套接字描述符,由socket()函数创建。
  • addr:指向struct sockaddr结构体的指针,包含了要绑定的IP地址和端口号信息。
  • addrlen:addr指针所指向的结构体的大小。

返回值:

  • 成功:返回0。
  • 失败:返回-1,并设置errno为相应的错误码。

可能遇到的问题:

  1. 如果传入的sockfd不是有效的套接字描述符,bind()函数将返回-1,并设置errno为EBADF(Bad file number)。
  2. 如果传入的addr指针为NULL,bind()函数将返回-1,并设置errno为EINVAL(Invalid argument)。
  3. 如果传入的addrlen小于实际结构体大小,bind()函数将返回-1,并设置errno为EINVAL(Invalid argument)。
  4. 如果传入的IP地址和端口号已经被其他套接字绑定,bind()函数将返回-1,并设置errno为EADDRINUSE(Address already in use)。
  5. 如果传入的IP地址不在本地主机上,bind()函数将返回-1,并设置errno为EADDRNOTAVAIL(Cannot assign requested address)。

 listen()函数

listen()函数用于监听来自客户端的连接请求。它是TCP服务器端套接字的一个函数,用于设置套接字为监听模式,以便接受客户端的连接请求。

#include 
int listen(int sockfd, int backlog);

参数:

  • sockfd:一个已绑定到本地地址(使用bind()函数)的服务端套接字描述符。
  • backlog:允许挂起的最大连接数。通常设置为5或更大的值。

返回值:

  • 成功:返回0。
  • 失败:返回-1,并设置errno。

可能遇到的问题:

  1. 如果sockfd不是一个有效的套接字描述符,listen()函数将返回-1,并设置errno为EBADF。
  2. 如果sockfd未绑定到一个本地地址,listen()函数将返回-1,并设置errno为EINVAL。
  3. 如果backlog小于0,listen()函数将返回-1,并设置errno为EINVAL。
  4. 如果系统资源不足,listen()函数将返回-1,并设置errno为EAGAIN或ENOMEM。

connect()函数

#include 

int connect(
  SOCKET         s,
  const struct sockaddr *name,
  int            namelen
);

参数说明:

  • s:一个有效的套接字描述符,由socket()函数创建。
  • name:指向一个sockaddr结构的指针,该结构包含了要连接的服务器的地址信息。
  • namelen:name参数指向的结构的大小(以字节为单位)。

返回值:

  • 如果成功,返回0;如果失败,返回SOCKET_ERROR。可以使用WSAGetLastError()函数获取错误代码。

可能遇到的问题:

  1. 网络不可用或无法访问目标服务器。
  2. 目标服务器未运行或未监听指定的端口。
  3. 连接超时或被拒绝。
  4. 本地计算机上的防火墙设置阻止了连接。
  5. 本地计算机上的网络配置问题。

accept()函数

accept()函数用于接受一个已连接的客户端请求。它是在服务器端使用的,用于从已完成连接队列中取出一个连接请求,并创建一个新的套接字与客户端进行通信。

#include 
#include 
#include 
#include 

SOCKET accept(
  SOCKET   s,
  struct sockaddr *addr,
  int      *addrlen
);

参数说明:

  1. s:服务器端的套接字描述符,通常是通过socket()函数创建的。
  2. addr:指向一个sockaddr结构体的指针,用于存储客户端的地址信息。
  3. addrlen:指向一个整数的指针,表示addr缓冲区的大小。在调用accept()之前,需要将addrlen设置为addr缓冲区的大小;在调用accept()之后,addrlen将被设置为实际接收到的客户端地址信息的长度。

返回值:

  • 如果成功,accept()函数返回一个新的套接字描述符,用于与客户端进行通信。
  • 如果失败,返回INVALID_SOCKET(通常为-1)。

可能遇到的问题:

  1. 如果在调用accept()之前没有调用bind()和listen()函数,将无法接受客户端的连接请求。
  2. 如果客户端连接请求的数量超过了系统允许的最大值,accept()函数可能会阻塞,直到有可用的连接请求为止。
  3. 如果客户端连接请求被拒绝,accept()函数将返回错误代码。

 select()函数

#include 
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

参数说明:

  • nfds:需要监控的文件描述符的最大值加1。
  • readfds:需要监控可读事件的socket文件描述符集合。
  • writefds:需要监控可写事件的socket文件描述符集合。
  • exceptfds:需要监控异常事件的socket文件描述符集合。
  • timeout:select()函数的超时时间,如果设置为NULL,则表示永远等待。

返回值:

  • 成功:返回就绪的文件描述符个数。
  • 超时:返回0。
  • 出错:返回-1。

可能遇到的问题:

  1. 每次调用select()函数时,都需要重新设置文件描述符集合,这可能会导致效率较低。
  2. select()函数只能处理最多1024个文件描述符,如果需要处理更多的文件描述符,可以使用poll()函数。
  3. select()函数在Windows环境下不可用,可以使用WSAEventSelect()函数替代。

示例

#include 
#include 
#include 
#include 

#pragma comment(lib, "Ws2_32.lib")

int main() {
    WSADATA wsaData;
    WSAStartup(MAKEWORD(2, 2), &wsaData);

    SOCKET listenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    sockaddr_in serverAddr;
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_addr.s_addr = htonl(INADDR_ANY);
    serverAddr.sin_port = htons(8888);
    bind(listenSocket, (SOCKADDR*)&serverAddr, sizeof(serverAddr));
    listen(listenSocket, 5);

    fd_set readfds;
    FD_ZERO(&readfds);
    FD_SET(listenSocket, &readfds);

    while (1) {
        struct timeval timeout;
        timeout.tv_sec = 5;
        timeout.tv_usec = 0;

        int ret = select(0, &readfds, NULL, NULL, &timeout);
        if (ret == SOCKET_ERROR) {
            printf("select error: %d", WSAGetLastError());
            break;
        } else if (ret == 0) {
            printf("select timeout");
            continue;
        }

        for (int i = 0; i < listenSocket + 1; i++) {
            if (FD_ISSET(i, &readfds)) {
                if (i == listenSocket) {
                    sockaddr_in clientAddr;
                    int clientAddrLen = sizeof(clientAddr);
                    SOCKET clientSocket = accept(listenSocket, (SOCKADDR*)&clientAddr, &clientAddrLen);
                    FD_SET(clientSocket, &readfds);
                    printf("new connection: %d", clientSocket);
                } else {
                    char buffer[1024];
                    int recvLen = recv(i, buffer, sizeof(buffer) - 1, 0);
                    if (recvLen <= 0) {
                        closesocket(i);
                        FD_CLR(i, &readfds);
                        printf("connection closed: %d", i);
                    } else {
                        buffer[recvLen] = '\0';
                        printf("received from %d: %s", i, buffer);
                    }
                }
            }
        }
    }

    closesocket(listenSocket);
    WSACleanup();
    return 0;
}

send()函数

send()函数用于发送数据

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

参数说明:

  • sockfd:表示要发送数据的套接字描述符。
  • buf:指向要发送数据的缓冲区。
  • len:要发送数据的长度。
  • flags:可选参数,用于设置发送数据的行为,如阻塞或非阻塞等。常用的标志有:MSG_DONTROUTE(不查找路由表)、MSG_OOB(发送带外数据)等。

返回值:

  • 成功时,返回实际发送的字节数。
  • 失败时,返回-1,并设置errno为相应的错误码。

可能遇到的问题:

  1. 发送缓冲区满:当发送缓冲区满时,send()函数会阻塞,直到有足够的空间可用。可以通过设置socket选项SO_SNDBUF来调整发送缓冲区的大小。
  2. 网络中断:当网络连接中断时,send()函数可能会返回-1,并设置errno为EINTR。此时,可以尝试重新发送数据。
  3. 地址不可达:当目标地址不可达时,send()函数会返回-1,并设置errno为EHOSTUNREACH。需要检查目标地址是否正确。
  4. 超时:当发送操作超时时,send()函数会返回-1,并设置errno为EAGAIN或EWOULDBLOCK。可以通过设置socket选项SO_SNDTIMEO来设置发送超时时间。

recv()函数

用于接收从指定的socket发送过来的数据

#include 

int recv( SOCKET s, char *buf,  int len,  int flags);

参数说明:

  • s:指定要接收数据的socket。
  • buf:指向接收到的数据的缓冲区。
  • len:指定要接收的最大字节数。
  • flags:通常设置为0,表示使用默认操作。

返回值:

  • 如果成功,返回接收到的字节数;
  • 如果失败,返回SOCKET_ERROR,可以通过调用WSAGetLastError()函数获取错误代码。

可能遇到的问题:

  1. 网络连接断开或对方关闭连接,recv()函数会返回0,表示连接已关闭。
  2. 阻塞模式下,如果没有数据可接收,recv()函数会一直等待,直到有数据到达或发生错误。
  3. 非阻塞模式下,如果没有数据可接收,recv()函数会立即返回SOCKET_ERROR,错误代码为WSAEWOULDBLOCK。
  4. 缓冲区大小不足以容纳接收到的数据,recv()函数会截断数据并返回实际接收到的字节数。
  5. 网络延迟或丢包可能导致数据接收不完整或顺序错乱。

代码示例

服务端

#include 
#include 
#pragma comment(lib, "ws2_32.lib") // Winsock Library

#define BUF_SIZE 100
#define SERVER_IP "127.0.0.1"
#define SERVER_PORT 1234
int main() {
    WSADATA wsaData;
    SOCKET hServSock, hClntSock;
    char buffer[BUF_SIZE];
    int strLen;
    SOCKADDR_IN servAddr, clntAddr;
    int clntAddrSize;

    // 初始化Winsock
    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
        std::cerr << "WSAStartup failed.\n";
        return -1;
    }

    // 创建套接字
    hServSock = socket(AF_INET, SOCK_STREAM, 0);
    if (hServSock == INVALID_SOCKET) {
        std::cerr << "Socket creation failed.\n";
        return -1;
    }

    // 绑定套接字
    memset(&servAddr, 0, sizeof(servAddr));
    servAddr.sin_family = AF_INET;
    servAddr.sin_addr.s_addr = inet_addr(SERVER_IP);
    servAddr.sin_port = htons(SERVER_PORT);
    if (bind(hServSock, (SOCKADDR*)&servAddr, sizeof(servAddr)) == SOCKET_ERROR) {
        std::cerr << "Bind failed.\n";
        return -1;
    }

    // 监听套接字
    if (listen(hServSock, 5) == SOCKET_ERROR) {
        std::cerr << "Listen failed.\n";
        return -1;
    }

    // 接受客户端的连接请求
    clntAddrSize = sizeof(clntAddr);
    hClntSock = accept(hServSock, (SOCKADDR*)&clntAddr, &clntAddrSize);
    if (hClntSock == INVALID_SOCKET) {
        std::cerr << "Accept failed.\n";
        return -1;
    }

    // 接收并打印数据
    while ((strLen = recv(hClntSock, buffer, BUF_SIZE, 0)) != 0) {
        buffer[strLen] = '\0';
        std::cout << "Received: " << buffer << std::endl;
    }

    // 关闭套接字
    closesocket(hClntSock);
    closesocket(hServSock);
    WSACleanup();

    return 0;
}

客户端

#include 
#include 
#pragma comment(lib, "ws2_32.lib") // Winsock Library

#define BUF_SIZE 100
#define SERVER_IP "127.0.0.1"
#define SERVER_PORT 1234
int main() {
    WSADATA wsaData;
    SOCKET hSocket;
    char buffer[BUF_SIZE];
    int strLen;
    SOCKADDR_IN servAddr;

    // 初始化Winsock
    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
        std::cerr << "WSAStartup failed.\n";
        return -1;
    }

    // 创建套接字
    hSocket = socket(PF_INET, SOCK_STREAM, 0);
    if (hSocket == INVALID_SOCKET) {
        std::cerr << "Socket creation failed.\n";
        return -1;
    }

    // 设置服务器地址结构体
    memset(&servAddr, 0, sizeof(servAddr));
    servAddr.sin_family = AF_INET;
    servAddr.sin_addr.s_addr = inet_addr(SERVER_IP);
    servAddr.sin_port = htons(SERVER_PORT);

    // 连接到服务器
    if (connect(hSocket, (SOCKADDR*)&servAddr, sizeof(servAddr)) == SOCKET_ERROR) {
        std::cerr << "Connect failed.\n";
        return -1;
    }

    // 发送数据
    while (true) {
        std::cin >> buffer;
        send(hSocket, buffer, strlen(buffer), 0);
    }

    // 关闭套接字
    closesocket(hSocket);
    WSACleanup();

    return 0;
}

编译

g++ server.cpp -o server -lws2_32

g++ client.cpp -o client -lws2_32

2.Linux环境

 在C语言的Linux环境下,实现socket通信以实现进程间通信,可以分为以下几个步骤:

  1. 创建socket
  2. 绑定地址和端口
  3. 监听连接
  4. 发送和接收数据

 创建socket

 需要包含头文件。然后,使用socket()函数创建一个socket

#include 
#include 
#include 

int main() {
    int server_sockfd;
    server_sockfd = socket(AF_INET, SOCK_STREAM, 0);
}

 绑定地址和端口

使用bind()函数将socket与指定的地址和端口绑定

#include 

struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(8888);
server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");

bind(server_sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr));

监听连接/主动连接

使用listen()函数开始监听客户端的连接请求

listen(server_sockfd, 5);

使用connect()函数用于建立与指定IP地址和端口号的服务器之间的连接 

#include 
#include 
#include 
#include 
#include 

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

参数说明:

  • sockfd:套接字描述符,由socket()函数创建。
  • addr:指向目标服务器的地址结构体指针,通常使用struct sockaddr_in来表示IPv4地址。
  • addrlen:地址结构体的大小,对于struct sockaddr_in,其大小为sizeof(struct sockaddr_in)

返回值:

  • 成功:返回0,表示连接已建立。
  • 失败:返回-1,表示连接失败,可以通过errno获取错误码。

发送和接收数据

服务端使用accept()函数接受客户端的连接请求,然后使用send()recv()函数进行数据的发送和接收。

#include 

struct sockaddr_in client_addr;
socklen_t client_addr_len = sizeof(client_addr);
int client_sockfd = accept(server_sockfd, (struct sockaddr *)&client_addr, &client_addr_len);

char buffer[1024];
send(client_sockfd, "Hello, client!", strlen("Hello, client!"), 0);
recv(client_sockfd, buffer, sizeof(buffer), 0);

代码示例

服务端

#include 
#include 
#include 
#include 

int main() {
    // 创建socket
    int server_fd = socket(AF_INET, SOCK_STREAM, 0);

    // 定义sockaddr_in结构体
    struct sockaddr_in server_addr;
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(8888); // 端口号
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY); // IP地址

    // 绑定socket
    bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr));

    // 监听socket
    listen(server_fd, 5);

    // 接受客户端连接
    struct sockaddr_in client_addr;
    socklen_t client_addr_len = sizeof(client_addr);
    int client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &client_addr_len);

    // 向客户端发送数据
    char msg[] = "Hello, client!";
    send(client_fd, msg, strlen(msg), 0);

    // 关闭socket
    close(client_fd);
    close(server_fd);

    return 0;
}

客户端

#include 
#include 
#include 
#include 
#include 

int main() {
    // 创建socket
    int client_fd = socket(AF_INET, SOCK_STREAM, 0);

    // 定义sockaddr_in结构体
    struct sockaddr_in server_addr;
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(8888); // 端口号
    server_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); // IP地址

    // 连接服务器
    connect(client_fd, (struct sockaddr*)&server_addr, sizeof(server_addr));

    // 接收服务器发送的数据
    char buffer[1024] = {0};
    recv(client_fd, buffer, sizeof(buffer), 0);
    printf("Received from server: %s\n", buffer);

    // 关闭socket
    close(client_fd);

    return 0;
}

3.domain socket

Domain Socket,也称为UNIX Domain Sockets (UDS),是一种在同一台主机上的进程间通信(IPC)机制。不同于网络套接字使用TCP或UDP协议通过IP地址和端口号进行通信,Domain Socket使用特殊的文件系统来进行通信。以下是关于Domain Socket的一些详细说明:

  • 地址格式差异:与网络套接字使用的sockaddr结构体不同,Domain Socket使用的是sockaddr_un结构体来表示地址。这意味着在创建或连接到Domain Socket时,需要提供服务器的路径名而不是IP地址和端口号。
  • 通信效率:由于Domain Socket是在内核中进行的通信,它们通常比基于TCP/IP的网络套接字通信更高效。这是因为数据传输不需要经过网络协议栈的处理,从而减少了通信延迟和CPU使用率。
  • 稳定性和可靠性:Domain Socket通信不受网络波动的影响,因此在本地通信中具有更高的稳定性和可靠性。
  • 客户端设置:在客户端方面,调用socket函数时,只需要提供服务器的地址(通常是文件系统中的路径)以及一个事先约定好的端口号(例如6510)。客户端的端口号通常由系统自动分配,而IP地址可以指定也可以不指定,如果不指定,则使用默认值。

综上 ,Domain Socket作为一种高效的本地通信方式,它不需要指定IP地址和端口号,而是通过文件系统路径来进行识别和访问。这种通信方式在本地进程间传递信息时提供了一种快速且稳定的方法。

在这种通信方式中,一个进程创建一个Unix域套接字,并将其绑定到一个文件系统中的路径。这个路径就像一个特殊的文件,可以作为通信的端点。其他进程可以通过打开这个特殊文件来连接到该套接字,从而实现进程间的通信。

Domain Socket通信是在本地进行的,所以无法通过网络抓包工具(如Wireshark)在网络接口卡上捕获到相关数据。如果需要查看或分析Domain Socket通信数据,可以使用其他方法,如在程序中添加日志记录功能,或者使用操作系统提供的工具来监控文件系统的变化

sockaddr_un与sockaddr_in

  1. sockaddr_un:这是一个用于表示本地通信(即在同一台机器上的进程间通信)的地址结构。它主要用于Unix域套接字(Unix Domain Sockets)。sockaddr_un结构体包含一个长度和一个路径名。长度字段表示路径名的长度,路径名则是一个文件系统路径,用于标识特定的Unix域套接字。

  2. sockaddr_in:这是一个用于表示Internet协议(IPv4)的网络地址的结构。它包含了一个网络字节序的32位整数(用于存储IP地址),一个网络字节序的16位整数(用于存储端口号),以及其他一些字段。

以下是这两个结构的C语言定义:

struct sockaddr_un {
    sa_family_t sun_family; /* AF_UNIX */
    char        sun_path[108]; /* Pathname */
};

struct sockaddr_in {
    short            sin_family;   /* e.g. AF_INET */
    unsigned short   sin_port;     /* e.g. htons(3490) */
    struct in_addr   sin_addr;     /* see struct in_addr, below */
    char             sin_zero[8];  /* zero this if you want to */
};

在创建套接字并进行连接或绑定操作时,可能需要使用这些结构。例如,可以创建一个sockaddr_in结构并将其传递给bind()函数,以便将套接字绑定到特定的IP地址和端口号。同样,你也可以创建一个sockaddr_un结构并将其传递给connect()bind()函数,以便进行本地通信。

服务端

#include 
#include 
#include 
#include 
#include 

int main() {
    struct sockaddr_un server_address;
    int server_fd;
    char buffer[1024];

    server_fd = socket(AF_UNIX, SOCK_STREAM, 0);
    if (server_fd == -1) {
        perror("socket error");
        exit(EXIT_FAILURE);
    }

    server_address.sun_family = AF_UNIX;
    strcpy(server_address.sun_path, "/tmp/domain_socket");

    if (bind(server_fd, (struct sockaddr*)&server_address, sizeof(struct sockaddr_un)) == -1) {
        perror("bind error");
        exit(EXIT_FAILURE);
    }

    if (listen(server_fd, 5) == -1) {
        perror("listen error");
        exit(EXIT_FAILURE);
    }

    int client_fd;
    struct sockaddr_un client_address;
    socklen_t client_len = sizeof(client_address);

    client_fd = accept(server_fd, (struct sockaddr*)&client_address, &client_len);
    if (client_fd == -1) {
        perror("accept error");
        exit(EXIT_FAILURE);
    }

    read(client_fd, buffer, 1024);
    printf("Received: %s\n", buffer);

    close(client_fd);
    close(server_fd);
    unlink("/tmp/domain_socket");

    return 0;
}

 客户端

#include 
#include 
#include 
#include 
#include 

int main() {
    struct sockaddr_un server_address;
    int client_fd;

    client_fd = socket(AF_UNIX, SOCK_STREAM, 0);
    if (client_fd == -1) {
        perror("socket error");
        exit(EXIT_FAILURE);
    }

    server_address.sun_family = AF_UNIX;
    strcpy(server_address.sun_path, "/tmp/domain_socket");

    if (connect(client_fd, (struct sockaddr*)&server_address, sizeof(struct sockaddr_un)) == -1) {
        perror("connect error");
        exit(EXIT_FAILURE);
    }

    char *message = "Hello from client";
    write(client_fd, message, strlen(message));

    close(client_fd);

    return 0;
}

你可能感兴趣的:(IPC,socket,进程间通信)