文章目录
- 前言
- 前置知识
- Server 端核心模型 【重点】
- 相关函数 【重点】
- socket 函数
- bind 函数
- listen 函数
- accept 函数
- close 函数
- sockaddr 数据结构 【重点】
前言
本文主要是对 Linux 网络编程中,Server 端的模型、相关函数 以及 sockaddr、sockaddr_in 结构体做介绍。
前置知识
① 理解啥是 socket ,可参考:浅谈 Linux 网络编程 socket
② 理解 字节流转换 函数,可参考:浅谈 Linux 网络编程 - 网络字节序
Server 端核心模型 【重点】
一定要记住 server 端的套路:
①创建 socket()
②绑定 ip + port,bind()
③设置连接上限,listen()
④阻塞,监听客户端的连接,accept()
⑤业务逻辑 ,read()/write()
⑥关闭 socket,close()
以上几个步骤是固定死的,直接背下来,把"前置知识"和接下来要讲解的 函数 理解后,我们按照这个套路就能写出 Server 端的 socket 模型。
还有两个需要记住的:
- listen() 这步是用来设置 Server 端的连接上限,而不是监听来自客户端的连接,不要被名字迷惑了。
- accept() 函数才是阻塞监听来自客户端的连接。
相关函数 【重点】
socket 函数
#include <sys/socket.h>
int socket(int domain, int type, int protocol); 创建一个 套接字
domain:指定协议族或地址族,例如:AF_INET、AF_INET6、AF_UNIX
type:指定套接字的类型,例如:SOCK_STREAM、SOCK_DGRAM
SOCK_STREAM(流式套接字,提供面向连接的可靠传输)
SOCK_DGRAM(数据报套接字,提供无连接的不可靠传输)
protocol: 指定具体的协议编号,通常为0表示自动选择合适的协议。
返回值:
成功: 新套接字所对应文件描述符
失败: -1 errno
对于这个函数的参数,一般是这样的:
fd = socket (AF_INET, SOCK_STREAM, 0);
bind 函数
这个函数的第二个参数是重点,不懂的,建议先去看看本篇的【sockaddr 数据结构】章节,再回来看 bind 函数的参数传递。
#include <arpa/inet.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
给 socket 绑定一个 地址结构 (IP+port)
sockfd: socket() 函数返回值
struct sockaddr_in addr; // 重点掌握这个结构体
addr.sin_family = AF_INET;
addr.sin_port = htons(8888);
addr.sin_addr.s_addr = htonl(INADDR_ANY); // 获取本机任意可用的 ip,转换成网络字节流
addr: 传入参数(struct sockaddr *)&addr
addrlen: sizeof(addr) (服务端)地址结构的大小。
返回值:
成功:0
失败:-1 errno
listen 函数
该函数是设置 server 连接上限的。
int listen(int sockfd, int backlog); 设置同时与服务器建立连接的上限数。(同时进行3次握手的客户端数量)
sockfd: socket() 函数的返回值
backlog:上限数值。最大值 128.
返回值:
成功:0
失败:-1 errno
accept 函数
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); 阻塞等待客户端建立连接,成功的话,返回一个与客户端成功连接的 socket 文件描述符。
sockfd: socket() 函数的返回值
struct sockaddr_in cli_addr;
cli_addr:是传出参数。成功与服务器建立连接的那个 客户端的地址结构(IP+port),因为是传出参数, cli_addr 成员的值由 accept 函数赋值
socklen_t clit_addr_len = sizeof(cli_addr);
addrlen:是传入传出参数。客户端结构体大小, &clit_addr_len。
入:cli_addr 的大小。 出:客户端 cli_addr 实际大小。
返回值:
成功:返回能与客户端进行数据通信的 socket 对应的文件描述。
失败: -1 , errno
重点要注意的就是 accept 返回值,accept 返回的 socket 才是真正与 client 建立连接的 socket。
在"前置知识"中说过,socket 是成对出现的,所以 client 和 server 都有自己的 socket,需要注意的是,客户端的socket (cfd) 连接的是 server 的第二个 socket( fd2 )。
server 端的两个套接字:
socket 函数创建的是 监听套接字(fd1),用于监听来自客户端的连接请求,和进行端口、IP 绑定。
由 accept 函数创建的是用于通信的套接字(fd2),用于和客户端建立连接,称为 通信套接字 。
close 函数
这个没啥好说的,就是关闭 socket。
close(socket_fd);
sockaddr 数据结构 【重点】
这个 sockaddr 和 bind 函数的第二个参数 " const struct sockaddr *addr "有关。
在早期的时候,使用的是 sockaddr,后来出现了新的、用于 ipv4 的 sockaddr_in,所以现在使用的都是 sockaddr_in。
sockaddr_in 相比于 sockaddr,二者的大小都是一样的,只是在空间划分上分的更细:
按以上的说法,就是需要在 bind 函数的第二个参数传入 sockaddr_in 类型的结构体变量,但是 bind 函数的声明是这样的:
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
很明显,第二个参数是 sockaddr 类型,因此我们在传 sockaddr_in 类型的参时需要做强转,例如:
struct sockaddr_in addr;
// 这里省略给 addr 的成员赋值
... ...
// 将 sockaddr_in 类型的结构体变量 addr,强转成 sockaddr 类型的结构体变量
bind(fd, ( struct sockaddr *) &addr, addrlrn);
sockaddr_in 结构体的内部也需要关注,因为 sockaddr_in 是一个传入参数,也就是需要先给 sockaddr_in 的结构体变量(addr)赋值,然后再将结构体变量(addr)传入bind函数。
sockaddr_in 结构体内部是这样的:
给 sockaddr_in 的成员变量赋值:
struct sockaddr_in addr;
addr.sin_family = AF_INET; // ip v4
addr.sin_port = htons(8888); // 本地字节序 转 网络字节序 【前置知识 中有介绍】
addr.sin_addr.s_addr = htonl(INADDR_ANY); // 获取本机的任一有效 ip,本地字节序 转 网络字节序 【前置知识 中有介绍】
以上就是对 sockaddr_in 结构体成员的赋值和 bind 的第二个参数传递的介绍。