socket初步学习
Published: 2024-01-12 01:31:31

# 1.Synopsis
包含头文件
```c++
#include <sys/types.h>
#include <sys/socket.h>

//function signature
int socket (int domain, int type, int protocol);
```
j信）。那么这个domain是什么呢？比如说AF_INET对应的就是IPv4,地址是这样的192.168.1.1，然后AF_INET6的地址形式2001:0db8:85a3:0000:0000:8a2e:0370:7334，AF_UNIX 或 AF_LOCAL为/tmp/socket。不同的domain对应不同的网络协议和通信方式。
对于type，我们一般会用的形式如下SOCK_STREAM（TCP）、SOCK_DGRAM（UDP）、SOCK_RAW（用于网络应用程序的开发和测试）、SOCK_SEQPACKET（和TCP的区别就是保留了数据边界）、SOCK_PACKET（用于特定的网络分析和配置）。SOCK_NONBLOCK和SOCK_CLOEXEC，分别是非阻塞模式和执行关闭模式，第一个如果一个操作不能立即完成，直接返回错误；第二个设置一个套接字，然后这个套接字是如果该进程中使用了exec系列函数，那么这个套接字直接关闭，这两个type可以与传统的type or使用。
type是SOCK_SEQPACKET时，可以认为每个读操作都是原子的，即使在面对信号中断时也是如此。

/*这里面先小提一嘴exec，exec会开一个新程序，这个新程序会替换当前进程的映像、数据、堆和栈等部分为新的程序。pid、文件描述符什么的不变，总而言之，开一个新进程替换了原来的程序，
关于fork，fork是在父进程的基础上，创建了一个新的进程，这个进程被称作子进程，子进程会得到父进程的数据、代码、堆和栈的副本，子进程和父进程有各自的地址空间，对空间更改不会影响到对方。
这两者经常一起用，先用fork，然后用exec，也就是先创建了一个子进程，然后在子进程中用exec加载并运行新程序。*/

一般来说，只要确定了domain还有type，protocol直接就确定了，所以protocol一般设置为0，，但是也有一些例外，
SOCK_DGRAM和 SOCK_RAW 的套接字发送的是数据包，用的是sendto和recvfrom。
SOCK_STREAM套接字采用的是流式通信，用的是send和recv。

可以对套接字用fcntl设置F_SETOWN，设置了这个以后，该套接字如果产生了SIGIO 或 SIGURG，可以由进程处理（当然要设置handler）（Set Owner）Using F_SETOWN is equivalent to an ioctl(2) call with the FIOSETOWN or SIOCSPGRP argument.
setsockopt和getsockopt分别是用于设置和获取套接字选项。

关于套接字的一些系统调用、库函数以及主题
getaddrinfo(3), accept(2), bind(2),  close(2),  connect(2),  fcntl(2),  getpeername(2),  getsockname(2),  getsockopt(2),
       ioctl(2), listen(2), read(2), recv(2), select(2), send(2), shutdown(2), socketpair(2), write(2), getpro‐
       toent(3), address_families(7), ip(7), socket(7), tcp(7), udp(7), unix(7)

### getaddrinfo(3)
```c++
int getaddrinfo(const char *node, const char *service,
                       const struct addrinfo *hints,
                       struct addrinfo **res);
void freeaddrinfo(struct addrinfo *res);
const char *gai_strerror(int errcode);
```
其中node是网址/域名，service是服务，比如http或https，然后hints用于指定期望返回得套接字地址类型的条件，简单说hints过滤了getaddrinfo返回的东西，然后res是一个指针，指向一个链表，链表中寸的都是符合条件的返回addrinfo结构体

下面我们来看下addrinfo里面的内容：
```c++
struct addrinfo {
               int              ai_flags;
               int              ai_family;
               int              ai_socktype;
               int              ai_protocol;
               socklen_t        ai_addrlen;
               struct sockaddr *ai_addr;
               char            *ai_canonname;
               struct addrinfo *ai_next;
           };

//我们一般不用sockaddr，用的是sockaddr_in 
struct sockaddr_in {
    sa_family_t    sin_family; /* 地址族：AF_INET */
    in_port_t      sin_port;   /* 端口号 */
    struct in_addr sin_addr;   /* IPv4 地址 */
};

struct sockaddr_in6 {
    sa_family_t     sin6_family;   /* 地址族：AF_INET6 */
    in_port_t       sin6_port;     /* 端口号 */
    uint32_t        sin6_flowinfo; /* IPv6 流信息 */
    struct in6_addr sin6_addr;     /* IPv6 地址 */
    uint32_t        sin6_scope_id; /* 范围ID（用于链路本地地址） */
};
```
1.ai_flags：标志位AI_PASSIVE将会返回一个通配地址，这个地址可以用于创建套接字并监听。AI_CANONNAME表示解析地址时返回关联的规范名称。AI_NUMERICHOST表示node参数已经是一个数字格式的IP地址了，不需要再DNS解析了。AI_NUMERICSERV表示service参数是一个端口号，而不是服务名。
2.ai_family:AF_INET 表示IPv4，AF_INET6 表示IPv6。AF_UNSPEC获取任意类型的地址。
3.ai_socktype：套接字类型，比如SOCK_STREAM代表流套接字，
SOCK_DGRAM 代表数据报套接字。
4.ai_protocol：协议类型，
5.ai_addrlen是返回的套接字地址的长度，因为不同类型的地址具有不同的大小，比如IPv4和IPv6.
6.ai_canonname：规范名，比如我们穿的node名字可能是google.com,但是规范名可能是www.google.com

其中，当hints参数为NULL的时候，getaddrinfo就好像是设置了一个带有默认值的hints结构。具体详查。
还有inet_aton可以把点分十进制ip地址转换成二进制的。

addr.sin_addr.s_addr = INADDR_ANY; 像这种，就是说任何网络接口的连接请求都会被接收到，包括主机的公网地址，私网地址，本地回环地址等等。

如何进行回环测试？以及node参数为NULL的时候？
AI_PASSIVE 没有被设置&&当node参数不为NULL的时候，我们通过getaddrinfo可以返回一个可以主动发起连接的地址。
AI_PASSIVE 没有被设置&&当node参数为NULL的时候，我们通过getaddrinfo返回的是回环地址，我们用这种回环地址可以确保客户端能够正常连接到服务器，即使没有实际的网络连接。