💐 🌸 🌷 🍀 🌹 🌻 🌺 🍁 🍃 🍂 🌿 🍄🍝 🍛 🍤
📃个人主页 :阿然成长日记 👈点击可跳转
📆 个人专栏: 🔹数据结构与算法🔹C语言进阶🔹C++🔹Liunx
🚩 不能则学,不知则问,耻于问人,决无长进
🍭 🍯 🍎 🍏 🍊 🍋 🍒 🍇 🍉 🍓 🍑 🍈 🍌 🍐 🍍
文章目录
- [一、 什么是socket套接字](#一、 什么是socket套接字)
- 二、Socket函数接口详解
-
- [套接字的分类及struct sockaddr*](#套接字的分类及struct sockaddr*)
- 1.socket
- 2.bind
- 3.listen
- 4.accept
- 5.connet
- 6.读写操作
- 7.close()函数
- 8.字节序转换接口
- 9.IP地址格式转换函数
一、 什么是socket套接字
我们使用一张图片很形象的可以看出socket所处的位置。
从用户层面来看:
从上图看出,Socket处于应用层 与传输层 的中间软件抽象层,它是一组接口。并且前面我们也学过,运输层和网络层其实属于操作系统内层面,我们只给用户提供使用的接口即可,不可能让用户能直接访问到内核部分。
在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。
从操作系统层面来看:
linux下一切皆文件!!!
所以本质来说Socket即是一种特殊的文件
,一些socket函数【bind()、listen()等等】,就是对这个文件进行的操作(读/写IO、打开、关闭)
二、Socket函数接口详解
cpp
// 创建 socket 文件 (TCP/UDP, 客户端 + 服务器)
int socket(int domain, int type, int protocol);
// 绑定端口号 (TCP/UDP, 服务器)
int bind(int socket, const struct sockaddr *address,socklen_t address_len);
// 开始监听socket (TCP, 服务器)
int listen(int socket, int backlog);
// 接收请求 (TCP, 服务器)
int accept(int socket, struct sockaddr* address,socklen_t* address_len);
// 建立连接 (TCP, 客户端)
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
上面的参数中带有 struct sockaddr*结构体,这是个什么呢?
首先我们要知道socket的英文为插座,而我们的ip+端口号可以决定是哪一台主机上的哪一个进程,相当于一个插头,谁插入这个socket插槽,谁就可以与之通信。
套接字的分类及struct sockaddr*
1.网络套接字
2.原始套接字
3.unix域间套接字
这个struct sockaddr*结构体是一个父类一般还有两个衍生的子类,他们一个sockaddr_in
(inet,网络通信)主要用于网络间的通信同时也支持本地,另一个sockaddr_un
(域间套接字)只能在本地通信。而原始套接字可以跨过传输层(TCP/IP协议)访问底层的数据
cpp
网络套接字
struct sockaddr_in {
short int sin_family; // 地址族,一般为AF_INET(IPv4)和AF_INET6(IPv6)
unsigned short int sin_port; // 端口号,网络字节序
struct in_addr sin_addr; // IP地址
unsigned char sin_zero[8];// 用于填充,使sizeof(sockaddr_in)等于16
};
原始套接字
struct sockaddr {
sa_family_t sa_family; /* address family, AF_xxx */
char sa_data[14];//sa_data是一个字节数组,用于存储地址和端口号信息的具体内容,具体内容的长度和格式依赖于协议族的不同。
};
unix域间套接字
struct sockaddr_un {
sa_family_t sun_family; /* AF_UNIX */
char sun_path[108]; /* 带有路径的文件名 */
};//通过同一个文件的路径来让进程看到同一份资源
其中还有一个结构体是struct in_addr
,这个函数内部如下:
cpp
struct in_addr {
uint32_t s_addr; /* address in network byte order */
};
简单来说原始套接字
使用了一个sa_data数组将IP和端口号存储在了一起,很多网络编程函数诞生早于IPv4协议,那时候都使用的是sockaddr结构体。但是sockaddr的缺陷是:sa_data把目标地址和端口信息混在一起了。IPv4为了解决这个问题,于是便设计了sockaddr_in专门用于网络通信
结构体,把port和ip分开来存储。
sockaddr_in 是用于 IPv4 地址的特定地址结构体。它扩展了 sockaddr,并提供了 IPv4 地址(通过 sin_addr 字段存储)和端口号(通过 sin_port 字段存储)的字段。sockaddr_in 使用的是网络字节序(大端字节序
)来存储这些值。如果不知道网络字节序请搜索一下。
1.socket
格式:
cpp
int socket(int domain, int type, int protocol);
参数:
domain
:指定套接字的协议族,如AF_INET(IPv4)或AF_INET6(IPv6)。
type
:指定套接字的类型。
SOCK_STREAM(面向连接的流套接字)
SOCK_DGRAM(无连接的数据报套接字)。
protocol
:指定使用的传输协议,通常可以设置为0以自动选择合适的协议。
返回值:返回一个int型的数字来表示套接字
作用 :创建一个套接字
,将domain、type和protocol参数传递给socket()函数以指定套接字的特性,并返回一个int型套接字描述符
,用于后续的操作。
2.bind
格式:
cpp
int bind(int socket, const struct sockaddr *addr,socklen_t addrlen);
参数:
sockfd
:要绑定的套接字描述符,也就是socket函数的返回值addr
:是一个指向 sockaddr 结构的指针,该结构包含了要绑定的IP地址和端口号。addrlen
:指定addr结构体的大小。
返回值:
- bind函数的返回值用于指示操作是否成功:
如果 bind 函数成功执行,它将返回 0。
如果 bind 函数执行失败,它将返回 -1,并设置全局变量 errno 以指示错误的具体原因。
作用:bind函数用于将一个套接字(socket)与特定的IP地址和端口号绑定
3.listen
格式:
cpp
int listen(int socket, int backlog)
参数:
socket
:要设置为监听状态的套接字描述符。backlog
:指定等待连接队列的最大长度,用于限制同时可以等待处理的连接请求的数目
返回值:
- 成功时:当listen函数成功地将套接字置于监听状态时,它返回0。这表示服务器已经准备好接受客户端的连接请求。
- 失败时:如果listen函数执行失败,它将返回-1,并设置全局变量errno以指示错误的具体原因。此时,可以通过检查errno的值来确定错误类型,并据此采取相应的错误处理措施。
错误码:
EADDRINUSE: | 表示地址已被使用,即尝试监听的端口已被其他进程占用。 |
---|---|
EINVAL: | 表示无效的参数,可能是因为套接字未绑定地址,或者该套接字已被连接(对于某些类型的套接字,如SOCK_DGRAM,监听操作可能不被允许)。 |
ENOTSOCK: | 表示文件描述符不是一个套接字。 |
WSAENETDOWN(Windows特有): | 表示网络子系统失效。 |
WSAEINPROGRESS(Windows特有): | 表示一个阻塞的套接字调用正在运行中。 |
WSAEMFILE(Windows特有): | 表示无可用文件描述符。 |
WSAENOBUFS(Windows特有): | 表示无可用缓冲区空间。 |
作用: 将套接字设置为监听状态,开始接受客户端的连接请求。通过指定backlog参数,可以控制连接队列的长度。
4.accept
格式:
cpp
int accept(int socket, struct sockaddr* address,
socklen_t* address_len)
参数:
sockfd
:监听 套接字描述符。addr
:用于存储客户端的地址信息的结构体指针,可以是struct sockaddr、struct sockaddr_in或struct sockaddr_in6等类型的指针。addrlen
:指向一个整数变量,用于传递addr结构体的大小,并在接受连接后更新为实际的地址长度。
返回值:
-
成功时的返回值
当accept函数成功接受一个客户端的连接请求时,它会返回一个非负值。这个非负值就是新创建的socket文件描述符或对象的标识符,用于后续的通信操作。
-
失败时的返回值
如果accept函数在尝试接受连接请求时遇到错误(例如,监听socket已经被关闭),它将返回-1,并设置全局变量errno以指示错误的具体原因。此时,可以通过检查errno的值来确定错误类型,并据此采取相应的错误处理措施。
作用: 等待并接受客户端的连接请求,并返回一个新的套接字描述符,该描述符用于与客户端进行通信。同时,可以获取客户端的地址信息
5.connet
格式:
cpp
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
参数:
- sockfd:要进行连接的套接字描述符。
- addr:要连接的目标地址的结构体指针,该结构包含了要绑定的IP地址和端口号。
- addrlen:指定addr结构体的大小。
返回值:
- 成功时的返回值
当connect函数成功地将客户端套接字与服务器套接字连接起来时,它返回0。这表示连接已经成功建立,客户端可以继续通过该套接字发送和接收数据。 - 失败时的返回值
如果connect函数在尝试建立连接时遇到错误(如网络不可达、服务器未运行、服务器拒绝连接等),它将返回-1,并设置全局变量errno以指示错误的具体原因。此时,可以通过检查errno的值来确定错误类型,并据此采取相应的错误处理措施。
错误码
ECONNREFUSED: | 表示连接被远程计算机拒绝。这通常意味着没有服务在目标端口上监听,或者服务器程序拒绝连接请求。 |
---|---|
ENETUNREACH: | 表示网络不可达。这可能是因为网络线路故障、路由表配置错误或远程主机不可达等原因。 |
ETIMEDOUT: | 表示连接超时。这可能是因为远程主机没有响应连接请求,或者连接请求在传输过程中被延迟或丢失。 |
EINPROGRESS(在非阻塞模式下): | 表示连接操作正在进行中。在非阻塞模式下,connect函数可能无法立即完成连接操作,此时将返回-1,并将errno设置为EINPROGRESS。在这种情况下,需要调用其他函数(如select、poll或epoll)来检查连接是否成功建立。 |
作用: 与另一个套接字建立连接,通常用于客户端连接服务器。通过指定目标地址和端口号,使套接字能够与目标进行通信。
6.读写操作
以下函数均可读取Socket数据
cpp
ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
主要来说说recvfrom和sendto函数
recvfrom参数中的addr的意义就是获取发送发放的信息,便于之后使用sento发送时使用。
7.close()函数
在服务器与客户端建立连接之后,会进行一些读写操作,完成了读写操作就要关闭相应的socket描述字,好比操作完打开的文件要调用fclose关闭打开的文件。
8.字节序转换接口
#include<arpa/inet.h>
cpp
uint32_t htonl(uint32_t hostlong); //32位整数从主机字节序转换为网络字节序
uint16_t htons(uint16_t hostshort); //16位整数从主机字节序转换为网络字节序
uint32_t ntohl(uint32_t netlong): //32位整数从网络字节序转换为主机字节序
uint16_t ntohs(uint16_t netshort); //16位整数从网络字节序转换为主机字节序
9.IP地址格式转换函数
cpp
//点分十进制的IP地址字符串转换成in_addr_t类型
in_addr_t inet_addr(const char *cp)
//将结构struct in_addr中的二进制IP地址转换为一个点分十进制
char *inet_ntoa(struct in_addr in)