网编(2):套接字的地址族和协议

函数:

#include <sys/socket.h>
int socket(int domain, int type, int protocol);
//成功时返回文件描述符,失败时返回-1。
domain  //套接字中使用的协议族(Protocol Fam ily ) 信息。
type  //套接字数据传输类型信息。
protocol  //计算机间通信中使用的协议信息。

协议族( Protocol Family)——————————————

PF_INET     //IPv4互联网协议族
PF_INET6    //IPv6互联网协议族
PF_LOCAL    //本地通信的UNIX协议族
PF_pACKET   //底层套接字的协议族
PF_IPX      //IPX Novell 协议族

另外,套接字中实际采用的最终协议信息是通过socket函数的第三个参数传递的。在指定的协议族范围内通过第一个参数决定第三个参数。

套接字类型( Type )——————————————

套接字类型指的是套接字的数据传输方式。

这里写图片描述

套接字类型1 : 面向连接的套接字(SOCK_STREAM)

可靠的、按序传递的、基于宇节的面向连接的数据传输方式的套接字。

传输方式特征:

  • 传输过程中数据不会消失。
  • 按序传输数据。
  • 传输的数据不存在数据边界(Boundary)。

套接字类型2: 面向消息的套接字(SOCK_DGRAM)

不可靠的、不按序传递的、以数据的高速传输为目的的套接字。

传输方式特征:

  • 强调快速传输而非传输顺序。
  • 传输的数据可能丢失也可能损毁。
  • 传输的数据有数据边界。
  • 限制每次传输的数据大小。

协议类型( protocol)——————————————

该参数决定最终采用的协议。

IPPROTO_TCPIPPTOTO_UDPIPPROTO_SCTPIPPROTO_TIPCTCP
TCP传输协议UDP传输协议STCP传输协议TIPC传输协议

IPv4协议族中面向连接的套接字

int tcp_socket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);

IPv4协议族中面向消息的套接字

int udp_socket = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);

TCP套接字和UDP套接字不会共用端口号,所以允许重复。

怎么判断一个文件描述符是不是套接字描述符?

int issockettype(int fd)
{
	struct stat st;
	int err = fstat (fd, &st);
	if(err < 0){
		return -1;
	}
	if ((st.st_mode & S_IFMT) == S_IFSOCK){
		return 1;
	} else {
		return 0;
	}
}

 

1 地址族与数据序列

scoker结构体定义:

struct sockaddr_in
{
	sa_family_t sin_family;//地址族( Address Family )   4字节
	uint16_t sin_port;//16 位TCP/UDP 端口号             4字节
	struct in_addr sin_addr;//32 位IP                   4字节             
	char sin_zero[8);//不使用
};

成员sin_family
每种协议族适用的地址族均不同。

AF_LOCAL只是为了说明具有多种地址族而添加的。

成员sin_port
该成员保存16位端口号,重点在于它以网络字节序保存(关于这一点稍后将给出详细说明)。

成员sin_addr
该成员保存32位IP地址信息,且也以网络字节序保存。为理解好该成员,应同时观察结构体in_addr 。但结构体in_addr声明为uint32_t, 因此只需当作32位整数型即可。

成员sin_zero
无特殊含义。只是为使结构体sockaddr_ in的大小与sockaddr结构体保持一致而插入的成员。必需填充为0 , 否则无法得到想要的结果。后面会另外讲解sockaddr。

 

但是绑定函数没有直接使用该结构体,而是用了地址。

	SOCKADDR_IN servAddr;
//...
	if (bind(hServSock, (SOCKADDR*)&servAddr, sizeof(servAddr)) == SOCKET_ERROR)//第二步
		ErrorHandling("bind() error");

//...

因为上面的第二个参数的结构如下,如果直接进行设置IP地址和端口号,不方便,所以就用了C语言的一个小技巧。

typedef struct sockaddr {
    ADDRESS_FAMILY sa_family;           // Address family.  4字节
    CHAR sa_data[14];                   // Up to 14 bytes of direct address.
} SOCKADDR;


typedef USHORT ADDRESS_FAMILY;
typedef unsigned short USHORT;

sockaddr_in是sockaddr继承而来。

sockaddr _in是保存IPv4地址信息的结构体。那为何还需要通过sin_family单独指定地址族信息呢?这与之前讲过的sockaddr结构体有关。结构体sockaddr并非只为IPv4设计,这从保存地址信息的数组sa_data长度为14 宇节也可看出。因此,结构体sockaddr要求在sin_family 中指定地址族信息。为了与sockaddr保持一致, sockaddr_ in结构体中也有地址族信息。

2 字节序(Order) 与网络字节序

CPU 向内存保存数据的方式有2种,这意味着CPU解析数据的方式也分为2种。

  • 大端序(Big Endian ): 高位字节存放到低位地址。
  • 小端序(Little Endian ): 高位字节存放到高位地址。

如果发送短用大端,接收短用小端,则会出问题,因此,在通过网络传输数据时约定统一方式,这种约定称为网络字节序(Network Byte Order), 非常简单一~统一为大端序。

即,先把数据数组转化成大端序格式再进行网络传输。因此,所有计算机接收的数据是网络字节序格式,小端序系统传输数据时应转化为大端序排列方式。

字节序转换( Endian Conversions)

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 中Ion砓皂型占用4个字节,这很关键)。因此, htons是h 、to 、n 、s的组合,也可以解释为“把short型数据从主机宇节序转化为网络宇节序”。
如:ntobs可以解释为“把short型数据从网络宇节序转化为主机宇节序” 。

接收数据不用考虑字节序的问题。

则,除了向sockaddr_ in结构体变量填充数据外,其他情况无需考虑字节序问题。

字符串形式的IP地址转换成32位整数型数据:

#include <arpa/inet.h>
in_addr_t inet_addr(const char* string);

另一个函数利用了in_addr结构体,且其使用频率更高。

#include <arpa/inet.h>
int inet_aton(const char* string, struct in_addr * addr);

可以这样使用:

inet_aton(addr, &addr_inet.sin_addr);

字节序到字符串:

#include <arpa/inet.h>
char* inet_ntoa(struct in_addr adr);

网络地址初始化步骤:

struct sockaddr_in addr;
char* serv_ip = "211.117.168.13";//声明IP 地址字符串
char * serv_port = "9190";//声明端口号字符串

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));//基于宇符串的端口号初始化

INADDR_ANY

每次创建服务器端套接字都要输入IP地址会有些繁琐, 此时可如下初始化地址信息。

struct sockaddr_in addr;
char * serv_port = "9190";
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr.sin_port = htons(atoi(serv_port));

与之前方式最大的区别在于,利用常数INADDR_ANY分配服务器端的IP地址。若采用这种方式, 则可自动获取运行服务器端的计算机IP地址, 不必亲自输入。而且,若同一计算机中已分配多个IP地址(多宿主( Multi-homed ) 计算机, 一般路由器属于这一类), 则只要端口号一致,就可以从不同IP地址接收数据。因此,服务器端中优先考虑这种方式。而客户端中除非带有一部分服务器端功能, 否则不会采用。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值