原文链接: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控制阻塞和非阻塞特性
- 传统文件描述符通信:和读写文件一样流式读写
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提供了结构体形式发生消息,更高效稳定安全
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; /* 数据缓冲区的长度 */
};
- 面向无连接的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对数据的封装有几个优点:
- iovec能够支持多个不同缓冲区数据的发送接收,这个相比其他接口有非常高的性能优势,因为不要求数据逻辑上连续了
- 能发送控制信息,例如文件描述符对象
- 可以使用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不需要进行连接
实现广播有几点要求:
- 服务器和客户端都需要通过setsockopt设置fd允许广播
- UDP不同于TCP,UDP服务器和客户端的配置行为差别较大,服务器不监听,客户端要绑定
- 服务器设置广播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;
}