linux网络编程系列(一)--套接口、ip、端口号相关

1. 网络相关概念

1.1 套接口的概念

套接口,也叫“套接字”。是操作系统内核中的一个数据结构,它是网络中的节点进行相互通信的门户。网络通信,归根到底还是进程间的通信(不同计算机上的进程间通信)。在网络中,每一个节点(计算机或路由)都有一个网络地址,也就是IP地址。两个进程通信时,首先要确定各自所在的网络节点的网络地址。但是,网络地址只能确定进程所在的计算机,而一台计算机上很可能同时运行着多个进程,所以仅凭网络地址还不能确定到底是和网络中的哪一个进程进行通信,因此套接口中还需要包括其他的信息,也就是端口号(PORT)。在一台计算机中,一个端口号一次只能分配给一个进程,也就是说,在一台计算机中,端口号和进程之间是一一对应关系。所以,使用端口号和网络地址的组合可以唯一的确定整个网络中的一个网络进程。
例如,如网络中某一台计算机的IP为123.207.251.21,操作系统分配给计算机中某一应用程序进程的端口号为1500,则此时 123.207.251.21:1500就构成了一个套接口。

1.2 端口号的概念

在网络技术中,端口大致有两种意思:

  • 一是物理意义上的端口,如集线器、交换机、路由器等用于连接其他网络设备的接口;
  • 二是指TCP/IP协议中的端口;

端口号的范围从0-65535,一类是由互联网指派名字和号码公司ICANN负责分配给一些常用的应用程序固定使用的“周知的端口”,其值一般为0~1024,例如http的端口号是80,ftp为21,ssh为22,telnet为23等;还有一类是用户自己定义的,通常是大于1024的整型值。

1.3 ip地址的表示

通常用户在表达IP地址时采用的是点分十进制表示的数值(或者是为冒号分开的十六进制Ipv6地址),而在通常使用的socket编程中使用的则是二进制值,这就需要将这两个数值进行转换。
ipv4地址:32bit, 4字节,相当于一个整型,通常采用点分十进制记法。
例如对于:10000000 00001011 00000011 00011111, 点分十进制表示为:128.11.3.31。

2. socket概念

Linux中的网络编程是通过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信息数据结构

//头文件<netinet/in.h>  sockaddr和sockaddr_in大小一致
struct sockaddr
{
     unsigned short sa_family; /*地址族*/
     char sa_data[14]; /*14字节的协议地址,包含该socket的IP地址和端口号。*/
};
struct sockaddr_in
{
     short int sa_family; /*地址族 AF_INET IPv4协议 AF_INET6 IPv6协议*/
     unsigned short int sin_port; /*端口号*/
     struct in_addr sin_addr; /*IP地址*/
     unsigned char sin_zero[8]; /*填充0 以保持与struct sockaddr同样大小*/
};
struct in_addr
{
unsigned long int s_addr; /* 32位IPv4地址,网络字节序 */
};

2.3 数据存储优先顺序的转换

计算机数据存储有两种字节优先顺序:高位字节优先(称为大端模式)和低位字节优先(称为小端模式)。内存的低地址存储数据的低字节,高地址存储数据的高字节的方式叫小端模式。内存的高地址存储数据的低字节,低地址存储数据高字节的方式称为大端模式。
eg,对于内存中存放的数0x12345678来说:

  • 如果是采用大端模式存放的,则其真实的数是:0x12345678;
  • 如果是采用小端模式存放的,则其真实的数是:0x78563412;

如果称某个系统所采用的字节序为主机字节序,则它可能是小端模式的,也可能是大端模式的。而端口号和IP地址都是以网络字节序存储的,不是主机字节序,网络字节序都是大端模式。要把主机字节序和网络字节序相互对应起来,需要对这两个字节存储优先顺序进行相互转化。这里用到四个函数:htons(),ntohs(),htonl()和ntohl().这四个地址分别实现网络字节序和主机字节序的转化,这里的h代表host,n代表network,s代表short,l代表long。通常16位的IP端口号用s代表,而IP地址用l来代表。

#include <arpa/inet.h> 
uint32_t htonl(uint32_t hostlong);   //将主机的无符号长整型数转换成网络字节序
uint16_t htons(uint16_t hostshort);  //将主机的无符号短整形数转换成网络字节序
uint32_t ntohl(uint32_t netlong);    //将一个无符号长整型数从网络字节序转换为主机字节序
uint16_t ntohs(uint16_t netshort);   //将一个无符号短整形数从网络字节序转换为主机字节序

2.4 地址格式转化

通常用户在表达地址时采用的是点分十进制表示的数值(或者是为冒号分开的十进制Ipv6地址),而在通常使用的socket编程中使用的则是32位的网络字节序的二进制值,这就需要将这两个数值进行转换。这里在Ipv4中用到的函数有inet_aton()、inet_addr()和inet_ntoa(),而IPV4和Ipv6兼容的函数有inet_pton()和inet_ntop()。

2.4.1 IPv4的函数原型
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int inet_aton(const char *straddr, struct in_addr *addrptr);
char *inet_ntoa(struct in_addr inaddr);
in_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的函数原型
#include <arpa/inet.h>
int inet_pton(int family, const char *src, void *dst);
const 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 例子
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
int main()
{
  char ip[] = "192.168.0.101";
  struct in_addr myaddr;
 memset((void*)&myaddr, 0, sizeof(struct in_addr));
 /* inet_aton */
 int iRet = inet_aton(ip, &myaddr);
 if ( iRet == 1)
 {
   printf("%ld\n", myaddr.s_addr);
   /* inet_addr */
   printf("%x\n", inet_addr(ip));
 }
 else
 {
  printf("call inet_aton failed\n");
 }

 /* inet_pton */
 iRet = inet_pton(AF_INET, ip, &myaddr);
 if ( iRet == 1 )
 {
  printf("%x\n", myaddr.s_addr);
 }
 else
 {
  printf("call inet pton failed\n");
 }
 myaddr.s_addr = 0xac100ac4;
 /* inet_ntoa */
 printf("%s\n", inet_ntoa(myaddr));
 /* inet_ntop */
 inet_ntop(AF_INET, &myaddr, ip, 16);
 puts(ip);
 return 0;
}

3. 名字地址转化

通常,人们在使用过程中都不愿意记忆冗长的IP地址,尤其到Ipv6时,地址长度多达128位,那时就更加不可能一次性记忆那么长的IP地址了。因此,使用主机名或域名将会是很好的选择。主机名与域名的区别:主机名通常在局域网里面使用,通过/etc/hosts文件,主机名可以解析到对应的ip,域名通常是再internet上使用。

众所周知,百度的域名为:www.baidu.com,而这个域名其实对应了一个百度公司的IP地址,那么百度公司的IP地址是多少呢?我们可以利用ping www.baidu.com来得到百度公司的ip地址。那么,系统是如何将www.baidu.com 这个域名转化为IP地址220.181.111.148的呢?

在linux中,有一些函数可以实现主机名和地址的转化,最常见的有gethostbyname()、gethostbyaddr()等,它们都可以实现IPv4和IPv6的地址和主机名之间的转化。其中gethostbyname()是将主机名转化为IP地址,gethostbyaddr()则是逆操作,是将IP地址转化为主机名。

函数原型:

#include <netdb.h>
struct hostent* gethostbyname(const char* hostname);
struct hostent* gethostbyaddr(const char* addr, size_t len, int family);
结构体:
struct hostent
{
     char *h_name; /*正式主机名*/
     char **h_aliases; /*主机别名*/
     int h_addrtype; /*主机IP地址类型 IPv4为AF_INET*/
     int h_length; /*主机IP地址字节长度,对于IPv4是4字节,即32位*/
     char **h_addr_list; /*主机的IP地址列表*/
}
#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:

//test.cpp 将百度的www.baidu.com 转换为ip地址
#include <netdb.h>
#include <sys/socket.h>
#include <stdio.h>
#include <arpa/inet.h>
int main(int argc, char **argv)
{
 char *ptr, **pptr;
 struct hostent *hptr = NULL;
 char str[32] = {0};
 if ( argc < 2 )
 {
  printf("please input an addr,eg:./a.out www.baidu.com\n");
  return 0;
 }
 /* 取得命令后第一个参数,即要解析的域名或主机名 */
 ptr = argv[1];
 /* 调用gethostbyname()。结果存在hptr结构中 */
 if((hptr = gethostbyname(ptr)) == NULL)
 {
  printf("gethostbyname error for host:%s\n", ptr);
  return 0;
 }
 else
 {
  /* 将主机的规范名打出来 */
  printf("official hostname:%s\n", hptr->h_name);
 }
 /* 主机可能有多个别名,将所有别名分别打出来 */
 for(pptr = hptr->h_aliases; *pptr != NULL; pptr++)
  printf("alias:%s\n", *pptr);
 /* 根据地址类型,将地址打出来 */
 switch(hptr->h_addrtype)
 {
  case AF_INET:
  case AF_INET6:
   pptr = hptr->h_addr_list;
   /* 将刚才得到的所有地址都打出来。其中调用了inet_ntop()函数 */
   for(; *pptr!=NULL; pptr++ )
   {
    printf("address:%s\n", inet_ntop(hptr->h_addrtype, *pptr, str, sizeof(str)));
   }
   printf("first address: %s\n", inet_ntop(hptr->h_addrtype, hptr->h_addr, str, sizeof(str)));
   break;
  default:
   printf("unknown address type\n");
   break;
 }
 return 0;
}

编译运行
g++ test.cpp
./a.out www.baidu.com

official hostname:www.a.shifen.com
alias:www.baidu.com
address:14.215.177.39
address:14.215.177.38
first address: 14.215.177.39

本人在简书上写的内容均为本人原创,转载需经本人同意,欢迎转载分享,请注明出处。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,378评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,356评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,702评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,259评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,263评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,036评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,349评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,979评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,469评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,938评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,059评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,703评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,257评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,262评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,485评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,501评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,792评论 2 345

推荐阅读更多精彩内容

  • 第一章 TCP/IP简介 基本的C/S服务模型 网络编程是指编写的网络通信程序可以与网络上的其他程序进行通信。 T...
    Waldo_cuit阅读 1,911评论 0 6
  • 名词延伸 通俗的说,域名就相当于一个家庭的门牌号码,别人通过这个号码可以很容易的找到你。如果把IP地址比作一间房子...
    杨大虾阅读 20,588评论 2 57
  • 网络编程 一.楔子 你现在已经学会了写python代码,假如你写了两个python文件a.py和b.py,分别去运...
    go以恒阅读 1,992评论 0 6
  • 我们深谙信息交流的价值,那网络中进程之间如何通信,如我们每天打开浏览器浏览网页时,浏览器的进程怎么与web服务器通...
    紫色冰雨阅读 1,025评论 0 4
  • 文章首发于个人blog欢迎指正补充,可联系lionsom_lin@qq.com原文地址:《网络是怎样连接的》阅读整...
    lionsom_lin阅读 14,111评论 6 31