文章目录
一、Socket网络编程的原理
1.步骤
①创建Socket:socket()
②绑定地址:bind()
③监听连接请求:listen()
④接受连接:accept()
⑤发起连接:connect()
⑥发送数据send()、接收数据revc()
⑦关闭连接:close()
2.函数详解
(1)创建Socket:socket()
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
- 作用:创建一个新的Socket。
- 参数:
- domain:指定通信协议族,如AF_INET(IPv4)或AF_INET6(IPv6)。
- type:指定Socket的类型,如SOCK_STREAM(TCP)或SOCK_DGRAM(UDP)。
- protocol:指定协议,通常为0,表示使用默认协议。
- 返回值:成功返回Socket描述符,失败返回-1。
1.参数
int socket(int address_family, int type, int protocol);
(1)address family:
①AF_INET:IPv4 网络协议。用于TCP/IP和UDP/IP网络通信。
②AF_INET6:IPv6 网络协议。用于TCP/IP和UDP/IP网络通信,但支持IPv6地址。
③AF_UNIX(或AF_LOCAL):本地通道通信。用于在同一台机器上的进程间通信。
2)type:
①SOCK_STREAM:TCP协议,提供面向连接的稳定数据传输,保证数据能够按顺序、完整地到达。
②SOCK_DGRAM:UDP协议,提供无连接的数据传输服务。发送的是独立的消息,不保证顺序或数据完整性。
③SOCK_RAW:提供原始网络协议访问。在网络模型中,这种类型的套接字允许直接访问IP层,通常用于网络协议的开发和测试。
(3)protocol:默认协议填0
2.返回值:
①成功时,socket() 返回一个非负整数,即新创建的套接字文件描述符。
②出错时,返回 -1,并设置全局变量 errno 以表示具体的错误类型。
3.创建一个使用IPv4地址和TCP协议的套接字:
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == -1) {
perror("socket creation failed");
}
这里,AF_INET 指定使用IPv4地址,SOCK_STREAM 指定使用面向连接的数据传输方式(TCP),0 表示自动选择使用TCP协议。
(2)绑定地址:bind()
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
- 作用:将Socket绑定到一个地址。
- 参数:
- sockfd:Socket描述符。
- addr:指向要绑定的地址结构的指针。
- addrlen:地址结构的长度。
- 返回值:成功返回0,失败返回-1。
(3)监听连接请求:listen() [仅适用于TCP]
#include <sys/socket.h>
int listen(int sockfd, int backlog);
- 作用:开始监听连接请求。
- 参数:
- sockfd:Socket描述符。
- backlog:等待连接队列的最大长度。
- 返回值:成功返回0,失败返回-1。
(4)接受连接:accept()
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
- 作用:接受客户端的连接请求。
- 参数:
- sockfd:Socket描述符。
- addr:用于存储客户端地址信息的结构体指针。
- addrlen:客户端地址结构体的长度。
- 返回值:成功返回一个新的Socket描述符,失败返回-1。
(5)发起连接:connect() [仅适用于TCP客户端]
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
- 作用:TCP客户端向服务器端发起连接请求。
- 参数:
- sockfd:Socket描述符。
- addr:指向服务器端地址结构的指针。
- addrlen:地址结构的长度。
- 返回值:成功返回0,失败返回-1。
#include <sys/types.h>
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
connect()成功返回0,失败返回-1
以下是一个简单的 TCP 客户端示例,展示了如何使用 connect() 连接到服务器:
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main() {
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0) {
perror("Socket creation failed");
return -1;
}
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(12345); // 服务器监听的端口
inet_pton(AF_INET, "192.168.1.100", &server_addr.sin_addr); // 服务器的IP地址
if (connect(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
perror("Connection failed");
return -1;
}
printf("Connected to the server\n");
// 之后可以使用sockfd进行数据传输
close(sockfd); // 关闭套接字
return 0;
}
在这个示例中,客户端程序创建了一个套接字,设置服务器的 IP 地址和端口,然后尝试与服务器建立连接。如果 connect() 调用成功,客户端就与服务器建立了连接,并可以通过该套接字进行数据通信。
(6)发送数据send()
#include <sys/types.h>
#include <sys/socket.h>
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
- 作用:发送数据。
- 参数:
- sockfd:Socket描述符。
- buf:指向要发送数据的缓冲区。
- len:要发送的数据长度。
- flags:传输控制标志,通常为0。
- 返回值:成功返回发送的字节数,失败返回-1
发送数据:将数据放到发送缓冲区。由内核决定什么时候将数据发送出去。
(7)接收数据revc()
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
- 作用:接收数据。
- 参数:
- sockfd:Socket描述符。
- buf:用于存储接收数据的缓冲区。
- len:缓冲区的大小。
- flags:接收控制标志,通常为0。
- 返回值:成功返回接收的字节数,失败返回-1。
接收数据:当数据送到Linux内核后,数据不是立即给到应用程序,而是放在接收缓冲区,等应用程序什么时候调用recv()函数,什么时候才由内核给到应用程序。
(8)关闭Socket连接:close()
#include <unistd.h>
int close(int sockfd);
- 作用:关闭Socket连接。
- 参数:
- sockfd:Socket描述符。
- 返回值:成功返回0,失败返回-1。
3.TCP
4.UDP
二、socket 套接字
socket套接字:(IP:port)
①IP用来定位是哪台电脑,端口号用来定位是哪个进程
②socket_fd 是 int类型的数字,故套接字socket可理解为一套用于连接的数字
三、多线程
多线程的弊端:需要CPU上下文切换
四、IO模型
1.阻塞式IO模型
就等着你反馈。
程序查询方式(独占查询)
2.非阻塞式IO模型
轮询检查
程序查询方式(定时查询)
3.IO多路复用
1.定义:IO多路复用,是一种同步IO模型。
①IO:操作系统中,数据在用户态和内核态之间的读写操作
②多路:多个TCP连接 (多个socket)
③复用:一个或多个线程资源
④IO多路复用:一个或多个线程处理多个TCP连接
2.作用:利用IO多路复用,可以实现一个线程监控多个文件句柄。
3.IO多路复用的使用场景:要设计一个高性能的网络服务器。
这个高性能服务器可以供多个客户端同时连接,并且能处理这些客户端传上来的请求。
4.实现IO多路复用的三种模型:
①select
②poll
③epoll
对于待检测集合select和poll是基于线性方式处理的,epoll是基于红黑树来管理待检测集合的。
select和poll每次都会线性扫描整个待检测集合,集合越大速度越慢,epoll使用的是回调机制,效率高,处理效率也不会随着检测集合的变大而下降
腾讯面试,请描述select、poll、epoll这三种多路复用技术的原理
好文推荐:Linux IO模式及 select、poll、epoll详解
五、select、poll、epoll
1.select
1.原理
select采用轮询 + 遍历方式:遍历bitmap,将需要监听的文件描述符置1,不需要监听的文件描述符置0
三种fd:写描述符、读描述符、异常描述符
select会监视这三种文件描述符,当数据可读/可写/异常/超时 时会返回。返回后通过遍历fdset(文件描述符的集合)来找到就绪的fd,去触发相应的IO操作。
2.优点
3.缺点:
①文件描述表为bitmap结构,长度最大为1024
②fdset不可重用,每次循环遍历都得重新创建
③频繁的用户态和内核态拷贝,性能开销较大
④需要对文件描述符表进行遍历,轮询时间复杂度为O(n)
4.代码
5.模型演示
2.poll
1.原理:
poll也是采用轮询 + 遍历方式,但poll采用的是链表方式来存储fd
2.优缺点
3.代码
//pollfd结构体原型
struct pollfd{
int fd; //文件描述符
short events; //注册的事件,用户关注的事件,如读写事件
short revents; //实际发生的事件,由内核填充。系统内核填充并返回的事件
};
4.模型演示
3.epoll
epoll模型是为了高效地处理大量并发连接
1.原理
epoll采用事件通知机制,将轮询改成了回调。
epoll的底层数据结构是红黑树 + 双向链表。
①epoll_create():在系统启动时,在Linux内核中申请一个红黑树结构的文件系统,再返回epoll对象(fd对象)
②epoll_ctl():创建客户端和服务器端的连接,并把连接注册到eventpoll中。
在每新建一个连接的时候,同步更新epoll对象中的fd,并绑定一个callback()回调函数。
通过 epoll_ctl() 将 sockfd 添加到 epfd 的监控列表中,并指定我们感兴趣的事件是 EPOLLIN(即套接字可读,通常表示有新的连接请求)。这样,当 sockfd 上发生了 EPOLLIN 事件时,epoll_wait() 将能够检测到这一事件。
③epoll_wait():检查双向链表list中是否有已就绪的事件,若有则立刻返回不阻塞,为空则阻塞等待。
轮询所有的callback集合,并触发对应的IO操作。
分布式系统中常用的组件,Redis、Nginx都采用epoll模型
2.优缺点
3.代码
4.模型演示
4.三者对比
select
poll
epoll
六、Redis
Redis为什么这么快?
①内存
②单线程
③IO多路复用
④内部数据结构