socket编程之常用api介绍与socket、select、poll、epoll高并发服务器模型代码实现(2)

简介: 主要API函数介绍socket

主要API函数介绍

socket

int socket(int domain, int type, int protocol);

函数描述: 创建socket

参数说明:

  • domain: 协议版本
函数描述: 创建socket
参数说明:
domain: 协议版本
- - AF_
  • type:协议类型
- - SOCK_STREAM 流式, 默认使用的协议是TCP协议
- - SOCK_DGRAM  报式, 默认使用的是UDP协议
  • protocal:
- - 一般填0, 表示使用对应类型的默认协议.
  • 返回值:
- - 成功: 返回一个大于0的文件描述符
- - 失败: 返回-1, 并设置errno


 当调用socket函数以后, 返回一个文件描述符, 内核会提供与该文件描述符相对应的读和写缓冲区, 同时还有两个队列, 分别是请求连接队列和已连接队列(监听文件描述符才有,listenFd)

84bb513317ae4fd58ac21dce93e1cf6a.png

bind

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);


函数描述: 将socket文件描述符和IP,PORT绑定

参数说明:


  • socket: 调用socket函数返回的文件描述符
  • addr: 本地服务器的IP地址和PORT,


struct sockaddr_in serv;
serv.sin_family = AF_INET;
serv.sin_port = htons(8888);
//serv.sin_addr.s_addr = htonl(INADDR_ANY);
//INADDR_ANY: 表示使用本机任意有效的可用IP
inet_pton(AF_INET, "127.0.0.1", &serv.sin_addr.s_addr);
  • addrlen: addr变量的占用的内存大小


返回值:


  • 成功: 返回0
  • 失败: 返回-1, 并设置errno


listen

int listen(int sockfd, int backlog);

函数描述: 将套接字由主动态变为被动态

参数说明:

  • sockfd: 调用socket函数返回的文件描述符
  • backlog: 在linux系统中,这里代表全连接队列(已连接队列)的数量。在unix系统种,这里代表全连接队列(已连接队列)+ 半连接队列(请求连接队列)的总数


返回值:


  • 成功: 返回0
  • 失败: 返回-1, 并设置errno


accept

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);  

函数说明:获得一个连接, 若当前没有连接则会阻塞等待.

函数参数:


  • ockfd: 调用socket函数返回的文件描述符
  • addr: 传出参数, 保存客户端的地址信息
  • addrlen: 传入传出参数, addr变量所占内存空间大小


返回值:


  • 成功: 返回一个新的文件描述符,用于和客户端通信
  • 失败: 返回-1, 并设置errno值.


accept函数是一个阻塞函数, 若没有新的连接请求, 则一直阻塞.

  从已连接队列中获取一个新的连接, 并获得一个新的文件描述符, 该文件描述符用于和客户端通信. (内核会负责将请求队列中的连接拿到已连接队列中)

connect

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);


函数说明: 连接服务器

函数参数:


  • sockfd: 调用socket函数返回的文件描述符
  • addr: 服务端的地址信息
  • addrlen: addr变量的内存大小


返回值:

  • 成功: 返回0
  • 失败: 返回-1, 并设置errno值


读取和发送数据

接下来就可以使用write和read函数进行读写操作了。除了使用read/write函数以外, 还可以使用recv和send函数。

ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
ssize_t send(int sockfd, const void *buf, size_t len, int flags); 
//对应recv和send这两个函数flags直接填0就可以了


注意: 如果写缓冲区已满, write也会阻塞, read读操作的时候, 若读缓冲区没有数据会引起阻塞。


高并发服务器模型-select

select介绍

  多路IO技术: select, 同时监听多个文件描述符, 将监控的操作交给内核去处理

int select(int nfds, fd_set * readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);


数据类型fd_set::文件描述符集合——本质是位图

函数介绍: 委托内核监控该文件描述符对应的读,写或者错误事件的发生

参数说明:


  • nfds: 最大的文件描述符+1
  • readfds: 读集合, 是一个传入传出参数

    传入: 指的是告诉内核哪些文件描述符需要监控
    传出: 指的是内核告诉应用程序哪些文件描述符发生了变化
  • writefds: 写文件描述符集合(传入传出参数,同上)
  • execptfds: 异常文件描述符集合(传入传出参数,同上)
  • timeout:

    NULL--表示永久阻塞, 直到有事件发生
    0   --表示不阻塞, 立刻返回, 不管是否有监控的事件发生
    >0  --到指定事件或者有事件发生了就返回
  • 返回值: 成功返回发生变化的文件描述符的个数。失败返回-1, 并设置errno值。


select-api

将fd从set集合中清除


void FD_CLR(int fd, fd_set *set);

功能描述: 判断fd是否在集合中

返回值: 如果fd在set集合中, 返回1, 否则返回0

int FD_ISSET(int fd, fd_set *set);

将fd设置到set集合中

void FD_SET(int fd, fd_set *set);

初始化set集合

void FD_ZERO(fd_set *set);

用select函数其实就是委托内核帮我们去检测哪些文件描述符有可读数据,可写,错误发生

int select(int nfds, fd_set * readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

select优缺点

select优点:

  1. select支持跨平台

select缺点:


1.代码编写困难

2.会涉及到用户区到内核区的来回拷贝

3.当客户端多个连接, 但少数活跃的情况, select效率较低(例如: 作为极端的一种情况, 3-1023文件描述符全部打开, 但是只有1023有发送数据, select就显得效率低下)

4.最大支持1024个客户端连接(select最大支持1024个客户端连接不是有文件描述符表最多可以支持1024个文件描述符限制的, 而是由FD_SETSIZE=1024限制的)


FD_SETSIZE=1024 fd_set使用了该宏, 当然可以修改内核, 然后再重新编译内核, 一般不建议这么做

select代码实现

#include <errno.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/poll.h>
#include <sys/epoll.h>
#include <pthread.h>
#define MAX_LEN  4096
int main(int argc, char **argv) {
    int listenfd, connfd, n;
    struct sockaddr_in svr_addr;
    char buff[MAX_LEN];
    if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
        printf("create socket error: %s(errno: %d)\n", strerror(errno), errno);
        return 0;
    }
    memset(&svr_addr, 0, sizeof(svr_addr));
    svr_addr.sin_family = AF_INET;
    svr_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    svr_addr.sin_port = htons(8081);
    if (bind(listenfd, (struct sockaddr *) &svr_addr, sizeof(svr_addr)) == -1) {
        printf("bind socket error: %s(errno: %d)\n", strerror(errno), errno);
        return 0;
    }
    if (listen(listenfd, 10) == -1) {
        printf("listen socket error: %s(errno: %d)\n", strerror(errno), errno);
        return 0;
    }
    //select
    fd_set rfds, rset, wfds, wset;
    FD_ZERO(&rfds);
    FD_ZERO(&wfds);
    FD_SET(listenfd, &rfds);
    int max_fd = listenfd;
    while (1) {
        rset = rfds;
        wset = wfds;
        int nready = select(max_fd + 1, &rset, &wset, NULL, NULL);
        if (FD_ISSET(listenfd, &rset)) { //
            struct sockaddr_in clt_addr;
            socklen_t len = sizeof(clt_addr);
            if ((connfd = accept(listenfd, (struct sockaddr *) &clt_addr, &len)) == -1) {
                printf("accept socket error: %s(errno: %d)\n", strerror(errno), errno);
                return 0;
            }
            FD_SET(connfd, &rfds);
            if (connfd > max_fd) max_fd = connfd;
            if (--nready == 0) continue;
        }
        int i = 0;
        for (i = listenfd + 1; i <= max_fd; i++) {
            if (FD_ISSET(i, &rset)) { //
                n = recv(i, buff, MAX_LEN, 0);
                if (n > 0) {
                    buff[n] = '\0';
                    printf("recv msg from client: %s\n", buff);
                    FD_SET(i, &wfds);
                }
                else if (n == 0) { //
                    FD_CLR(i, &rfds);
                    close(i);
                }
                if (--nready == 0) break;
            }
            else if (FD_ISSET(i, &wset)) {
                send(i, buff, n, 0);
                FD_SET(i, &rfds);
                FD_CLR(i, &wfds);
            }
        }
    }
    close(listenfd);
    return 0;
}

目录
相关文章
|
29天前
|
JSON 安全 API
构建高效后端API:最佳实践与代码示例
【8月更文挑战第2天】 在数字化时代,后端API是连接数据与用户的桥梁。本文深入探讨了如何设计并实现高效的后端API,从理论到实践,提供了实用的技巧和代码示例。通过阅读本篇文章,你将学会如何避免常见的陷阱,优化你的API性能,从而提供更加流畅的用户体验。
|
6天前
|
API 开发工具 网络架构
【Azure Developer】如何通过Azure Portal快速获取到对应操作的API并转换为Python代码
【Azure Developer】如何通过Azure Portal快速获取到对应操作的API并转换为Python代码
|
27天前
|
Java API 数据格式
Spring Boot API参数读取秘籍大公开!6大神器助你秒变参数处理大师,让你的代码飞起来!
【8月更文挑战第4天】Spring Boot凭借其便捷的开发和配置特性,成为构建微服务的热门选择。高效处理HTTP请求参数至关重要。本文介绍六种核心方法:查询参数利用`@RequestParam`;路径变量采用`@PathVariable`;请求体通过`@RequestBody`自动绑定;表单数据借助`@ModelAttribute`或`@RequestParam`;请求头使用`@RequestHeader`;Cookie则依靠`@CookieValue`。每种方法针对不同场景,灵活运用可提升应用性能与用户体验。
41 9
|
2月前
|
JSON Shell API
阿里云PAI-Stable Diffusion开源代码浅析之所有api的入参如何看
阿里云PAI-Stable Diffusion开源代码浅析之所有api的入参如何看
|
2月前
|
Java
如何在Java中实现多线程的Socket服务器?
在Java中,多线程Socket服务器能同时处理多个客户端连接以提升并发性能。示例代码展示了如何创建此类服务器:监听指定端口,并为每个新连接启动一个`ClientHandler`线程进行通信处理。使用线程池管理这些线程,提高了效率。`ClientHandler`读取客户端消息并响应,支持简单的文本交互,如发送欢迎信息及处理退出命令。
|
29天前
|
JavaScript 前端开发 中间件
打造卓越后端:构建高效API的最佳实践与实战代码示例——解锁高性能Web服务的秘密
【8月更文挑战第2天】构建高效后端API:最佳实践与代码示例
49 0
|
2月前
|
运维 Devops API
阿里云云效操作报错合集之直接用API调用可以使用,但是本地用Go代码调用失败,是什么导致的?
本合集将整理呈现用户在使用过程中遇到的报错及其对应的解决办法,包括但不限于账户权限设置错误、项目配置不正确、代码提交冲突、构建任务执行失败、测试环境异常、需求流转阻塞等问题。阿里云云效是一站式企业级研发协同和DevOps平台,为企业提供从需求规划、开发、测试、发布到运维、运营的全流程端到端服务和工具支撑,致力于提升企业的研发效能和创新能力。
|
2月前
|
运维 Devops API
阿里云云效操作报错合集之调用旧版API添加代码源时遇到500错误,是什么导致的
本合集将整理呈现用户在使用过程中遇到的报错及其对应的解决办法,包括但不限于账户权限设置错误、项目配置不正确、代码提交冲突、构建任务执行失败、测试环境异常、需求流转阻塞等问题。阿里云云效是一站式企业级研发协同和DevOps平台,为企业提供从需求规划、开发、测试、发布到运维、运营的全流程端到端服务和工具支撑,致力于提升企业的研发效能和创新能力。
|
16天前
|
机器人 API Python
智能对话机器人(通义版)会话接口API使用Quick Start
本文主要演示了如何使用python脚本快速调用智能对话机器人API接口,在参数获取的部分给出了具体的获取位置截图,这部分容易出错,第一次使用务必仔细参考接入参数获取的位置。
|
1天前
|
存储 JSON API
淘系API接口(解析返回的json数据)商品详情数据解析助力开发者
——在成长的路上,我们都是同行者。这篇关于商品详情API接口的文章,希望能帮助到您。期待与您继续分享更多API接口的知识,请记得关注Anzexi58哦! 淘宝API接口(如淘宝开放平台提供的API)允许开发者获取淘宝商品的各种信息,包括商品详情。然而,需要注意的是,直接访问淘宝的商品数据API通常需要商家身份或开发者权限,并且需要遵循淘宝的API使用协议。
淘系API接口(解析返回的json数据)商品详情数据解析助力开发者
下一篇
云函数