自学内容网 自学内容网

C++ Linux多进程Socket通信

原文链接:C++ Linux多进程Socket通信

接口函数

套接字由于本身是为计算机网络服务的,因此相比于一般的通信方式,套接字会更复杂.

socket创建与连接

// 创建套接字
int socket(int domain, int type, int protocol);
    return fd ID(成功) -1(错误)
    domain协议族 AF_INET(IPV4) AF_INET6(IPV6) AF_UNIX AF_LOCAL 用于本机通信
    type类型 SOCK_STREAM(TCP) SOCK_DGRAM(UDP) SOCK_RAW(原始套接字)
    protocol协议  0为自动选择 有IPPROTO_TCP和IPPROTO_UDP

// 绑定计算机实际的端口
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
    return 0(成功) -1(错误)
    sockfd: socket返回的文件描述符
    addr: IP的地址结构体(下面详细介绍)
    addrlen: 由于不同的协议地址长度不同,需要指定

// 监听
int listen(int sockfd, int backlog);
    return 0(成功) -1(错误)
    backlog为最大连接数

// 客户端请求连接(仅针对有连接服务)
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
    return 0(成功) -1(错误)
    sockfd: 客户端的socket
    addr,addrlen:服务器IP和大小
    
// 接受连接(服务器接收客户端连接)
int accept(int sock, struct sockaddr *addr, socklen_t *addrlen);
    return 与客户端通信的fd(成功) -1(错误)
    sock: 服务器的socket
    addr,addrlen:请求连接的客户端IP和大小

地址创建与转换

套接字的IP和端口需要进行处理后才能传入

由于计算机本身字节序和网络字节序不一定一致,需要进行处理转换,都是大端不需要转,而小端字节序需要和网络字节序相互转换.

IP:PORT 以IPV4为例 每个数字1字节0-255,4个字段需要32位为无符号int,端口0-65535 16位2字节,都按照网络字节序存放.

V6由于地址更长有128位,因此结构与v4不同,并且有些服务可能没有考虑到v6用户的支持,某些服务v6会存在问题.

#include <arpa/inet.h>

IPV4的地址结构体:
struct sockaddr_in {
    short int          sin_family;  // 地址族,通常为 AF_INET 或v6
    unsigned short int sin_port;    // 端口号,网络字节序
    struct in_addr     sin_addr;    // IP 地址
};
// `struct in_addr` 定义如下:
struct in_addr {
    uint32_t s_addr;  // IP 地址,网络字节序,32位无符号,4字节,每个字节0-255
};

//IPV4字符串转32位网络字节序IP
in_addr_t inet_addr(const char *cp);
   return 网络字节序地址(成功) INADDR_NONE(错误)






V6由于地址更长有128,因此结构与v4不同
struct sockaddr_in6
// 端口转换 
//主机转网络 host to network short
uint16_t htons(uint16_t hostshort); 
   return 大端序端口号(成功)
// 网络转主机
uint16_t ntohs(uint16_t netshort);
   return 主机端口号(成功)


// 地址转换(包括IP)
// 主机 转 网络
int inet_pton(int af, const char *src, void *dst);
    return 成功则为1,若输入不是有效的表达式则为0,若出错则为-1

// 网络 转 主机
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
    return 字符指针(成功) NULL(错误)
    af: 协议族AF_INET 或AF_INET6
    src,dst: 原IP字符串和目的字符串
    size: IP长度大小

通信接口

连接建立后可以相互发送数据,socket提供3种通信方法

注意一点,由于socket网络建立连接后本质还是通过IO读写的,可以通过fcntl控制阻塞和非阻塞特性

  1. 传统文件描述符通信:和读写文件一样流式读写
ssize_t write(int fd, const void *buf, size_t nbytes);
    return n(字节数) -1(错误)
ssize_t read(int fd, void *buf, size_t nbytes);
    return n(字节数) -1(错误)
  1. 更安全高效的接口通信:linux提供了结构体形式发生消息,更高效稳定安全

send和recv相比文件读写,可以参数控制并且会受协议影响实际工作方式
ssize_t send(int sockfd, const void buf[.len], size_t len, int flags);
    return n(字节数) -1(错误)
ssize_t recv(int sockfd, void buf[.len], size_t len,int flags);
    return n(字节数) -1(错误)


支持更复杂的消息处理和附加控制信息
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
    return n(字节数) -1(错误)
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
    return n(字节数) -1(错误)

flags 标志:
    0:默认标志,表示普通的数据发送操作。
    MSG_OOB:发送带外数据,通常用于紧急数据。
    MSG_DONTROUTE:绕过路由表,直接发送数据到目标主机。
    MSG_NOSIGNAL:不产生信号(在某些系统中),用于避免在发送数据时中断进程。



其中消息结构体为:
struct msghdr {
    void         *msg_name;       /* 地址 */
    socklen_t     msg_namelen;    /* 地址长度 */
    struct iovec *msg_iov;        /* 指向 iovec 结构体的指针 */
    size_t        msg_iovlen;     /* iovec 结构体的数量 */
    void         *msg_control;    /* 指向控制信息的指针 */
    size_t        msg_controllen; /* 控制信息的长度 */
    int           msg_flags;      /* 消息标志 */
};
数据指针结构:
struct iovec {
    void  *iov_base;  /* 数据缓冲区的起始地址 */
    size_t iov_len;   /* 数据缓冲区的长度 */
};

  1. 面向无连接的UDP通信:使用下面两个接口
ssize_t sendto(int sockfd, const void buf[.len], size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);
ssize_t recvfrom(int sockfd, void buf[restrict .len], size_t len,int flags,struct sockaddr *_Nullable restrict src_addr,socklen_t *_Nullable restrict addrlen);

UDP存在数据丢失可能,但是客户端加入多播组,可以实现多播,适合视频流等

实例

使用tcp send/recv阻塞通信

服务器监听,客户端连接,之后相互发送一个消息

server

#include<iostream>
#include<string>
#include<cstring>
#include<sys/socket.h>
// #include<sys/types.h>
#include<unistd.h>
#include<arpa/inet.h>

using namespace std;


int main(){

    int socket_fd;
    string server_ip="127.0.0.1";
    uint16_t server_port=8001;

    socket_fd=socket(AF_INET,SOCK_STREAM,0);
    if(socket_fd==-1){
        perror("socket");
        exit(EXIT_FAILURE);
    }

    struct sockaddr_in server_addr;
    server_addr.sin_family=AF_INET;
    server_addr.sin_port=htons(server_port);
    inet_pton(AF_INET,server_ip.c_str(),&server_addr.sin_addr.s_addr);

    if(bind(socket_fd,(const struct sockaddr*)&server_addr,sizeof(server_addr))==-1){
        perror("bind");
        exit(EXIT_FAILURE);
    }

    if(listen(socket_fd,10)==-1){
        perror("listen");
        exit(EXIT_FAILURE);
    }

    cout<<"[server]("<<server_ip<<":"<<server_port<<"): start listening"<<"\n";

    struct sockaddr_in client_addr;
    char client_ip[INET_ADDRSTRLEN];
    uint16_t client_port;

    socklen_t clientaddr_len=sizeof(client_addr);
    int client_fd;
    client_fd=accept(socket_fd,(sockaddr*)&client_addr,&clientaddr_len);
    if(client_fd<0){
        perror("accept");
        exit(EXIT_FAILURE);
    }

    inet_ntop(AF_INET,&client_addr.sin_addr,client_ip,INET_ADDRSTRLEN);
    client_port=ntohs(client_addr.sin_port);

    cout<<"[server] connect with: ("<<client_ip<<":"<<client_port<<")"<<"\n";

    int buf_size=100;
    char buf[buf_size];
    ssize_t size;
    size=recv(client_fd,buf,buf_size,0);
    if(size<0){
        perror("recv");
    }else if(size==0){
        cout<<"connection closed"<<"\n";
    }else{
        cout<<"[server] recv "<<size<<": ("<<client_ip<<":"<<client_port<<"):"<<buf<<"\n";
    }
    memcpy(buf,"this is server",sizeof("this is server"));
    size=send(client_fd,buf,buf_size,0);
    if(size<-1){
        cout<<"send failed"<<"\n";
    }else if(size==0){
        cout<<"connection closed"<<"\n";
    }else{
        cout<<"[server] send to: ("<<client_ip<<":"<<client_port<<"):"<<buf<<"\n";
    }


    close(socket_fd);

    return 0;
}

client

#include<iostream>
#include<string>
#include<cstring>
#include<sys/socket.h>
#include<unistd.h>
#include<arpa/inet.h>

using namespace std;


int main(){

    int socket_fd;
    string server_ip="127.0.0.1";
    uint16_t server_port=8001;

    socket_fd=socket(AF_INET,SOCK_STREAM,0);
    if(socket_fd==-1){
        perror("socket");
        exit(EXIT_FAILURE);
    }

    struct sockaddr_in server_addr;
    server_addr.sin_family=AF_INET;
    server_addr.sin_port=htons(server_port);
    inet_pton(AF_INET,server_ip.c_str(),&server_addr.sin_addr.s_addr);


    if(connect(socket_fd,(sockaddr*)&server_addr,sizeof(server_addr))==-1){
        perror("connect");
        exit(EXIT_FAILURE);
    }
    int buf_size=100;
    char buf[buf_size]="this is client\0";
    ssize_t size;
    size=send(socket_fd,buf,buf_size,0);
    if(size<-1){
        cout<<"send failed"<<"\n";
    }else if(size==0){
        cout<<"connection closed"<<"\n";
    }else{
        cout<<"[client] send to: ("<<server_ip<<":"<<server_port<<"):"<<buf<<"\n";
    }

    size=recv(socket_fd,buf,buf_size,0);
    if(size<0){
        perror("recv");
    }else if(size==0){
        cout<<"connection closed"<<"\n";
    }else{
        cout<<"[client] recv "<<size<<": ("<<server_ip<<":"<<server_port<<"):"<<buf<<"\n";
    }

    close(socket_fd);

    return 0;
}
[server](127.0.0.1:8001): start listening
[server] connect with: (127.0.0.1:47072)
[server] recv 100: (127.0.0.1:47072):this is client
[server] send to: (127.0.0.1:47072):this is server

[client] send to: (127.0.0.1:8001):this is client
[client] recv 100: (127.0.0.1:8001):this is server

使用tcp 非阻塞sendmsg/recvmsg 非阻塞处理通信

客户端与服务器连接,仅服务器设置非阻塞模式,客户端等两秒发消息,服务器会轮询等待,不阻塞.
然后服务器等2秒发消息,客户端没设置依旧阻塞到消息到达.

可以由此说明服务器和客户端得到的两个描述符fd是两个不同的描述符,类似于两个双向管道,设置其中一个不影响另外一个.

msghdr对数据的封装有几个优点:

  1. iovec能够支持多个不同缓冲区数据的发送接收,这个相比其他接口有非常高的性能优势,因为不要求数据逻辑上连续了
  2. 能发送控制信息,例如文件描述符对象
  3. 可以使用flag标记操作,如MSG_OOB标记该消息为紧急发送的消息

iovec多缓冲区 可以利用数组形式表示iovec iov[n];msg.msg_iov = iov;

server

#include<iostream>
#include<string>
#include<cstring>
#include<sys/socket.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<fcntl.h>

using namespace std;


int main(){

    int socket_fd;
    string server_ip="127.0.0.1";
    uint16_t server_port=8001;

    socket_fd=socket(AF_INET,SOCK_STREAM,0);
    if(socket_fd==-1){
        perror("socket");
        exit(EXIT_FAILURE);
    }

    struct sockaddr_in server_addr;
    server_addr.sin_family=AF_INET;
    server_addr.sin_port=htons(server_port);
    inet_pton(AF_INET,server_ip.c_str(),&server_addr.sin_addr.s_addr);

    if(bind(socket_fd,(const struct sockaddr*)&server_addr,sizeof(server_addr))==-1){
        perror("bind");
        exit(EXIT_FAILURE);
    }

    if(listen(socket_fd,10)==-1){
        perror("listen");
        exit(EXIT_FAILURE);
    }

    cout<<"[server]("<<server_ip<<":"<<server_port<<"): start listening"<<"\n";

    struct sockaddr_in client_addr;
    char client_ip[INET_ADDRSTRLEN];
    uint16_t client_port;

    socklen_t clientaddr_len=sizeof(client_addr);
    int client_fd;
    client_fd=accept(socket_fd,(sockaddr*)&client_addr,&clientaddr_len);
    if(client_fd<0){
        perror("accept");
        exit(EXIT_FAILURE);
    }

    inet_ntop(AF_INET,&client_addr.sin_addr,client_ip,INET_ADDRSTRLEN);
    client_port=ntohs(client_addr.sin_port);

    cout<<"[server] connect with: ("<<client_ip<<":"<<client_port<<")"<<"\n";

    int label;
    label=fcntl(client_fd,F_GETFL);
    if(label==-1){
        cout<<"fcntl F_GETFL error\n";
    }
    cout<<"[server] communicate is no block?:"<<(bool)(O_NONBLOCK&label)<<"\n";
    
    label|=O_NONBLOCK;
    fcntl(client_fd,F_SETFL,label);
    if(label==-1){
        cout<<"fcntl F_GETFL error\n";
    }
    cout<<"[server] setting no block correct?:"<<(bool)(O_NONBLOCK&label)<<"\n";



    int buf_size=100;
    char buf[buf_size];
    ssize_t size;

    struct msghdr msg;
    struct iovec iov;
    iov.iov_base=buf;
    iov.iov_len=buf_size;
    msg.msg_iov=&iov;
    msg.msg_iovlen=1;
    msg.msg_name=&client_addr;
    msg.msg_namelen=clientaddr_len;

    while(true){
        size=recvmsg(client_fd,&msg,0);
        if(size<0){
            cout<<"[server] Resource temporarily unavailable, retry after 1s"<<"\n";
            sleep(1);
        }else if(size==0){
            cout<<"connection closed"<<"\n";
            break;
        }else{
            cout<<"[server] recv "<<size<<": ("<<client_ip<<":"<<client_port<<"):"<<buf<<"\n";
            break; // break to send msg if recv success   
        }
    }

    cout<<"sleep 2s"<<"\n";
    sleep(2);

    memcpy(buf,"this is server",sizeof("this is server"));
    while(true){
        size=sendmsg(client_fd,&msg,0);
        if(size<-1){
            cout<<"send failed"<<"\n";
            sleep(1);
        }else if(size==0){
            cout<<"connection closed"<<"\n";
            break;
        }else{
            cout<<"[server] send to: ("<<client_ip<<":"<<client_port<<"):"<<buf<<"\n";
            break; // send success should break from loop 
        }
    }


    close(socket_fd);

    return 0;
}

client

#include<iostream>
#include<string>
#include<cstring>
#include<sys/socket.h>
#include<unistd.h>
#include<arpa/inet.h>

using namespace std;


int main(){

    int socket_fd;
    string server_ip="127.0.0.1";
    uint16_t server_port=8001;

    socket_fd=socket(AF_INET,SOCK_STREAM,0);
    if(socket_fd==-1){
        perror("socket");
        exit(EXIT_FAILURE);
    }

    struct sockaddr_in server_addr;
    server_addr.sin_family=AF_INET;
    server_addr.sin_port=htons(server_port);
    inet_pton(AF_INET,server_ip.c_str(),&server_addr.sin_addr.s_addr);


    if(connect(socket_fd,(sockaddr*)&server_addr,sizeof(server_addr))==-1){
        perror("connect");
        exit(EXIT_FAILURE);
    }
    
    
    int buf_size=100;
    char buf[buf_size]="this is client\0";
    ssize_t size;

    struct msghdr msg;
    struct iovec iov;
    iov.iov_base=buf;
    iov.iov_len=buf_size;
    msg.msg_iov=&iov;
    msg.msg_iovlen=1;
    msg.msg_name=&server_addr;
    msg.msg_namelen=(socklen_t)sizeof(server_addr);

    cout<<"sleep 2s"<<"\n";
    sleep(2);

    while(true){
        size=sendmsg(socket_fd,&msg,0);
        if(size<-1){
            cout<<"send failed, retry"<<"\n";
            sleep(1);
            continue; 
        }else if(size==0){
            cout<<"connection closed"<<"\n";
            break;
        }else{
            cout<<"[client] send to: ("<<server_ip<<":"<<server_port<<"):"<<buf<<"\n";
            break; // send success should break from loop 
        }
    }

    while(true){
        size=recvmsg(socket_fd,&msg,0);
        if(size<0){
            cout<<"[client] Resource temporarily unavailable, retry after 1s"<<"\n";
            sleep(1);
        }else if(size==0){
            cout<<"connection closed"<<"\n";
            break;
        }else{
            cout<<"[client] recv "<<size<<": ("<<server_ip<<":"<<server_port<<"):"<<buf<<"\n";
            break;
        }

    }
    close(socket_fd);

    return 0;
}

使用udp sendto/recvfrom广播消息

udp需要对文件描述符设置广播
int setsockopt( int socket, int level, int option_name,const void *option_value, size_t ption_len);

level=SOL_SOCKET option_name=SO_BROADCAST为广播设置 option_value!=0允许广播

并且UDP不需要进行连接

实现广播有几点要求:

  1. 服务器和客户端都需要通过setsockopt设置fd允许广播
  2. UDP不同于TCP,UDP服务器和客户端的配置行为差别较大,服务器不监听,客户端要绑定
  3. 服务器设置广播IP,客户端设置监听广播IP

感觉广播这里还是有点问题,后面深入再改

server

#include<iostream>
#include<string>
#include<cstring>
#include<sys/socket.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<fcntl.h>

using namespace std;


int main(){

    int socket_fd;
    uint16_t server_port=8001;


    socket_fd=socket(AF_INET,SOCK_DGRAM,0);
    if(socket_fd==-1){
        perror("socket");
        exit(EXIT_FAILURE);
    }

    int broadcast_enable=1;
    if(setsockopt(socket_fd,SOL_SOCKET,SO_BROADCAST,&broadcast_enable,sizeof(broadcast_enable))<0){
        perror("setsockopt");
        exit(EXIT_FAILURE);
    }


    struct sockaddr_in broadcast_addr;
    broadcast_addr.sin_family=AF_INET;
    broadcast_addr.sin_addr.s_addr=htonl(INADDR_BROADCAST);
    broadcast_addr.sin_port=htons(server_port);

    
    int buf_size=100;
    char buf[buf_size]="this is broadcast msg\0";
    ssize_t size;

    if((size=sendto(socket_fd,buf,buf_size,0,(struct sockaddr*)&broadcast_addr,sizeof(broadcast_addr)))<0){
        perror("sendto");
    }else{
        cout<<"[server] broadcast: [size="<<size<<"] send:"<<buf<<"\n";
    }

    close(socket_fd);

    return 0;
}

client

#include<iostream>
#include<string>
#include<cstring>
#include<sys/socket.h>
#include<unistd.h>
#include<arpa/inet.h>

using namespace std;


int main(){

    int socket_fd;
    uint16_t recv_port=8001;
    string recv_ip="255.255.255.255";

    socket_fd=socket(AF_INET,SOCK_DGRAM,0);
    if(socket_fd==-1){
        perror("socket");
        exit(EXIT_FAILURE);
    }

    int broadcast_enable=1;
    if(setsockopt(socket_fd,SOL_SOCKET,SO_BROADCAST,&broadcast_enable,sizeof(broadcast_enable))<0){
        perror("setsockopt");
        exit(EXIT_FAILURE);
    }


    struct sockaddr_in recv_addr;
    socklen_t addrlen=sizeof(recv_addr);
    recv_addr.sin_family=AF_INET;
    recv_addr.sin_port=htons(recv_port);
    inet_pton(AF_INET,recv_ip.c_str(),&recv_addr.sin_addr.s_addr);
    // recv_addr.sin_addr.s_addr=htonl(INADDR_ANY);

    if (bind(socket_fd, (struct sockaddr *)&recv_addr, sizeof(recv_addr)) < 0) {
        perror("bind");
        close(socket_fd);
        exit(EXIT_FAILURE);
    }
    
    int buf_size=100;
    char buf[buf_size];
    ssize_t size;

    if((size=(recvfrom(socket_fd,buf,buf_size,0,(struct sockaddr *)&recv_addr,&addrlen)))<0){
        perror("recvfrom");
    }else{
        cout<<"[client] recv: [size="<<size<<"] send:"<<buf<<"\n";
    }

    close(socket_fd);

    return 0;
}

原文地址:https://blog.csdn.net/qq_58539881/article/details/142445261

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!