原文链接: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; unsigned short int sin_port; struct in_addr sin_addr; }; struct in_addr { uint32_t s_addr; }; in_addr_t inet_addr (const char *cp) ; return 网络字节序地址(成功) INADDR_NONE (错误) V6由于地址更长有128 位,因此结构与v4不同 struct sockaddr_in6uint16_t htons (uint16_t hostshort); return 大端序端口号(成功) uint16_t ntohs (uint16_t netshort); return 主机端口号(成功) 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 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 (错误)
更安全高效的接口通信: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; size_t msg_iovlen; void *msg_control; size_t msg_controllen; int msg_flags; }; 数据指针结构: struct iovec { void *iov_base; size_t iov_len; };
面向无连接的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 <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对数据的封装有几个优点:
iovec能够支持多个不同缓冲区数据的发送接收,这个相比其他接口有非常高的性能优势,因为不要求数据逻辑上连续了
能发送控制信息,例如文件描述符对象
可以使用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 ; } } 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 ; } } 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 ; } } 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不需要进行连接
实现广播有几点要求:
服务器和客户端都需要通过setsockopt设置fd允许广播
UDP不同于TCP,UDP服务器和客户端的配置行为差别较大,服务器不监听,客户端要绑定
服务器设置广播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); 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 ; }