1)申请资源

int socket(int domain, int type, int protocol)

int domain:AF_INET/AF_INET6/AF_UINIX/AF_UPSPEC,从这里可以看出socket接口不仅支持网络通信,还支持本机通信(UNIX域套接字),一般本机进程间通信就会使用这种协议族;

int type:SOCK_DGRAM/SOCK_RAW/SOCK_SEQPACKET/SOCK_STREAM这个就是指定数据传输格式了,可能是字节流(TCP),可能是单个数据报(UDP),原始套接字可以直接访问IP层。

Int protocol:前面两个参数确定了,最后一个参数一般都是默认的0,直接可以用前面两个参数推断出最后一个参数;比如domain=AF_INET,type=STREAM,那么这一定是使用的TCP传输协议。

这个函数的功能就是在内核中申请一个句柄。如果是TCP协议,就会在内核中申请一块发送和接收缓冲区。接收缓冲区也就是所谓的TCP窗口,这个窗口的大小等于带宽延迟乘积时可以得到最高的网络吞吐量(看了《TCP/IP详解》这本书学到的)。调用完这个接口还没有确定TCP连接的四元组(仅限于TCP才有四元组的说法):本地IP、本地端口、远程IP、远程端口。

2)指定网卡

int bind(int sockfd, const struct sockaddr *addr, socklen_t len)

这个函数就是给套接字指定一个IP地址,IP地址也就是指定一个外出接口,外出接口也就是实际的网卡,当然也可能是环回接口(127.0.0.1),环回接口只是一个虚拟网卡驱动。可以通过shell命令查看本机的所有接口,如下图,可以看到本机网络接口有enp4s0f1/lo/virbr0/virbr0-nic/wlp3s0
image.png
实际上ifconfig命令读取的信息就是来自这个目录(这是我看《深入linux内核架构》学来的)。一般只有服务器才做地址绑定操作,其实也不必是地址绑定,但一定需要绑定监听的端口,因为地址可以设置成INADDR_ANY,内核可以在创建连接时自动绑定接收地址。如果这步操作中绑定了网卡,就相当于指定了通信四元组的本地IP、本地端口,现在就差远程IP、远程端口就可以通信了。

3)监听

int listen(int sockfd, int backlog)

int backlog:这个是指定TCP接受连接受队列大小,《TCP/IP详解》这本书说三次握手完成的连接才会放入这个待处理队列;这个队列里面的连接个人认为还没有为这些连接分配socket资源(还需要去看linux内核架构确认这个细节)。所谓的DoS拒绝服务攻击就是针对这个缓存队列,攻击者模拟很多客户端不断发送SYN分节,将这个队列沾满,让那些正常的连接请求没有机会进入队列。

4)接受连接

int accept(int sockfd, strcuct sockaddr addr, socklen_t len)
这个函数会从TCP等待连接队列里面取出一个已经完成3次握手的连接,并分配一个socket的句柄资源,这个句柄指向的连接会把通信四元组:本地IP、本地端口、远程IP、远程端口都填上,现在就可以进行完整的通信了。

5)接收数据

read
完成连接之后(只能是TCP协议),就可以使用这个接口来读取数据。

ssize_t recv(int fd, void buf, size_t nbytes, int flags)
与send函数对应,这个就是接收TCP数据的接口。*默认情况下这是阻塞函数,直到从TCP缓存得到指定的字节数之后才会返回。
所以一般服务器开发这里就需要用到多线程(reactor、preactor)或者IO端口复用(select、poll、epoll、IOCP)技术。

ssize_t recvfrom(int fd, void buf, size_t len, int flags, sockaddr addr, socklen_t addrlen)
通常这个函数是针对接收UDP数据的,但TCP接收数据也可以用它;另外这个接口返回了发送方的地址信息,是为了回复消息的时候能调用sendto接口,知道将数据回复给谁。

6)发送数据

write()
只能是TCP函数使用

read/write函数在网络数据传输的应用很好地印证了“unix系统万物皆文件”这句话;建立完整的连接之后,就可以像读普通文件一样对待网络字节流,这很好地对不同设备的细节进行完美隐藏。要想对网络数据传输有更多的操作自由和特殊功能,还需要使用以下专门接口。

ssize_t send(int sockfd, const void buf, size_t nbytes, int flags)
和write用处一样
int flags标识部分操作是对TCP首相设置。
这个函数主要针对TCP协议的数据发送,它实际上只把数据拷贝到了TCP发送缓冲区*如果对UDP套接字调用了connet函数也可以使用这个接口来进行UDP数据的发送。

ssize_t sendto(int fd, void buf, size_t nbtyes, int flags, const strct sockaddr destaddr, socklen_t destlen)
这个发送函数指明了发送的目的地址,主要是针对UDP数据的发送。

7)关闭连接

void close()

void closesocket()

int shutdown (int sockfd, int how)
int socketfd是指向socket接口返回的那个句柄
int how:SHUT_RD/SHUT_WR/SHUT_RDWR
这个接口可以明确地指向半关闭过程,也就是从应用层感知到TCP发送第一个FIN分节—进入FIN_WAIT_1,收到FIN的确认分节之后就会进入FIN_WAIT_2,收到对端的FIN分节之后就会进入最后的TIME_WAIT.

8)连接函数

connect()用于建立与指定socket的连接。
头文件: #include
函数原型: int connect(SOCKET s, const struct sockaddr * name, int namelen);
本函数用于创建与指定外部端口的连接。s参数指定一个未连接的数据报或流类套接口。如套接口未被捆绑,则系统赋给本地关联一个唯一的值,且设置套接口为已捆绑。请注意若名字结构中的地址域为全零的话,则connect()将返回WSAEADDRNOTAVAIL错误。