转接口的换操作主要分为三类:字节序转换操作、IP地址转换操作和主机名转换操作。
2.1.1 字节序转换操作- 网络序转主机序
| 函数 | 含义 | 作用 |
|---|---|---|
| ntohs() | network to host short | 把unsigned short类型从网络序转换到主机序 |
| ntohl() | network to host long | 把unsigned long类型从网络序转换到主机序 |
- 主机序转网络序
| 函数 | 含义 | 作用 |
|---|---|---|
| htons() | host to network short | 把unsigned short类型从主机序转换到网络序 |
| htonl() | host to network long | 把unsigned long类型从主机序转换到网络序 |
实例:
#include#include #include #include using namespace std; void Hex(int n){ char bytes[2]; memcpy(bytes,&n,2); cout << showbase << setw(2) << setfill('0') << hex << (int)bytes[0] << setw(2) << (int)bytes[1] << endl; } int main(){ uint16_t n; cin >> n; Hex(n); uint16_t m = htons(n); Hex(m); uint16_t t = ntohs(m); Hex(t); }
[root@localhost socket]# ./a.out 16 0x1000 000x10 0x10002.1.2 IP地址转换操作
| 函数 | 功能 | 特点 |
|---|---|---|
| int inet_aton(const char *string, struct in_addr*addr) | 点分十进制数串转网络字节序长整型 | IPv4专用 |
| in_addr_t inet_addr(const char* string) | 点分十进制数串转网络字节序长整型 | IPv4专用 |
| char* inet_ntoa(struct in_addr addr) | 网络字节序长整型转点分十进制数串 | IPv4专用 |
| int inet_pton(int af, const char *src, void *dst) | 点分十进制数串转网络字节序长整型 | IPv4/IPv6通用(推荐) |
| const char *inet_ntop(int af, const void *src, char *dst, socklen_t cnt) | 网络字节序长整型转点分十进制数串 | IPv4/IPv6通用(推荐) |
实例:
#include#include #include using namespace std; int main(){ string s; cin >> s; in_addr addr; inet_aton(s.c_str(),&addr); cout << hex << addr.s_addr << endl; cout << inet_ntoa(addr) << endl; cout << inet_addr(s.c_str()) << endl; }
[root@localhost socket]# ./a.out 192.168.0.1 100a8c0 192.168.0.1 100a8c0
- 结构体
| 结构体 | 功能 | 特性 |
|---|---|---|
| struct sockaddr | 套接字地址结构 | IPv4/IPv6通用 |
| struct sockaddr_in | IPv4套接字地址结构 | IPv4专用 |
| struct in_addr | IPv4地址结构 | IPv4专用 |
| in_addr_t | IPv4地址类型 | IPv4专用 |
| struct sockaddr_in6 | IPv6套接字地址结构 | IPv6专用 |
- 套接字地址结构
struct sockaddr {
unsigned short sa_family; // 套接字地址簇类型,为AF_INET
char sa_data[14]; // 套接字地址数据(14位的协议地址)
};
- IPv4套接字地址结构
struct sockaddr_in{
short sin_family; // 套接字地址簇类型,为AF_INET
unsigned short sin_port; // 端口号,网络字节序
struct in_addr sin_addr; // IP地址,网络字节序
unsigned char sin_zero[8];// 填充字节
};
sin_zero[8]用来保证结构体struct sockaddr_in的大小和结构体struct sockaddr的大小相等。
- IPv4地址结构
struct in_addr {
in_addr_t s_addr;
};
- IPv4地址类型
typedef unsigned int in_addr_t;
- IPv6套接字地址结构
struct sockaddr_in6{
uint8_t sin6_len; //IPv6 为固定的24 字节长度
sa_family_t sin6_family; //套接字地址簇类型,为AF_INET6
in_port_t sin6_port; //16 位端口号,网络字节序
uint32_t sin6_flowinfo; //32 位流标签
struct in6_addr sin6_addr; //128 位IP地址
}
2.1.3 主机名转换操作
| 函数 | 功能 |
|---|---|
| struct hostent *gethostbyname(const char *hostname) | 主机名转地址 |
| struct hostent *gethostbyaddr(const char * addr, int len, int type) | 地址转主机名 |
| struct hostent *gethostbyaddr(const char * addr, int len, int type) | 地址转主机名 |
- 主机名字和地址信息struct hostent
| 参数 | 含义 |
|---|---|
| h_name | 主机名字 |
| h_aliases | 以空指针结尾的主机别名队列 |
| h_addrtype | 地址类型。AF_INET/AF_INET6 |
| h_length | 地址长度。在AF_INET类型地址中为4 |
| h_addr | 第一个IP地址 |
| h_addr_list | 以空指针结尾的IP地址的列表 |
int socket(int domain, int type, int protocol)
- 参数
| 参数 | 含义 |
|---|---|
| domain | 协议域 AF_INET:IPv4;AF_INET6:IPv6;AF_LOCAL:Unix域 |
| type | 类型 SOCK_STREAM:流式套接字;SOCK_DGRAM:数据报套接字;SOCK_RAW:原始套接字 |
| protocol | 协议 0:自动根据type匹配协议;IPPROTO_TCP/IPPROTO_UDP |
- 返回值
| 返回值 | 含义 |
|---|---|
| -1 | 失败 |
| >0 | socket描述符 |
int close(int sockfd) int shutdown(int sockfd,int howto)
- 参数
| 参数 | 含义 |
|---|---|
| sockfd socket | 套接字 |
| howto | 关闭方式 |
- 关闭方式
| 方式 | 值 | 含义 |
|---|---|---|
| SHUT_RD | 0 | 关闭连接的读 |
| SHUT_WR | 1 | 关闭连接的写 |
| SHUT_RDWR | 2 | 连接的读和写都关闭 |
int setsockopt(int sockfd, int level, int optname,const void *optval, socklen_t optlen)
- 参数
| 参数 | 含义 |
|---|---|
| sockfd | 套接字描述符 |
| level | 选项层次 |
| optname | 选项 |
| optval | 选项值指针 |
| optlen | optval缓冲区长度 |
- 选项层次
| 参数 | 含义 |
|---|---|
| SOL_SOCKET | 通用套接字选项 |
| IPPROTO_TCP | TCP选项 |
| IPPROTO_IP | IP选项 |
| IPPROTO_IPV6 | IPv6选项 |
选项分为SOL_SOCKET级别和IPPROTO_IP级别两个级别
- 返回值
| 返回值 | 含义 |
|---|---|
| -1 | 失败 |
| 0 | 成功 |
int getsockopt(int sockfd, int level, int optname,void *optval, socklen_t *optlen)
- 参数
| 参数 | 含义 |
|---|---|
| sockfd | 套接字描述符 |
| level | 选项层次 |
| optname | 选项 |
| optval | 选项值指针 |
| optlen | optval缓冲区长度 |
- 返回值
| 返回值 | 含义 |
|---|---|
| -1 | 失败 |
| 0 | 成功 |
int bind(int socket, const struct sockaddr* address, socklen_t address_len)
| 参数 | 含义 |
|---|---|
| socket | 套接字描述符 |
| address | 地址和端口号 |
| address_len | address缓冲区的长度 |
- 返回值
| 返回值 | 含义 |
|---|---|
| 0 | 成功 |
| SOCKET_ERROR | 失败 |
int listen(int sockfd, int backlog)
- 参数
| 参数 | 含义 |
|---|---|
| sockfd | 监听的socket描述符 |
| backlog | 排队的最大连接个数 |
- 返回值
| 返回值 | 含义 |
|---|---|
| -1 | 失败 |
| 0 | 成功 |
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
- 参数
| 参数 | 含义 |
|---|---|
| sockfd | 客户端的socket描述字 |
| addr | 服务器的socket地址 |
| addrlen | 服务器的socket地址的长度 |
- 返回值
| 返回值 | 含义 |
|---|---|
| -1 | 失败 |
| 0 | 成功 |
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen)
- 参数
| 参数 | 含义 |
|---|---|
| sockfd | 服务器的socket描述符,监听socket描述符 |
| addr | 客户端的socket地址 |
| addrlen | 客户端的socket地址的长度 |
- 返回值
| 返回值 | 含义 |
|---|---|
| -1 | 失败 |
| 非-1 | 连接描述符 |
注:说明
如果不需要获取客户端的套接字地址,后两个参数可设置为NULL。
int connfd = accept(listenfd,NULL,NULL);
如果需要获取,则按照如下方式设置:
struct sockaddr_in remote_addr; bzero(&remote_addr,sizeof(remote_addr)); socklen_t remote_addr_len = sizeof(remote_addr); int connfd = accept(listenfd,(struct sockaddr*)&remote_addr,&remote_addr_len);2.2.9 发送
方式1:
ssize_t write(int fd, const void *buf, size_t len);
- 参数
| 参数 | 含义 |
|---|---|
| fd | 文件描述符 |
| buf | 写入数据 |
| len | 写入数据的长度 |
- 返回值
| 返回值 | 含义 |
|---|---|
| >0 | 实际所写的字节数 |
| <0 | 出错 |
方式2:
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
- 参数
| 参数 | 含义 |
|---|---|
| sockfd | sockfd文件描述符 |
| buf | 写入数据 |
| len | 写入数据的长度 |
| flags | 通常为0 |
- 返回值
| 返回值 | 含义 |
|---|---|
| >0 | 实际所写的字节数 |
| <0 | 出错 |
方式3:
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);
- 参数
| 参数 | 含义 |
|---|---|
| sockfd | sockfd文件描述符 |
| buf | 写入数据 |
| len | 写入数据的长度 |
| flags | 通常为0 |
| dest_addr | 目标socket地址 |
| addrlen | 目标socket地址长度 |
- 返回值
| 返回值 | 含义 |
|---|---|
| >0 | 实际所写的字节数 |
| <0 | 出错 |
方式1:
ssize_t read(int fd, void *buf, size_t len);
- 参数
| 参数 | 含义 |
|---|---|
| fd | 文件描述符 |
| buf | 读取数据 |
| len | 读取数据的长度 |
- 返回值
| 返回值 | 含义 |
|---|---|
| 0 | 读到文件的结束 |
| >0 | 实际所写的字节数 |
| <0 | 出错 |
方式2:
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
- 参数
| 参数 | 含义 |
|---|---|
| fd | 文件描述符 |
| buf | 读取数据 |
| len | 读取数据的长度 |
| flags | 通常为0 |
- 返回值
| 返回值 | 含义 |
|---|---|
| 0 | 读到文件的结束 |
| >0 | 实际所写的字节数 |
| <0 | 出错 |
方式3:
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen)
- 参数
| 参数 | 含义 |
|---|---|
| fd | 文件描述符 |
| buf | 读取数据 |
| len | 读取数据的长度 |
| flags | 通常为0 |
| dest_addr | 目标socket地址 |
| addrlen | 目标socket地址长度 |
- 返回值
| 返回值 | 含义 |
|---|---|
| 0 | 读到文件的结束 |
| >0 | 实际所写的字节数 |
| <0 | 出错 |
| 特殊地址 | ipv4 | ipv6 |
|---|---|---|
| 通配地址 | in_addr.sin_addr.s_addr = htonl(INADDR_ANY) | in6_addr.sin6_addr=in6addr_any |
| 回环地址 | in_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK) | in6_addr.sin6_addr=in6addr_loopback |
| 广播地址 | in_addr.sin_addr.s_addr = htonl(INADDR_BROADCAST) | 无广播 |
- 客户端/发送端
必须指定连接/发送的IP(广播地址、回环地址或者某个具体地址)。
必须指定连接/发送的port。 - 服务器/接受端
IP指定为通配地址、回环地址或者某个具体地址。
必须指定绑定监听/接受的port。
Linux查看网络连接状态指令
netstat :查看网络连接状态、socket端口打开状态
| 选项 | 作用 |
|---|---|
| -antp | 查看tcp的状态 |
| -anup | 查看udp的状态 |
| C/S | 函数 |
|---|---|
| Server | socket()、bind()、listen()、accept()、recv()/read()andsend()/write()、close() |
| Client | socket()、connect()、send()/write()andrecv()/read()、close() |
- 客户端流程:4个步骤
int main(){
//1.打开套接字
fd = socket();
//2.连接服务器
connect(fd,...);
//3.写入/读取数据
write(fd,...);
read(fd,...);
//4.关闭套接字
close(fd);
}
- 详细代码:
#include#include #include #include #include using namespace std; int main(){ //1.打开套接字 int fd = socket(AF_INET,SOCK_STREAM,0); if(-1 == fd){ perror("open socket error"); return 1; } //设置套接字 sockaddr_in addr; addr.sin_family = AF_INET; inet_aton("127.0.0.1",&addr.sin_addr); //点分十进制数串转网络字节序长整型,存入addr的IP地址 addr.sin_port = htons(8080);//addr的端口号 = 主机序转化为网络序 //2.连接服务器 int res = connect(fd,(sockaddr*)&addr,sizeof(addr)); if(-1 == res){ perror("connect error"); return 1; } //3.写入/读取数据 string s; cin >> s; write(fd,s.c_str(),s.size()+1); char buffer[256] = {0}; read(fd,buffer,256); cout << buffer << endl; //4.关闭套接字 close(fd); }
- 服务端流程:6个步骤
int main(){
//1.打开套接字
fd = socket();
//2.绑定
bind(fd,...);
//3.监听
listen(fd,backlog);
//4.连接客户端
connfd = accept(fd,...);
//5.读写数据
read(connfd);
write(connfd);
//6.关闭套接字
close(connfd);
close(fd);
}
- 详细代码:
#include#include #include #include #include using namespace std; int main(){ //1.打开套接字 int fd = socket(AF_INET,SOCK_STREAM,0); if(-1 == fd){ perror("open socket error"); return 1; } //编辑套接字信息 sockaddr_in addr; addr.sin_family = AF_INET; inet_aton("127.0.0.1",&addr.sin_addr); //点分十进制数串转网络字节序长整型,存入addr的IP地址 addr.sin_port = htons(8080);//addr的端口号 = 主机序转化为网络序 //2.绑定 int res = bind(fd,(sockaddr*)&addr,sizeof(addr)); if(-1 == res){ perror("bind error"); return 1; } //3.监听 res = listen(fd,4); if(-1 == res){ perror("listen error"); return 1; } //4.连接客户端 int connfd = accept(fd,NULL,NULL); if(-1 == connfd){ perror("accept error"); return 1; } //5.读写数据 char buffer[256] = {0}; read(connfd,buffer,256); cout << buffer << endl; string s; cin >> s; write(connfd,s.c_str(),s.size()+1); //6.关闭套接字 close(connfd); close(fd); }
执行结果:
[root@localhost socket]# ./client zjl 123 ... ... ... [root@localhost socket]# ./sever zjl 123
步骤:执行./sever开启服务器,执行./client开启客户端,客户端向服务器发送数据,服务器接收到之后从服务器端向客户端发送数据。
2.3.4 UDP基本流程 2.3.4.1 单播基本流程
- 发送者
(1)打开socket
connfd = socket(AF_INET,SOCK_DGRAM,0)
(2)设置发送地址和端口
struct sockaddr_in si; si.sin_family = AF_INET; // 套接字地址簇,一般使用AF_INET si.sin_port = htons(端口); // 16位端口,网络序 si.sin_addr.s_addr = inet_addr(IP地址); // IP地址,网络序
(3)发送数据
sendto(connfd,buf,buf_size,0,(struct sockaddr *)&si,sizeof(si));
(4)关闭socket
close(connfd);
- 接收者
(1)打开socket
int connfd = socket(AF_INET, SOCK_DGRAM, 0);
(2)设置接收地址和端口
struct sockaddr_in si; si.sin_family = AF_INET; // 套接字地址簇,一般使用AF_INET si.sin_port = htons(端口); // 16位端口,网络序 si.sin_addr.s_addr = INADDR_ANY; // INADDR_ANY表示接收来自任意IP、任意网卡的发给指定端口的数据
(3)端口绑定
int ret = bind(connfd, (struct sockaddr *)&si, sizeof(si));
(4)接受数据
recv(connfd,buf,buf_size,0);
(5)关闭socket
close(connfd);
实例:
- unicast_send
#include#include #include #include #include #include #include #include #include #include #define BUFFER_SIZE 1024 int main(int argc,char **argv) { if(4 != argc){ printf("usage:%s n",argv[0]); } char *ip=argv[1]; //目的主机IP地址 int port = atoi(argv[2]);//目的主机端口号 // 1. 打开socket int connfd= socket(PF_INET,SOCK_DGRAM,0); assert(udpfd >= 0); // 2. 设置发送地址和端口 struct sockaddr_in address; //目的主机地址 bzero(&address,sizeof(address)); address.sin_family = AF_INET; address.sin_port = htons(port); inet_pton(AF_INET,ip,&address.sin_addr); // 3. 发送数据 sendto(connfd,argv[3],strlen(argv[3])+1,0,(struct sockaddr *)&address,sizeof(address)); // 4. 关闭socket close(connfd); return 0; }
- unicast_recv
#include2.3.4.2 组播/多播#include #include #include #include #include #include #include #include #include #define BUFFER_SIZE 1024 int main(int argc,char **argv) { if(3 != argc) { printf("usage:%s n",argv[0]); } char *ip = argv[1]; //用户自身的IP int port = atoi(argv[2]); //服务器端口 char buf[BUFFER_SIZE]; // 1. 打开socket int connfd = socket(PF_INET,SOCK_DGRAM,0); assert(client_fd >= 0); // 2. 设置接收地址和端口 struct sockaddr_in address,addr; socklen_t len = sizeof(addr); bzero(&address,sizeof(address)); address.sin_family = AF_INET; address.sin_port = htons(port); inet_pton(AF_INET,ip,&address.sin_addr); // 3. 端口绑定 int ret = bind(connfd,(struct sockaddr *)&address,sizeof(address)); assert(ret != -1); // 4. 接受数据 printf("recv datan"); ret = recvfrom(connfd,buf,BUFFER_SIZE-1,0,(struct sockaddr *)&addr,&len); buf[ret] = ' '; printf("n%sn",buf); // 5. 关闭socket close(connfd); return 0; }
基本流程
-
发送者
与单播发送者一致 -
接收者
(1)打开套接字
int socket(int domain, int type, int protocol); 返回套接字
(2)构建服务器地址结构
struct sockaddr_in serveraddr; bzero(&serveraddr, sizeof(serveraddr)); serveraddr.sin_family = AF_INET; serveraddr.sin_addr.s_addr = htonl(INADDR_ANY); //IP serveraddr.sin_port = htons(SERVER_PORT);//端口
(3)绑定地址
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
(4)构建组播属性结构
struct ip_mreqn group;
inet_pton(AF_INET,GROUP,&group.imr_multiaddr);//设置组播地址
inet_pton(AF_INET,"0.0.0.0",&group.imr_address);//设置本地地址
group.imr_ifindex=if_nametoindex("ent0");//设置网卡接口
(5)设置组播权限和属性
setsockopt(sockfd,IPPROTO_IP,IP_MULTICAST_IF,&group,sizeof(group));//设置组播权限及选项
(6)设置客户端组播地址
struct sockaddr_in cliaddr; bzero(&cliaddr,sizeof(cliaddr)); cliaddr.sin_family=AF_INET; inet_pton(AF_INET,GROUP,&cliaddr.sin_addr.s_addr); cliaddr.sin_port=htons(CLIENT_PORT);
(7)发送数据
sendto(sockfd,buf,strlen(buf),0,(structsockaddr*)&cliaddr, sizeof(cliaddr));//往组播地址发送信息,返回数据大小
(8)关闭套接字
close(fd);
实例:
- multicast_recv.c
#include2.3.4.3 广播#include #include #include #include #include #include void addroute(int connfd,const char* multi_ip,const char* dev){ struct rtentry rt; memset(&rt,0,sizeof(rt)); struct sockaddr_in* addr = (struct scokaddr_in*)&rt.rt_gateway; addr->sin_family = AF_INET; addr->sin_addr.s_addr = 0; addr = (struct scokaddr_in*)&rt.rt_dst; addr->sin_family = AF_INET; addr->sin_addr.s_addr = inet_addr(multi_ip); addr = (struct scokaddr_in*)&rt.rt_genmask; addr->sin_family = AF_INET; addr->sin_addr.s_addr = 0xFFFFFFFF; rt.rt_flags = RTF_UP|RTF_HOST; rt.rt_dev = dev; if(ioctl(connfd,SIOCADDRT,&rt)){ perror("ioctl err"); close(connfd); } } int main(int argc, char *argv[]) { if(5 != argc){ printf("usage:%s n",argv[0]); return 1; } int connfd = socket(AF_INET, SOCK_DGRAM, 0); if(connfd < 0){ perror("socket error"); return 1; } // 有些系统可能需要在路由中添加组播地址 // addroute(sock,argv[2],argv[4]); int reuse = 1; if(setsockopt(connfd, SOL_SOCKET, SO_REUSEADDR, (char *)&reuse, sizeof(reuse)) < 0){ perror("setsockopt SO_REUSEADDR error"); close(connfd); return 1; } struct sockaddr_in addr; addr.sin_family = AF_INET; addr.sin_addr.s_addr = inet_addr(argv[1]); addr.sin_port = htons(atoi(argv[3])); if(bind(connfd, (struct sockaddr*)&addr, sizeof(addr))){ perror("bind error"); close(connfd); return 1; } struct ip_mreq group; group.imr_multiaddr.s_addr = inet_addr(argv[2]); group.imr_interface.s_addr = inet_addr(argv[1]); if(setsockopt(connfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char *)&group, sizeof(group)) < 0){ perror("Adding multicast group error"); close(connfd); return 1; } char buf[BUFSIZ]; if(read(connfd, buf, sizeof(buf)-1) < 0){ perror("read error"); close(connfd); return 1; } printf("recv:%sn",buf); close(connfd); return 0; }
基本流程
- 发送者
(1)打开socket
cfd = socket(AF_INET,SOCK_DGRAM,0) :
(2)打开广播
setsockopt(cfd,SOL_SOCKET,SO_BROADCAST,&n,sizeof(n));
参数n: 0表示关闭属性,非0表示打开属性
(3)设置发送地址和端口
struct sockaddr_in si;
si.sin_family = AF_INET;
si.sin_port = htons(端口);
si.sin_addr.s_addr = inet_addr("255.255.255.255");
(4)发送数据
sendto(cfd,buffer,buffer_size,0,(struct sockaddr *)&si,sizeof(si));
(5)关闭socket
close(cfd);
- 接收者
与单播发送者一致
实例:
- boardcast_send.c
#include#include #include #include #include #include int main(int argc,char* argv[]) { if(4 != argc){ printf("usage:%s n",argv[0]); return 1; } int connfd= socket(AF_INET,SOCK_DGRAM, 0); if (-1 == connfd) { perror("socket error"); return 1; } int opt = 1; if(setsockopt(connfd,SOL_SOCKET,SO_BROADCAST,(char*)&opt,sizeof(opt))==-1) { perror("setsockopt fail."); close(connfd); return 1; } struct sockaddr_in addr; addr.sin_family = AF_INET; addr.sin_addr.s_addr = inet_addr(argv[1]); addr.sin_port = htons(atoi(argv[2])); if (-1 == sendto(connfd, argv[3], strlen(argv[3])+1, 0, (struct sockaddr *)&addr, sizeof(addr))) { perror("sendto error"); close(connfd); return 1; } printf("send ok!n"); close(connfd); return 0; }



