unix系统之套接字通信

概述

socket(套接字)通信主要用于运行在不同服务器上的进程之间通信(服务器通过网络相连),也可以用于在同一服务器上的进程之间通信。套接字就相当于端与端之间的独立的门,通信就通过这个门来进行。

创建套接字

创建成功返回一个套接字描述符,基本上可以把它看成是一个文件描述符,很多文件描述符的api,对套接字描述符也是适用的。

#include <sys/socket.h>

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

domain

决定了通信的性质,这些性质包括地址格式等,一般有以下值:

  • AF_INET:IPv4 因特网域。
    AF_INET6:IPv6 因特网域。
  • AF_UNIX: UNIX 域。大多系统还定义了AF_LOCAL域,它是AF_UNIX的别名。
  • AF_UNSPEC: 未指定,这是一个可以表达任何域的通配符。

type

参数的类型,一般有以下:

  • SOCK_DGRAM:固定长度,无连接,不可靠的消息。
  • **SOCK_RAW: **IP 协议的数据报接口。(在 POSIX.1 中是可选的)
  • SOCK_SEQPACKET: 固定长度,顺序式,面向连接的,可靠的消息。
  • SOCK_STREAM: 顺序式,面向连接的,双向的,可靠的字节流。

protocol

通常是0,表示为给定的域和套接字类型的组合选择默认协议。当一对域和套接字类型支持多个协议时,可用protocol参数选择一个特定协议。AF_INET通信域和SOCK_STREAM套接字类型的默认协议是TCP。AF_INET通信域和SOCK_DGRAM套接字类型的默认协议是UDP。以下是protocol参数的可选值:

  • **IPPROTO_IP :**IPv4网络协议
  • **IPPROTO_IPV6 :**IPv6网络协议
  • **IPPROTO_ICMP :**因特网控制报文协议
  • **IPPROTO_RAW: **原始数据包协议
  • **IPPROTO_TCP: **传输控制协议
  • **IPPROTO_UDP: **用户数据报协议

绑定网络域

当通过socket函数获得套接字描述符后,对于服务端来讲,首先就是和一个具体的网络地址绑定,这个套接字一般称为监听套接字,其函数为:

#include <sys/socket.h>

int bind(int sockfd, const struct sockaddr *addr, socklen_t len);
  • sockfd:套接字描述符
  • addr:绑定的网络域
  • **len:**addr的长度

sockaddr

一个const struct sockaddr *指针, 绑定套接字的网络域,不同的网络域具有不同的地址格式,sockaddr 是地址的通用结构体:

struct sockaddr {
		sa_family_t		sa_family;/* address family */
		char			sa_data[];/* variable-length address */
		.
		.
		.
	};

这个地址结构根据地址创建socket时的地址协议族的不同而不同,如ipv4对应的是:

struct sockaddr_in {
    sa_family_t    sin_family; /* address family: AF_INET */
    in_port_t      sin_port;   /* port in network byte order */
    struct in_addr sin_addr;   /* internet address */
};

/* Internet address. */
struct in_addr {
    uint32_t       s_addr;     /* address in network byte order */
};

如ipv6对应的是:

struct sockaddr_in6 { 
    sa_family_t     sin6_family;   /* AF_INET6 */ 
    in_port_t       sin6_port;     /* port number */ 
    uint32_t        sin6_flowinfo; /* IPv6 flow information */ 
    struct in6_addr sin6_addr;     /* IPv6 address */ 
    uint32_t        sin6_scope_id; /* Scope ID (new in 2.4) */ 
};

struct in6_addr { 
    unsigned char   s6_addr[16];   /* IPv6 address */ 
};

如unix对应的是:

#define UNIX_PATH_MAX    108

struct sockaddr_un { 
    sa_family_t sun_family;               /* AF_UNIX */ 
    char        sun_path[UNIX_PATH_MAX];  /* pathname */ 
};

监听套接字

服务端针对面向连接的套接字进行监听,保证连接的有效性。服务端的监听函数如下:


int listen(int sockfd, int backlog);

backlog 参数表示能够排队最大连接请求数量,但实际最大的值的上限由系统决定,毕竟不可能设置一个超越系统配置的数量。
很明显,这个参数表明了内核需要维护一个存储与客户端连接关系的队列。

接受请求

服务端通过监听函数可以发现那些发起连接请求的客户端,而accept函数是去为这些客户端建立起真正的连接关系,函数如下:

int accept(int sockfd, struct sockaddr *restrict addr,socklen_t *restrict len);

accept默认会阻塞进程,直到有一个客户连接建立后返回,若函数成功返回,返回值是一个由内核自动生成的全新的套接字,代表与客户端的连接。也就是说,服务端每有新的客户端连接都会新建一个套接字。

连接网络域

当通过socket函数获得套接字描述符后,对于客户端来讲,就是可以与服务端具体的网络域进行连接了,函数如下:

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

发送消息


ssize_t send(int sockfd, const void *buf, size_t nbytes, int flags)
    
ssize_t sendto(int sockfd, const void *buf, size t nbytes, int flags,
               const struct sockaddr *destaddr, socklen t destlen);

ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);

flag: 0 阻塞、1 不阻塞

接收消息

ssize_t recv(int sockfd, void *buf, size t nbytes, int flags):

ssize_t recvfrom(int sockfd, void *restrict buf, size t len, int flags,
                 struct sockaddr *restrict addr,socklen t *restrict addrlen);

ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);

flag: 0 阻塞、1 不阻塞

关闭套接字的连接

int shutdown (int sockfd, int how);

how是指关闭的方式:

  • SHUT_RD: 关闭读。
  • SHUT_WR: 关闭写。
  • SHUT_RDWR: 关闭读写。

例子

因为基于SOCK_DGRAM类型的套接字比较简单,所以直接搞个SOCK_STREAM的一步到位。
服务端建立一个面向连接的套接字,去接收多个客户端的消息。客户端输入"end"结束连接。

服务端

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <string.h>
#include <arpa/inet.h>

int main(int argc, char *argv[])
{
    struct sockaddr_in clientaddr, serveraddr;
    int sockfd;
    int connectSockfd;
    int addrlen;
    pid_t pid;

    // 创建socket文件描述符用于处理监听
    if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
    {
        perror("socket create failed!");
        exit(1);
    }

    // 初始化服务端的网络域,包括地址族,端口,和IP地址
    bzero(&serveraddr, sizeof(serveraddr));
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_port = htons(atoi(argv[2]));
    inet_pton(AF_INET, argv[1], &serveraddr.sin_addr);

    // 将sockfd文件描述符和服务器地址绑定
    if ((bind(sockfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr))) < 0)
    {
        perror("bind error");
        close(sockfd);
        exit(1);
    }

    // 监听,设置最大连接为1,测试超过1时会发生什么
    if (listen(sockfd, 1) < 0)
    {
        perror("listen error");
        close(sockfd);
        exit(1);
    }

    printf("waiting client connecting\n");

    while (1)
    {
        // 处理客户端连接,返回一个与客户端相关的套接字
        addrlen = sizeof(clientaddr);
        if ((connectSockfd = accept(sockfd, (struct sockaddr *)&clientaddr, &addrlen)) < 0)
        {
            perror("accept error");
            continue;
        }

        if ((pid = fork()) < 0)
        {
            perror("listen error");
            close(sockfd);
            close(connectSockfd);
            exit(1);
        }
        // 父进程关连接fd,继续accept新的连接
        else if (pid > 0)
        {
            close(connectSockfd);
        }
        // 子进程关绑定fd,并处理当前连接fd的逻辑
        else
        {
            char buf[1024];
            char dst[128];
            close(sockfd);
            while (1)
            {
                memset(buf, 0, sizeof(buf));

                if ((recv(connectSockfd, buf, sizeof(buf), 0)) < 0)
                {
                    perror("recv error");
                    break;
                }
                printf("*-------------------------------------*");
                printf("---- from ip:%s\tport:%d ------\n", inet_ntop(AF_INET, &clientaddr.sin_addr.s_addr, dst, sizeof(dst)), ntohs(clientaddr.sin_port));
                if ((write(STDOUT_FILENO, buf, strlen(buf))) < 0)
                {
                    perror("write error");
                    break;
                }

                printf("*--------- msg recv success ------------*");

                // 发送消息
                if ((send(connectSockfd, "recv ok", 8, 0)) < 0)
                {
                    perror("send error");
                    break;
                }
                printf("*-------------------------------------*\n\n\n\n");
            }
            // 关闭用于监听的文件描述符
            close(connectSockfd);
        }
    }
    // 关闭用于监听的文件描述符
    close(sockfd);

    return 0;
}

客户端

#include <stdio.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>

int main(int argc, char *argv[])
{
    struct sockaddr_in serveraddr;
    int sockfd;
    char buf[1024];
    char dst[128];

    // 创建用于连接服务器的sockfd
    if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
    {
        perror("socket create failed!");
        exit(1);
    }

    // 初始化服务器端的地址信息,主机序转换为网络序
    bzero(&serveraddr, sizeof(serveraddr));
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_port = htons(atoi(argv[2]));
    inet_pton(AF_INET, argv[1], &serveraddr.sin_addr);

    // 连接服务器
    if ((connect(sockfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr))) < 0)
    {
        perror("connect error");
        close(sockfd);
        exit(1);
    }

    while (1)
    {
        printf("*------- please enter message to send: --------*\n");
        memset(buf, 0, sizeof(buf));
        // 读取控制台输入
        if ((read(STDOUT_FILENO, buf, sizeof(buf))) < 0)
        {
            perror("read error");
            break;
        }

        // 输入end结束输入
        if (strncmp(buf, "end", 3) == 0)
        {
            break;
        }

        // 发送
        if ((send(sockfd, buf, strlen(buf), 0)) < 0)
        {
            perror("read error");
            break;
        }
        printf("*-------  msg send success! --------*\n\n\n");

        // 接收
        if ((recv(sockfd, buf, sizeof(buf), 0)) < 0)
        {
            perror("read error");
            break;
        }
        printf("------ from ip:%s\tport:%d  -----\n", inet_ntop(AF_INET, &serveraddr.sin_addr.s_addr, dst, sizeof(dst)), ntohs(serveraddr.sin_port));
        // 打印到控制台
        if ((write(STDOUT_FILENO, buf, strlen(buf))) < 0)
        {
            perror("read error");
            break;
        }
        char dst[128];
        printf("\n*---------  msg recv success !  -----------*\n\n\n");
    }

    // 释放连接的文件描述符
    close(sockfd);

    return 0;
}

其他

可使用tcpdump命令来监听两者之间的交互。

  • -i 指定网卡名称
  • host 指定host
  • port 指定端口
  • -vv 输出详细的报文信息
tcpdump -vv -i lo host 127.0.0.1
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
好的,下面是一个简单的 C++ 程序,用于在 UNIX 域上进行套接字通信: ```cpp #include <iostream> #include <sys/socket.h> #include <sys/un.h> #include <unistd.h> #include <cstring> const char* SOCKET_PATH = "/tmp/mysocket"; int main() { // 创建套接字 int sockfd = socket(AF_UNIX, SOCK_STREAM, 0); if (sockfd == -1) { std::cerr << "Failed to create socket" << std::endl; return 1; } // 绑定到 socket 地址 struct sockaddr_un addr; memset(&addr, 0, sizeof(addr)); addr.sun_family = AF_UNIX; strncpy(addr.sun_path, SOCKET_PATH, sizeof(addr.sun_path) - 1); if (bind(sockfd, (struct sockaddr*)&addr, sizeof(addr)) == -1) { std::cerr << "Failed to bind socket" << std::endl; close(sockfd); return 1; } // 监听连接请求 if (listen(sockfd, 5) == -1) { std::cerr << "Failed to listen on socket" << std::endl; close(sockfd); return 1; } // 等待客户端连接 int clientfd = accept(sockfd, nullptr, nullptr); if (clientfd == -1) { std::cerr << "Failed to accept client" << std::endl; close(sockfd); return 1; } // 读取客户端发送的数据 char buf[256]; int numBytes = read(clientfd, buf, sizeof(buf)); if (numBytes == -1) { std::cerr << "Failed to read from client" << std::endl; close(clientfd); close(sockfd); return 1; } // 打印接收到的数据 std::cout << "Received " << numBytes << " bytes: " << buf << std::endl; // 关闭套接字 close(clientfd); close(sockfd); return 0; } ``` 在这个程序中,我们首先使用 `socket` 函数创建了一个 UNIX套接字。然后,我们使用 `bind` 函数将套接字绑定到一个本地地址上,这里我们使用了 `/tmp/mysocket` 作为地址。接下来,我们使用 `listen` 函数开始监听连接请求。当有客户端连接时,使用 `accept` 函数接受连接请求,并返回一个新的套接字,可以使用这个套接字与客户端进行通信。在这个程序中,我们只是简单地从客户端读取数据,并将其打印到标准输出流中。最后,我们关闭套接字并退出程序。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

我叫小八

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值