Socket网络编程

一、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这三种多路复用技术的原理

IO多路复用之epoll

C语言实现QQ聊天室小项目 [完整源码]

Linux网络编程基础及多线程并发案例

好文推荐: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多路复用
④内部数据结构

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员爱德华

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值