以前发的在公众号上代码阅读体验不佳,因此排版后从新发布。css
1. 网络编程基本概念
1.1 什么是套接字
套接字,也叫socket,是操做系统内核中的一个数据结构,它是网络中的节点进行相互通讯的门户。网络通讯,说白了就是进程间的通讯(同一台机器上不一样进程或者不一样计算机上的进程间通讯)。linux
在网络中,每一台计算机或者路由都有一个网络地址,就是IP地址。两个进程通讯时,首先要肯定各自所在的网络节点的网络地址。可是,网络地址只能肯定进程所在的计算机,而一台计算机上通常都是同时运行着多个进程,因此仅凭网络地址还不能肯定究竟是和网络中的哪个进程进行通讯,所以套接口中还须要包括其余的信息,好比端口号和协议。web
1.2 端口号的概念
在网络的世界里,端口大体有两种:编程
一是物理意义上的端口,如交换机、路由器等用于链接其余网络设备的接口;微信
二是指TCP/IP协议族中的端口号;网络
端口号的范围从0-65535,分类以下:数据结构
一类是众所周知的,公用的端口号,其值通常为0~1024,例如http的端口号是80,ftp为21,ssh为22,telnet为23等;app
一类是用户本身定义的,一般是大于1024而且小于65535的整型值;ssh
1.3 ip地址的表示
一般咱们在表达IP地址时习惯使用点分十进制表示的数值(或者是为冒号分开的十六进制Ipv6地址),而在socket编程中使用的则是二进制值,这就须要对这两个数值进行转换。socket
ipv4地址:32bit, 4字节,至关于一个整型,一般采用点分十进制记法,例如对于:10000000 00001011 00000111 00011111, 点分十进制表示为:128.11.7.31。
2. socket的概念
socket是一种特殊的I/O接口,它也是一种文件描述符。如第一节所说,经过它不只能实现本地机器上的进程之间的通讯,并且经过网络可以在不一样机器上的进程之间进行通讯。
每个socket都用一个半相关描述{协议、本地地址、本地端口}来表示;
一个完整的套接字则用一个相关描述{协议、本地地址、本地端口、远程地址、远程端口}来表示;
socket也有一个相似于打开文件的函数调用,该函数返回一个整型的socket描述符,随后的链接创建、数据传输等操做都是经过这个socket描述符来实现的。
2.1 socket类型
2.1.1 流式socket(SOCK_STREAM)
用于TCP通讯,流式套接字提供可靠的、面向链接的通讯流,使用TCP协议,从而保证了数据传输的正确性和顺序性。
2.1.2 数据报socket(SOCK_DGRAM)
用于UDP通讯,数据报套接字定义了一种无链接的服务,数据经过相互独立的报文进行传输,是无序的,而且是不可靠的,它使用数据报协议UDP。
2.1.3 原始socket (SOCK_RAW)
用于新的网络协议实现的测试等,原始套接字容许对底层协议如IP或ICMP进行直接访问,它功能强大但使用较为不便,主要用于一些自定义协议的开发。
2.2 socket信息数据结构
1//头文件<netinet/in.h> sockaddr和sockaddr_in大小一致
2struct sockaddr
3{
4 unsigned short sa_family; /*地址族*/
5 char sa_data[14]; /*14字节的协议地址,包含该socket的IP地址和端口号。*/
6};
7struct sockaddr_in
8{
9 short int sa_family; /*地址族 AF_INET IPv4协议 AF_INET6 IPv6协议*/
10 unsigned short int sin_port; /*端口号*/
11 struct in_addr sin_addr; /*IP地址*/
12 unsigned char sin_zero[8]; /*填充0 以保持与struct sockaddr一样大小*/
13};
14struct in_addr
15{
16unsigned long int s_addr; /* 32位IPv4地址,网络字节序 */
17};
2.3 数据存储字节序的转换
计算机数据存储有两种字节优先顺序:高位字节优先(称为大端模式)和低位字节优先(称为小端模式)。
内存的低地址存储数据的低字节,高地址存储数据的高字节的方式叫小端模式;
内存的高地址存储数据的低字节,低地址存储数据高字节的方式称为大端模式;
eg,对于内存中存放的数0x12345678来讲:
若是是采用大端模式存放的,则其真实的数是:0x12345678;
若是是采用小端模式存放的,则其真实的数是:0x78563412;
端口号和IP地址都是以网络字节序存储的,不是主机字节序,网络字节序都是大端模式,而主机字节序则通常都是小端模式(也有特殊的是大端模式,这里不考虑)。因此在网络链接过程当中,要把主机字节序和网络字节序相互对应起来,须要对这两个字节存储顺序进行转换。
这里用到四个函数:htons(),ntohs(),htonl()和ntohl().这四个函数分别实现网络字节序和主机字节序的转化,这里的h表明host,n表明network,s表明short,l表明long。一般16位的IP端口号用s表明,而IP地址用l来表明。
1#include <arpa/inet.h>
2uint32_t htonl(uint32_t hostlong); //将主机的无符号长整型数转换成网络字节序
3uint16_t htons(uint16_t hostshort); //将主机的无符号短整形数转换成网络字节序
4uint32_t ntohl(uint32_t netlong); //将一个无符号长整型数从网络字节序转换为主机字节序
5uint16_t ntohs(uint16_t netshort); //将一个无符号短整形数从网络字节序转换为主机字节序
2.4 IP地址格式转化
一般在表达地址时采用的是点分十进制表示的数值(或者是为冒号分开的十进制Ipv6地址),而在socket编程中使用的则是32位的网络字节序的二进制值,这就须要对这两个数值进行转换。
这里在Ipv4中用到的函数有inet_aton()、inet_addr()和inet_ntoa(),而IPV4和Ipv6兼容的函数有inet_pton()和inet_ntop()。
2.4.1 IPv4的函数原型
1#include <sys/socket.h>
2#include <netinet/in.h>
3#include <arpa/inet.h>
4int inet_aton(const char *straddr, struct in_addr *addrptr);
5char *inet_ntoa(struct in_addr inaddr);
6in_addr_t inet_addr(const char *straddr);
函数inet_aton():将点分十进制数的IP地址转换成为网络字节序的32位二进制数值。返回值:成功,则返回1,不成功返回0.
参数straddr:存放输入的点分十进制数IP地址字符串。
参数addrptr:传出参数,保存网络字节序的32位二进制数值。
函数inet_ntoa():将网络字节序的32位二进制数值转换为点分十进制的IP地址。
函数inet_addr():功能与inet_aton相同,可是结果传递的方式不一样。inet_addr()若成功则返回32位二进制的网络字节序地址。
2.4.2 IPv4和IPv6兼容的函数原型
1#include <arpa/inet.h>
2int inet_pton(int family, const char *src, void *dst);
3const char *inet_ntop(int family, const void *src, char *dst, socklen_t len);
函数inet_pton跟inet_aton实现的功能相似,只是多了family参数,该参数指定为AF_INET,表示是IPv4协议,若是是AF_INET6,表示IPv6协议。
函数inet_ntop跟inet_ntoa相似,其中len表示表示转换以后的长度(字符串的长度)。
2.4.3 具体实现代码
1#include <stdio.h>
2#include <sys/socket.h>
3#include <netinet/in.h>
4#include <arpa/inet.h>
5#include <string.h>
6int main()
7{
8 char ip[] = "192.168.0.101";
9 struct in_addr myaddr;
10 memset((void*)&myaddr, 0, sizeof(struct in_addr));
11 /* inet_aton */
12 int iRet = inet_aton(ip, &myaddr);
13 if ( iRet == 1)
14 {
15 printf("%ld\n", myaddr.s_addr);
16 /* inet_addr */
17 printf("%x\n", inet_addr(ip));
18 }
19 else
20 {
21 printf("call inet_aton failed\n");
22 }
23
24 /* inet_pton */
25 iRet = inet_pton(AF_INET, ip, &myaddr);
26 if ( iRet == 1 )
27 {
28 printf("%x\n", myaddr.s_addr);
29 }
30 else
31 {
32 printf("call inet pton failed\n");
33 }
34 myaddr.s_addr = 0xac100ac4;
35 /* inet_ntoa */
36 printf("%s\n", inet_ntoa(myaddr));
37 /* inet_ntop */
38 inet_ntop(AF_INET, &myaddr, ip, 16);
39 puts(ip);
40 return 0;
41}
3. 域名与IP地址的对应关系
通常来说,咱们在上网的过程当中都不肯意记忆冗长的IP地址,尤为到Ipv6时,地址长度多达128位,那时就更加不可能一次性记忆那么长的IP地址了。咱们通常记住的,都是这个网站的域名地址。
你们都知道,百度的域名为:www.baidu.com,而这个域名其实对应了一个百度公司的IP地址,那么百度公司的IP地址是多少呢?
咱们能够利用ping www.baidu.com来获得百度公司的ip地址。那么,系统是如何将www.baidu.com 这个域名转化为IP地址的呢?
在linux中,最经常使用的是gethostbyname()和gethostbyaddr(),它们均可以实现IPv4/IPv6的地址和主机名之间的转化。其中gethostbyname()是将主机名转化为IP地址,gethostbyaddr()则是逆操做,是将IP地址转化为主机名。
函数原型:
1#include <netdb.h>
2struct hostent* gethostbyname(const char* hostname);
3struct hostent* gethostbyaddr(const char* addr, size_t len, int family);
4结构体:
5struct hostent
6{
7 char *h_name; /*正式主机名*/
8 char **h_aliases; /*主机别名*/
9 int h_addrtype; /*主机IP地址类型 IPv4为AF_INET*/
10 int h_length; /*主机IP地址字节长度,对于IPv4是4字节,即32位*/
11 char **h_addr_list; /*主机的IP地址列表*/
12}
13#define h_addr h_addr_list[0] /*保存的是ip地址*/
gethostbyname():用于将域名(www.baidu.com)或主机名转换为IP地址。参数hostname指向存放域名或主机名的字符串。
gethostbyaddr():用于将IP地址转换为域名或主机名。参数addr是一个IP地址,此时这个ip地址不是普通的字符串,而是要经过函数inet_aton()转换。len为IP地址的长度,AF_INET为4。family可用AF_INET:Ipv4或AF_INET6:Ipv6。
Example:
1//test.cpp 将百度的www.baidu.com 转换为ip地址
2#include <netdb.h>
3#include <sys/socket.h>
4#include <stdio.h>
5#include <arpa/inet.h>
6int main(int argc, char **argv)
7{
8 char *ptr, **pptr;
9 struct hostent *hptr = NULL;
10 char str[32] = {0};
11 if ( argc < 2 )
12 {
13 printf("please input an addr,eg:./a.out www.baidu.com\n");
14 return 0;
15 }
16 /* 取得命令后第一个参数,即要解析的域名或主机名 */
17 ptr = argv[1];
18 /* 调用gethostbyname()。结果存在hptr结构中 */
19 if((hptr = gethostbyname(ptr)) == NULL)
20 {
21 printf("gethostbyname error for host:%s\n", ptr);
22 return 0;
23 }
24 else
25 {
26 /* 将主机的规范名打出来 */
27 printf("official hostname:%s\n", hptr->h_name);
28 }
29 /* 主机可能有多个别名,将全部别名分别打出来 */
30 for(pptr = hptr->h_aliases; *pptr != NULL; pptr++)
31 printf("alias:%s\n", *pptr);
32 /* 根据地址类型,将地址打出来 */
33 switch(hptr->h_addrtype)
34 {
35 case AF_INET:
36 case AF_INET6:
37 pptr = hptr->h_addr_list;
38 /* 将刚才获得的全部地址都打出来。其中调用了inet_ntop()函数 */
39 for(; *pptr!=NULL; pptr++ )
40 {
41 printf("address:%s\n", inet_ntop(hptr->h_addrtype, *pptr, str, sizeof(str)));
42 }
43 printf("first address: %s\n", inet_ntop(hptr->h_addrtype, hptr->h_addr, str, sizeof(str)));
44 break;
45 default:
46 printf("unknown address type\n");
47 break;
48 }
49 return 0;
50}
编译运行
g++ test.cpp
./a.out www.baidu.com
1official hostname:www.a.shifen.com
2alias:www.baidu.com
3address:14.215.177.39
4address:14.215.177.38
5first address: 14.215.177.39
本文分享自微信公众号 - cpp加油站(xy13640954449)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。