0%

C++ Linux多进程Socket通信

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

接口函数

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

socket创建与连接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 创建套接字
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会存在问题.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#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. 传统文件描述符通信:和读写文件一样流式读写

    1
    2
    3
    4
    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(错误)
  2. 更安全高效的接口通信:linux提供了结构体形式发生消息,更高效稳定安全

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38

    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; /* 数据缓冲区的长度 */
    };

  3. 面向无连接的UDP通信:使用下面两个接口

    1
    2
    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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
#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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#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;
}
1
2
3
4
5
6
7
[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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
#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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
#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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#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;
}