文章目录
  1. 创建socket
  2. 命名socket
  3. 监听socket
  4. 接受连接
  5. 发起连接
  6. 关闭连接

创建socket

1
2
3
4
5
6
7
8
/*
引用方式: #include <sys/socket.h>
domain: 指定底层协议族; PF_INET: IPv4 || PF_INET6: IPv6
type: 指定服务类型; SOCK_STREAM: 流服务 || SOCK_UGRAM: 数据报服务; 对TCP/IP协议族来说,分别表示传输层使用TCP/UDP协议
protocol: 在前面两个参数的基础上更具体的选择一个协议, 一般设置为0, 表示使用默认协议
返回socket文件描述符: 成功 || 返回-1: 失败
*/
int socket (int domain, int type, int protocol);

命名socket

创建socket时,只给出了地址族,但是并未指定使用的具体socket地址,将一个socket与socket地址绑定称为socket命名。命名socket通常用在服务器端,这样客户端才知道该怎么连接该socket。而客户端通常不需要命名socket,采用匿名方式,即使用操作系统自动分配的socket地址。命名socket使用bind函数。

1
2
3
4
5
6
7
8
/*
引用方式: #include <sys/socket.h>
fd: 用于绑定的socket(文件描述符)
addr: 用于绑定的socket地址
len: socket地址长度, 可使用sizeof(* addr)
返回0: 成功 || 返回-1并设置errno: 失败
*/
int bind (int fd, const struct sockaddr * addr, socklen_t len);

常见的errno有两种:EACCES(被绑定的地址是受保护地址,仅超级用户可以访问。如普通用户将socket绑定到0-1023端口)与EADDRINUSE(被绑定的端口正在使用,如将socket绑定到处于TIME_WAIT状态的socket地址)。

监听socket

socket被命名后,还不能马上接受客户连接,需要使用listen函数来创建一个监听队列以存放待处理的客户连接。

1
2
3
4
5
6
7
/*
引用方式: #include <sys/socket.h>
sockfd: 要监听的socket
backlog: 内核监听队列的最大长度, 典型值为5
返回0: 成功 || 返回-1并设置errno: 失败
*/
int listen (int sockfd, int backlog);

在Ubuntu18.04上测试,连接socket的连接最多可以有(backlog+1)个。

接受连接

accept函数从listen监听队列中接受一个连接。

1
2
3
4
5
6
7
8
/*
引用方式: #include <sys/socket.h>
sockfd: 处于listen状态的socket
addr: 用于存放接收连接的socket地址
len: socket地址长度, 可使用sizeof(* addr)
返回新的连接socket: 成功 || 返回-1并设置errno: 失败
*/
int accept (int sockfd, struct sockaddr * addr, socklen_t len);

通过实验可知:accept只是从监听队列中取出连接,而不论连接处于哪种状态,更不关心网络状况的变化。

发起连接

客户端需要使用connect函数来主动连接服务器端的socket。

1
2
3
4
5
6
7
8
/*
引用方式: #include <sys/socket.h>
local_sockfd: 本地用来连接服务端socket的socket文件描述符
serv_addr: 要连接的socket地址
len: socket地址长度, 可使用sizeof(* serv_addr)
返回0: 成功 || 返回-1并设置errno: 失败
*/
int connect (int local_sockfd, const struct sockaddr * serv_addr, socklen_t len);

常见的errno有两种:ECONNREFUSED(目标端口不存在)与ETIMEDOUT(连接超时)。

关闭连接

socket也是文件,因此也可以通过close函数关闭。事实上,close并非总是立即关闭一个连接,而是将socket的引用计数减1;只有当socket的引用计数为0时,才真的关闭连接。

1
2
3
4
5
/*
引用方式: #include <unistd.h>
返回0: 成功 || 返回-1并设置errno: 失败
*/
int close(int fd);

如果要立即终止连接,使用shutdown函数,它是专门为网络编程设计的。

1
2
3
4
5
6
7
8
9
/*
引用方式: #include <sys/socket.h>
howto: 指定shutdown关闭连接的方式;
SHUT_RD: sockfd不可再被读, sockfd接收缓冲区中的数据全部被丢弃;
SHUT_WR: sockfd不可再被写, sockfd发送缓冲区中的数据会在真正关闭前全部发送出去, 连接处于半关闭状态;
SHUT_RDWR: 同时关闭sockfd的读与写;
返回0: 成功 || 返回-1并设置errno: 失败
*/
int shutdown(int sockfd, int howto);