网络编程——分配给套接字的IP地址与端口号
参考
- 《TCP/IP网络编程》 尹圣雨
分配给套接字的IP地址与端口号
IP是Internet Protocol(网络协议)的简写,是为收发网络数据而分配给计算机的值。端口号并非赋予计算机的值,而是为区分程序中创建的套接字而分配套接字的序号
网络地址(Internet Address)
IP地址分为两类:
(1)IPv4(Internet Protocol version 4):4字节地址族
(2)IPv6(Internet Protocol version 6):16字节地址族
现在还是主要使用IPv4,IPv6的普及将需要更长时间。
IPv4标准的4字节IP地址分为网络地址和主机(指计算机)地址,且分为A、B、C、D、E等类型。网络地址(网络ID)是为区分网络而设置的一部分IP地址。所以,“向相应网络传输数据”实际上是向构成网络的路由器(Router)或交换机(Switch)传递数据,由接收数据的路由器根据数据中的主机地址向目标主机传递数据
端口号
计算机中一般配有NIC(Network Interface Card,网络接口卡)数据传输设备。通过NIC向计算机内部传输数据时会用到IP。操作系统负责把传递到内部的数据适当分配给套接字,这时就要利用端口号。即,通过NIC接收的数据内由端口号,操作系统参考此端口号把数据传输给相应端口的套接字
无法将1个端口号分配给不同的套接字。另外,端口号由16位构成,可分配的端口号范围是0-65535。但0-1023是知名端口(Well-known PORT),一般分配给特定应用程序
虽然端口号不能重复,但TCP套接字和UDP套接字不会共用端口号,所以允许重复
地址信息的表示
表示IPv4地址的结构体
struct sockaddr_in
{sa_family_t sin_family; // 地址族(Address Family)unit16_t sin_port; // 16位TCP/UDP端口号struct in_addr sin_addr; // 32位IP地址char sin_zero[8]; // 不使用
};
其中,in_addr定义如下
struct in_addr
{in_addr_t s_addr; // 32位IPv4地址
};
里面存在大量额外定义的数据类型。可以参考POSIX(Portable Operating System Interface,可移植操作系统接口)。POSIX是为UNIX系列操作系统设立的标准
POSIX中定义的数据类型:
(1)sys/types.h头文件
int8_t:signed 8-bit int
uint8_t:unsigned 8-bit int (unsigned char)
int16_t:signed 16-bit int
uint16_t:unsigned 16-bit int (unsigned short)
int32_t:signed 32-bit int
uint32_t:unsigned 32-bit int (unsigned long)
(2)sys/socket.h
sa_family_t:地址族(address family)
socklen_t:长度(length of struct)
(3)netinet/in.h
in_addr_t:IP地址,声明为uint32_t
in_port_t:端口号,声明为uint16_t
之所以额外定义这些数据类型,是考虑到扩展性。例如,如果使用int32_t类型的数据,就能保证在任何时候都占用4字节,即使将来用64位表示int类型
结构体sockaddr_in的成员
(1)sin_family
每种协议族适用的地址族均不同。比如,IPv4使用4字节地址族,IPv6使用16字节地址族
AF_INET:IPv4网络协议中使用的地址族
AF_INET6:IPv6网络协议中使用的地址族
AF_LOCAL:本地通信中采用的UNIX协议的地址族
(2)sin_port
以网络字节序保存16位端口号
(3)sin_addr
以网络字节序保存32位IP地址信息。本质上为32位整型数
(4)sin_zero
无特殊含义。只是为使结构体sockaddr_in的大小与sockaddr结构体保持一致而插入的成员。必须填充为0
实际上,bind函数的第二个参数期望得到的是sockaddr结构体变量地址值。而sockaddr结构体的定义如下:
struct sockaddr
{sa_family_t sin_family; // 地址族sa_data[14]; // 地址信息
};
其中,sa_data保存的地址信息中需包含IP地址和端口号,剩余部分应填充0。而这对于包含地址信息来讲非常麻烦,继而有了新的结构体sockaddr_in,二者内存大小相同,只是sockaddr_in填写信息更方便
另外,由于sockaddr并非只为IPv4设计,因此,结构体要求在sin_family中指定地址族信息。为了与sockaddr一致,sockaddr_in结构体中也有地址族的信息,尽管sockaddr_in只用于保存IPv4地址信息
网络字节序与地址变换
字节序与网络字节序
CPU向内存保存数据的方式有2种,这意味着CPU解析数据的方式也有2种:
- 大端序(Big Endian):高位字节存放到低位地址
- 小端序(Little Endian):高位字节存放到高位地址
主机字节序(Host Byte Order)在不同CPU中也各不相同。目前主流的Intel系列CPU以小端序方式保存数据。2台字节序不同的计算机之间数据传递会出现问题,只有改变数据保存顺序才能被识别为同一值
所以,在通过网络传输数据时约定统一方式,这种约定称为网络字节序(Network Byte Order),统一为大端序。即,先把数据数组转化成大端序格式再进行网络传输
字节序转换(Endian Conversion)
转换字节序的函数:
unsigned short htons(unsigned short);
unsigned short ntohs(unsigned short);
unsigned long htonl(unsigned long);
unsigned long ntohl(unsigned long);
其中,
- htons中的h代表主机(host)字节序
- htons中的n代表网络(network)字节序
- s指short;l指的是long(Linux中long类型占用4个字节)
- 通常htons和ntohs用于端口号转换;htonl和ntohl用于IP地址转换
- 即使在大端序系统中,最好也经过主机字节序转换为网络字节序的过程,便于代码扩展
- 除了向sockaddr_in结构体变量填充数据外,其他情况无需考虑字节序问题,都是自动的
网络地址的初始化与分配
将字符串信息转换为网络字节序的整数型
sockaddr_in中保存地址信息的成员为32位整数型,但对于IP地址的表示,我们熟悉的是点分十进制表示法(Dotted Decimal Notation),而非整数型数据表示法
(1)inet_addr()
使用inet_addr函数可以将字符串形式的IP地址转换成32位整数型数据
#include <arpa/inet.h>in_addr_t inet_addr(const char* string);
成功时返回32位大端序整数型值,失败时返回INADDR_NONE
(2)inet_aton()
inet_aton函数与inet_addr函数在功能上完全相同,也将字符串形式IP地址转换为32位网络字节序整数并返回。只不过该函数利用了in_addr结构体,且使用频率更高
#include <arpa/inet.h>int inet_aton(const char* string, struct in_addr * addr);
成功时返回1,失败时返回0。string:含有需转换的IP地址信息的字符串地址值;addr:将保存转换结果的in_addr结构体变量的地址值
(3)inet_ntoa()
与inet_aton函数正好相反,inet_ntoa可以把网络字节序整数型IP地址转换成我们熟悉的字符串形式
#include <arpa/inet.h>char* inet_ntoa(struct in_addr adr);
成功时返回转换的字符串地址值,失败时返回-1
该函数未向程序员要求分配内存,而是在内部申请了内存并保存了字符串。也就是说,调用完该函数后,应立即将字符串信息复制到其他内存空间;否则,再次调用该函数,会覆盖之前保存的字符串信息
网络地址初始化
常见的网络地址信息初始化方法如下:
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr)); // 结构体变量addr的所有成员初始化为0
addr.sin_family = AF_INET; // 指定地址族
addr.sin_addr.s_addr = inet_addr(serv_ip); // 基于字符串的IP地址初始化
addr.sin_port = htons(atoi(serv_port)); // 基于字符串的端口号初始化
让所有成员初始化为0,是为了将sin_zero初始化为0
客户端地址信息初始化
服务器端声明sockaddr_in结构体变量,将其初始化为赋予服务器端IP和套接字的端口号,然后调用bind函数;而客户端则声明sockaddr_in结构体,并初始化为要与之连接的服务器端套接字的IP和端口号,然后调用connect函数
INADDR_ANY
利用常数INADDR_ANY分配服务器端的IP地址:
addr.sin_addr.s_addr = htonl(INADDR_ANY);
若采用这种方式,则可自动获取运行服务器端的计算机IP地址。而且,若同一计算机中已分配多个IP地址(多宿主(Multi-homed)计算机,路由器属于这一类),则只要端口号一致,就可以从不同IP地址接收数据。因此,服务器端中优先考虑这种方式;而客户端中除非带有一部分服务器端功能,否则不会采用
同一计算机中可以分配多个IP地址,实际IP地址的个数与计算机中安装的NIC的数量相等。即使是服务器端套接字,也需要决定应接收哪个IP传来的(哪个NIC传来的)数据。因此,服务器端套接字初始化过程中要求IP地址信息。若只有1个NIC,则直接使用INADDR_ANY
向套接字分配网络地址
bind函数负责把初始化的地址信息分配给套接字
#include <sys/socket.h>int bind(int sockfd, struct sockaddr* myaddr, socklen_t addrlen);
成功时返回0,失败时返回-1。sockfd:要分配地址信息(IP地址和端口号)的套接字文件描述符;myaddr:存有地址信息的结构体变量地址值;addrlen:第二个结构体变量的长度
基于Windows的实现
htons和htonl
与Linux平台下的使用无区别
inet_addr和inet_ntoa
Windows中不存在inet_aton,而inet_addr和inet_ntoa的使用于Linux下一样。但inet_addr会出现不能使用的情况,可以用inet_pton,需要包含头文件<Ws2tcpip.h>
#include <stdio.h>
#include <string.h>
#include <WinSock2.h>
#include <Ws2tcpip.h>
void ErrorHandling(char* message);int main(int argc, char* argv[])
{WSADATA wsaData;if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0){ErrorHandling("WSAStartup() error!");}{char* addr = "127.212.124.78";// unsigned long conv_addr = inet_addr(addr);// if (conv_addr == INADDR_NONE)// {// printf("Error occured! \n");// }// else// {// printf("Network ordered integer addr: %#lx \n", conv_addr);// }unsigned long conv_addr;if ((inet_pton(AF_INET, addr, &conv_addr)) == 0){printf("Error occured! \n");}else{printf("Network ordered integer addr: %#lx \n", conv_addr);}}{struct sockaddr_in addr;char* strPtr;char strArr[20];addr.sin_addr.s_addr = htonl(0x1020304);strPtr = inet_ntoa(addr.sin_addr);strcpy(strArr, strPtr);printf("Dotted-Decimal notation3 %s \n", strArr);}WSACleanup();return 0;
}void ErrorHandling(char* message)
{fputs(message, stderr);fputc('\n', stderr);exit(1);
}
WSAStringToAddress和WSAAddressToString
这是WinSock2中增加的2个转换函数,在功能上与inet_addr和inet_ntoa完全相同,但优点在于支持多种协议,在IPv4和IPv6中均可适用。但这两个函数依赖于特定平台,会降低兼容性
(1)WSAStringToAddress
#include <WinSock2.h>INT WSAStringToAddress(LPTSTR AddressString, INT AddressFamily, LPWSAPROTOCOL_INFO lpProtocolInfo,LPSOCKADDR lpAddress, LPINT lpAddressLength
);
成功时返回0,失败时返回SOCKET_ERROR。AddressString:含有IP和端口号的字符串地址值;AddressFamily:第一个参数中地址所属的地址族信息;lpProtocolInfo:设置协议提供这(Provider),默认为NULL;lpAddress:保存地址信息的结构体变量地址值;lpAddressLength:第四个参数中传递的结构体长度所在的变量地址值。新出现的各种类型都是针对默认数据类型的typedef声明
(2)WSAAddressToString
它将结构体中的地址信息转换成字符串形式
#include <WinSock2.h>INT WSAAddressToString(LPSOCKADDR lpsaAddress, DWORD dwAddressLength,LPWSAPROTOCOL_INFO lpProtocolInfo, LPSTR lpszAddressString,LPDWORD lpdwAddressStringLength
);
成功时返回0,失败时返回SOCKET_ERROR。lpsaAddress:需要转换的地址信息结构体变量地址值;dwAddressLength:第一个参数中结构体的长度;lpProtocolInfo:设置协议提供者(Provider),默认为NULL;lpszAddressString:保存转换结果的字符串的地址值;lpdwAddressStringLength:第四个参数中存有地址信息的字符串长度
示例:
#undef UNICODE
#undef _UNICODE
#include <stdio.h>
#include <WinSock2.h>int main(int argc, char* argv[])
{char* strAddr = "203.211.218.102:9190";char strAddrBuf[50];SOCKADDR_IN servAddr;int size;WSADATA wsaData;WSAStartup(MAKEWORD(2, 2), &wsaData);size = sizeof(servAddr);WSAStringToAddress(strAddr, AF_INET, NULL, (SOCKADDR*)&servAddr, &size);size = sizeof(strAddrBuf);WSAAddressToString((SOCKADDR*)&servAddr, sizeof(servAddr), NULL, strAddrBuf, &size);printf("Second conv result: %s \n", strAddrBuf);WSACleanup();return 0;
}
网络编程——分配给套接字的IP地址与端口号相关推荐
- TCP/IP网络编程 学习笔记_3 --给套接字分配IP地址和端口号
IP地址和端口号 1,IP地址:为使计算机连接到网络并收发数据,必须为其分配IP地址.IP地址分为两类:IPv4(4字节地址族)和IPv6(16字节地址族).它们主要区别就是在表示IP地址所用的字节数 ...
- 【Linux网络编程】UDP 套接字编程
[Linux网络编程]UDP 套接字编程 [1]用户数据报协议(UDP) UDP是一个简单的传输层协议,不保证UDP数据报会到达其最终目的地,不保证各个数据报的先后顺序跨网络后保持不变,也不保证每个数 ...
- Linux网络编程:原始套接字的魔力【续】
如何从链路层直接发送数据帧 本来以为这部分都弄完了,结果有朋友反映说看了半天还是没看到如何从链路层直接发送数据.因为上一篇里面提到的是从链路层"收发"数据,结果只&q ...
- 【网络编程】Socket套接字;UDP数据报套接字编程;TCP流套接字编程
文章目录 1. 什么是网络编程 2. 网络编程中的基本概念 3. Socket套接字 4 UDP数据报套接字编程 4.1 客户端服务器交互流程 4.2 UDP版本的回显服务 4.3 英译汉服务 5. ...
- UNIX网络编程.卷1,套接字联网API(第3版)(中文版)(Stevens经典著作,两位顶级网络编程专家应邀执笔修订)...
UNIX网络编程.卷1,套接字联网API(第3版)(中文版)(Stevens经典著作,两位顶级网络编程专家应邀执笔修订) 基本信息 原书名: Unix Network Programming, Vol ...
- python socket 域名_Python网络编程中的套接字名和DNS解析。
距离上一次TCP的文章,这一次要讲的是套接字名和DNS,并且还会涉及到网络数据的发送接受和网络错误的发生和处理. 下面说套接字名,在创建和部署每个套接字对象时总共需要做5个主要的决定,主机名和IP地址 ...
- JavaSE(十四)——网络编程(IP地址、端口号、TCP、UDP)
文章目录 1. 概述 2. InetAddress类 3. 端口 4. TCP协议 4.1 传输消息 4.2 文件上传 5. UDP协议 6. TCP与UDP区别 7. URL 8. 下载资源 9. ...
- 网络编程之通俗讲解,有了IP地址,为何还要用MAC地址?
网络编程之通俗讲解,有了IP地址,为何还要用MAC地址? 前言 书上说的 基本概念: 什么是MAC地址? 什么是IP地址? 为什么要用到MAC地址? IP地址与MAC地址的区别是什么? MAC地址涉及 ...
- 网络通信协议(IP地址,端口号,InetAddress,UDP与TCP)
一.计算机网络: 通过某种方式将多台计算机进行连接,实现多台计算机彼此之间的互联以及数据的交换.即在不同的计算机上编写一些实现了网络连接的程序,这些程序可以实现位于同一个网络中的计算机之间的数据的交换 ...
最新文章
- 数据分析 python 用途-Python 从爬虫到数据分析
- 【转】NHibernate入门教程
- 并不对劲的bzoj4816:loj2000:p3704[SDOI2017]数字表格
- [转] CMake入门
- es6 遍历数组对象获取所有的id_ES6对象遍历Object.keys()方法
- jquery解析XML
- 软件测试计划和测试报告
- POJ 2488 A Knight's Journey
- java digestutils.md5hex_linux下md5sum和DigestUtils.md5Hex的关系 博客分类: java
- java float 输出文本框_关于Java中float数输出时显示问题
- ubuntu20.04 nvidia 460显卡安装
- Android中使用OKHttp上传图片,从相机和相册中获取图片并剪切
- LocalDateTime的基本使用
- redis之数据倾斜如何处理
- word2013插入excel对象报错_教大家Excel2013如何插入对象文件
- colorkey唇釉是否安全_colorkey唇釉安全吗-colorkey唇釉真假辨别
- 北京工业大学计算机考研录取名单,2018年硕士研究生招生考试复试一志愿考生名单(信息学部)...
- 李开复:长尾效应带给媒体的不是威胁
- mini2440裸试验—计算器(LCD显示,触摸屏突破)
- 互联网的一些小知识点
热门文章
- 基于jquery框架实现以行的添加、上移、下移和删除操作
- java队列(Queue)用法总结
- Windows C++ CreateThread
- idea项目首次通过git上传到码云报错解决方法“push reject”
- 2.14 OrCAD中怎么运用表格创建复杂的元器件?
- PyCharm快捷键大全-你想要的全都有
- 初中python程序设计教学研究_初中Python程序设计教学方法初探
- 7种 JVM 垃圾收集器特点、优劣势及使用场景(多图)
- [364]尚未启动messenger服务,将不发送netsend通知
- 【MySQL】分组数据