网络编程学习2--套接字、地址

socket英文意思是”插口”,在网络编程中,它的寓意是可以通过插口接入的方式,快速完成网络连接和数据收发.

客户端和服务器工作的核心逻辑

客户端和服务器工作的核心逻辑.png
服务器端:

  1. 初始化socket(在客户端发起连接请求前,服务器必须先初始化好).
  2. 执行bind函数(将自己的服务能力绑定到一个众所周知的地址和端口上)
  3. 执行listen操作(将原先的socket转换为服务端的socket)
  4. 服务器阻塞在accept上,等待客户端请求的到来

客户端:

  1. 初始化socket
  2. 执行connect向服务器端的地址和端口发起连接请求(地址和端口是客户端预先知道的).

建立连接后,就可以进行数据传输了.

客户端进程向操作系统内核发起 write 字节流写操作,内核协议栈将字节流通过网络设备传输到服务器端,服务器端从内核得到信息,将字节流从内核读入到进程中,并开始业务逻辑的处理,完成之后,服务器端再将得到的结果以同样的方式写给客户端。可以看到,一旦连接建立,数据的传输就不再是单向的,而是双向的,这也是 TCP 的一个显著特性。

当客户端和服务器端完成交互后,需要断开连接时,就会执行close函数.操作系统内核此时会通过原先的连接链路向服务器端发送一个 FIN 包,服务器收到之后执行被动关闭,这时候整个链路处于半关闭状态,此后,服务器端也会执行 close 函数,整个链路才会真正关闭。半关闭的状态下,发起 close 请求的一方在没有收到对方 FIN 包之前都认为连接是正常的;而在全关闭的状态下,双方都感知连接已经关闭。

以上所有操作都是通过socket来完成的,socket是用来建立连接,传输数据的唯一途径.

socket地址格式

在使用socket时,首先要解决双方的寻址问题,由于需要socket的地址来建立连接,所以我们首先需要找到这个地址. 就像打电话(使用socket进行通信)时首先需要查找电话簿(寻址),找到你想要联系的那个人,你才可以建立连接,开始交流

通用sokcet地址格式

1
2
3
4
5
6
7
C复制代码/* POSIX.1g 规范规定了地址族为2字节的值.  */
typedef unsigned short int sa_family_t;
/* 描述通用套接字地址 */
struct sockaddr{
sa_family_t sa_family; /* 地址族. 16-bit*/
char sa_data[14]; /* 具体的地址值 112-bit */
};

在这个结构体里,第一个字段是地址族,它表示使用什么样的方式对地址进行解释和保存,好比电话簿里的手机号格式,或者是固话格式,这两种格式的长度和含义都是不同的.地址族在 glibc 里的定义非常多,常用的有以下几种:

  • AF_LOCAL:表示的是本地地址,对应的是 Unix 套接字,这种情况一般用于本地 socket 通信,很多情况下也可以写成 AF_UNIX、AF_FILE;
  • AF_INET:因特网使用的 IPv4 地址;
  • AF_INET6:因特网使用的 IPv6 地址

这里的 AF_ 表示的含义是 Address Family,即地址族, 但是很多情况下,我们也会看到以 PF_ 表示的宏,比如 PF_INET、PF_INET6 等,实际上 PF_ 的意思是 Protocol Family,也就是协议族的意思。我们用 AF_xxx 这样的值来初始化 socket 地址,用 PF_xxx 这样的值来初始化 socket。我们在<sys/socket.h>头文件中可以清晰地看到,这两个值本身就是一一对应的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
C复制代码/* 各种地址族的宏定义  */
#define AF_UNSPEC PF_UNSPEC
#define AF_LOCAL PF_LOCAL
#define AF_UNIX PF_UNIX
#define AF_FILE PF_FILE
#define AF_INET PF_INET
#define AF_AX25 PF_AX25
#define AF_IPX PF_IPX
#define AF_APPLETALK PF_APPLETALK
#define AF_NETROM PF_NETROM
#define AF_BRIDGE PF_BRIDGE
#define AF_ATMPVC PF_ATMPVC
#define AF_X25 PF_X25
#define AF_INET6 PF_INET6

通用地址结构中的通用的意思是适用于多种地址族.

IPv4 socket地址格式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
C复制代码/* IPV4套接字地址,32bit值.  */
typedef uint32_t in_addr_t;
struct in_addr
{
in_addr_t s_addr;
};

/* 描述IPV4的套接字地址格式 */
struct sockaddr_in
{
sa_family_t sin_family; /* 16-bit */ /* 地址族 */
in_port_t sin_port; /* 端口号 16-bit*/
struct in_addr sin_addr; /* Internet address. 32-bit */ /* 具体的地址值(IPv4的地址是32bit的) */


/* 这里仅仅用作占位符,不做实际用处 */
unsigned char sin_zero[8];
};

和通用地址格式一样,都有一个16bit的sin_family字段,对于IPv4来说这个字段的值就是AF_INET.

对于端口号,我们可以看到端口号最多是 16-bit,也就是说最大支持 2 的 16 次方,这个数字是 65536,所以我们应该知道支持寻址的端口号最多就是 65535。有一些保留端口,即约定俗成的,已经被对应服务广为使用的端口,如ssh的22端口,http的80端口,一般而言,大于5000的端口可以作为我们自己应用程序的端口使用.

glibc定义的保留端口如下:

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
33
34
35
36
37
38
39
40
41
42
C复制代码
/* Standard well-known ports. */
enum
{
IPPORT_ECHO = 7, /* Echo service. */
IPPORT_DISCARD = 9, /* Discard transmissions service. */
IPPORT_SYSTAT = 11, /* System status service. */
IPPORT_DAYTIME = 13, /* Time of day service. */
IPPORT_NETSTAT = 15, /* Network status service. */
IPPORT_FTP = 21, /* File Transfer Protocol. */
IPPORT_TELNET = 23, /* Telnet protocol. */
IPPORT_SMTP = 25, /* Simple Mail Transfer Protocol. */
IPPORT_TIMESERVER = 37, /* Timeserver service. */
IPPORT_NAMESERVER = 42, /* Domain Name Service. */
IPPORT_WHOIS = 43, /* Internet Whois service. */
IPPORT_MTP = 57,


IPPORT_TFTP = 69, /* Trivial File Transfer Protocol. */
IPPORT_RJE = 77,
IPPORT_FINGER = 79, /* Finger service. */
IPPORT_TTYLINK = 87,
IPPORT_SUPDUP = 95, /* SUPDUP protocol. */


IPPORT_EXECSERVER = 512, /* execd service. */
IPPORT_LOGINSERVER = 513, /* rlogind service. */
IPPORT_CMDSERVER = 514,
IPPORT_EFSSERVER = 520,

/* UDP ports. */
IPPORT_BIFFUDP = 512,
IPPORT_WHOSERVER = 513,
IPPORT_ROUTESERVER = 520,


/* Ports less than this value are reserved for privileged processes. */
IPPORT_RESERVED = 1024,


/* Ports greater this value are reserved for (non-privileged) servers. */
IPPORT_USERRESERVED = 5000

IPv6 socket地址格式

1
2
3
4
5
6
7
8
C复制代码struct sockaddr_in6
{
sa_family_t sin6_family; /* 16-bit */ /* 地址族 值为AF_INET6 */
in_port_t sin6_port; /* 传输端口号 # 16-bit */
uint32_t sin6_flowinfo; /* IPv6流控信息 32-bit*/
struct in6_addr sin6_addr; /* IPv6地址128-bit */
uint32_t sin6_scope_id; /* IPv6域ID 32-bit */
};

整个结构体长度是 28 个字节,其中流控信息和域 ID 先不用管,这两个字段,一个在 glibc 的官网上根本没出现,另一个是当前未使用的字段。这里的地址族显然应该是 AF_INET6,端口同 IPv4 地址一样,关键的地址从 32 位升级到 128 位,这个数字就大到恐怖了,完全解决了寻址数字不够的问题。

本地socket地址格式

前面的IPv4和IPv6地址格式都是因特网socket的格式,而本地socket格式是用来作为本地进程间的通信的,也就是前面提到的 AF_LOCAL

1
2
3
4
C复制代码struct sockaddr_un {
unsigned short sun_family; /* 固定为 AF_LOCAL */
char sun_path[108]; /* 路径名 */
};

几种socket地址格式的比较

IPv4和IPv6的socket地址格式的长度是固定的,而本地socket地址格式的长度是可变的.

几种socket地址格式的比较.png

问题

  1. 这些socket地址格式有什么共性?
    都具有地址族字段和地址字段(本地socket地址格式中,路径名即是地址).

通过通用socket地址格式来提供一个统一的接口,然后通过地址族字段来确定具体是什么类型的地址.
2. 为什么本地socket格式不需要端口号,而 IPv4 和 IPv6 socket格式却需要端口号?
在unix系统中,一切皆文件,socket也是文件.

本地socket本质上是在访问本地的文件系统,根据文件路径就可以区分,所以不需要端口号.而远程socket是直接将一段字节流发送到远程计算机的一个进程,而远程计算机可能同时有多个进程在进行监听,所以需要用端口号标志发给哪一个进程.

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%