套接字1

socket套接字(1)

在Linux中的网络编程是通过socket接口来进行的,它也是一种文件描述符。socket是一种常用的进程之间通信机制,通过它不仅能实现本地机器上的进程之间的通信,而且通过网络能够在不同机器上的进程之间进行通信。

每一个Socket 都用一个半相关描述:

{协议,本地地址,本地端口}

一个完整的Socket 则用一个相关描述:

{协议,本地地址,本地端口,远程地址,远程端口}

socket类型

常见的socket有3种类型:

  1. 流式socket(SOCK_STREAM) : 流式套接字提供可靠的、面向连接的通信流;它使用TCP协议,从而保证了数据传输的正确性和顺序性

  2. 数据报socket(SOCK_DGRAM) : 数据报套接字定义了一种无连接的服务,数据通过相互独立的报文进行传输,是无需并且不保证可靠、无差错的。它使用数据报协议UDP

  3. 原始socket(SOCK_RAW) : 原始套接字允许对底层协议进行直接访问,它功能强大,但使用较为复杂,主要用于协议的开发

地址结构相关处理

地址相关的数据类型有以下两个:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
struck sockaddr
{
/*地址族*/
unsigned short sa_family;
/*14字节的协议地址,包含该socket的IP地址和端口号*/
char sa_data[14];
};

struck sockaddr_in
{
/*地址族*/
short int sa_family;
/*端口号*/
unsigned short int sin_port;
/*IP地址*/
struck in_addr sin_addr;
/*填充0,以保持与struck sockaddr同样大小*/
unsigned char sin_zero[8];
}

这两个数据类型是等效的,可以互相妆化,通常sockaddr_in使用更加方便

sa_family可选值有:

  • AF_INET : IPv4协议
  • AF_INET6 : IPv6协议
  • AF_LOCAL : UNIX域协议
  • AF_LINK : 链路地址协议
  • AF_KEY : 密钥套接字

数据存储有点顺序

计算机数据存储有两种字节优先顺序:高位字节优先(大端模式)和低位字节优先(小端模式),Internet上数据以高字节优先顺序在网络上传输,因此在有些情况下需要对这两个字节存储优先顺序进行相互转化。这里用到了4个函数:htons()、ntohs()、htonl()、ntohl()。这四个函数分别实现网络字节序和主机字节序的转化。h代表host,n代表network,s代表short,l代表long。通常16位的IP端口号用s代表,而IP地址用l代表。

1
2
3
4
5
6
#include <netinet/in.h>

uint16_t htons (unit16_t host16bit);
uint16_t ntohs (unit16_t net16bit);
uint16_t htonl (unit32_t host32bit);
uint16_t ntohl (unit32_t net32bit);

地址格式转化

通常用户在表达地址时采用的是点分十进制表示的数值,而在socket编程中使用的是二进制值,这就需要将这两个值进行转换。IPv4中用到的函数有inet_aton()、inet_addr()和inet_ntoa(),IPv4和IPv6兼容的有inet_pton()和inet_ntop()。

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <arpa/inet.h>

//字符串转in_addr的函数:
int_addr_t inet_addr(const char *strptr);

int inet_aton(const char* strptr,struct in_addr *addrptr);

int inet_pton(int family,const char* strptr,void *addrptr);

//in_addr转字符串的函数:
char *inet_ntoa(struct in_addr inaddr);

const char *inet_ntop(int famliy,const void*addrptr,char *strptr,size_t len);

名字地址转换

在Linux中实现主机名和地址的转换函数有:gethostbyname()、gethostbyaddr()和getaddrinfo()等。其中gethostbyname()是将主机名转化为IP地址,gethostbyaddr()则是逆操作

gethostbyname()、gethostbyaddr()都涉及一个hostent的结构体:

1
2
3
4
5
6
7
8
struct hostent
{
char *h_name; /*正式主机名*/
char **h_aliases; /*主机别名*/
int h_addrtype; /*地址类型*/
int h_length; /*地址长度*/
char **h_addr_list; /*指向地址的指针数组*/
}

getaddrinfo()涉及一个addrinfo的结构体:

1
2
3
4
5
6
7
8
9
10
11
struct addrinfo
{
int ai_flags; /*AI_PASSIVE, AI_CANONNAME*/
int ai_famuly; /*地址族*/
int ai_socketype; /*socket类型*/
int ai_protocol; /*协议类型*/
size_t ai_addrlen; /*地址字节长度*/
char *ai_canonname; /*主机名*/
struct sockaddr *ai_addr; /*socket结构体*/
struct addrinfo *ai_next; /*下一个指针链表*/
}

gethostbyname()

1
2
#include <netdb.h>
struct hostent *gethostbyname(const char *hostname);

hostname : 主机名

成功返回hostent结构体指针,出错返回-1

getaddrinfo()

1
int getaddrinfo(const char *node, const char *service, const struct addrinfo *hints, struct addrinfo **result);

node : 网络地址或网络主机名

service : 服务名或十进制的端口号字串

hints : 服务线索

result : 返回结果

成功返回0,出错返回-1

在调用之前,首先要对hints服务器线索进行设置,下面给出addrinfo常见的选项值

ai_flags:

  • AI_PASSIVE 该套接口是用作被动打开
  • AI_CANONNAME 通知getaddrinfo函数返回主机的名字

ai_family:

  • AF_INET IPv4协议
  • AF_INET6 IPv6协议
  • AF_UNSPEC IPv4或IPv6协议

ai_socktype :

  • SOCK_STREAM 字节流套接字socket(TCP)
  • SOCK_DGRAM 数据报套接字spcket(UDP)

ai_protocol:

  • IPPROTO_IP IP协议
  • IPPROTO_IPV4 IPv4协议
  • IPPROTO_IPV6 IPv6协议
  • IPPROTO_UDP UDP
  • IPPROTO_TCP TCP
  • 通常服务端在调用getaddrinfo()之前,ai_flag设置AI_PASSIVE,用于bind()函数,主机名会设置为NULL
  • 客户端调用getaddrinfo()时,ai_flags一般不设置AI_PASSIVE,但是主机名和服务名不应该为空

getaddrinfo()用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>

int main ()
{
struct addrinfo hints, *res = NULL;
int rc;

memset (&hints, 0, sizeof (hints));
hints.ai_flags = AI_CANONNAME;
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_DGRAM;
hints.ai_protocol = IPPROTO_UDP;

rc = getaddrinfo("localhost", NULL, &hints, &res);
if (rc != 0)
{
perror("getaddrinfo");
exit(1);
}
else
{
printf("Host name is %s\n", res->ai_canonname);
}
exit(0);
}

套接字1
https://carl-5535.github.io/2021/06/30/Linux系统编程/套接字1/
作者
Carl Chen
发布于
2021年6月30日
许可协议