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;
}

目录
相关文章
|
5月前
|
安全 Java API
告别繁琐编码,拥抱Java 8新特性:Stream API与Optional类助你高效编程,成就卓越开发者!
【8月更文挑战第29天】Java 8为开发者引入了多项新特性,其中Stream API和Optional类尤其值得关注。Stream API对集合操作进行了高级抽象,支持声明式的数据处理,避免了显式循环代码的编写;而Optional类则作为非空值的容器,有效减少了空指针异常的风险。通过几个实战示例,我们展示了如何利用Stream API进行过滤与转换操作,以及如何借助Optional类安全地处理可能为null的数据,从而使代码更加简洁和健壮。
146 0
|
4月前
|
网络协议 API Windows
MASM32编程调用 API函数RtlIpv6AddressToString,windows 10 容易,Windows 7 折腾
MASM32编程调用 API函数RtlIpv6AddressToString,windows 10 容易,Windows 7 折腾
|
3月前
|
IDE API 定位技术
Python--API编程:IP地址翻译成实际的物理地址
Python--API编程:IP地址翻译成实际的物理地址
83 0
|
3月前
|
网络协议 测试技术 网络安全
Python编程-Socket网络编程
Python编程-Socket网络编程
35 0
|
5月前
|
JavaScript API 开发者
RESTful API 设计的传奇征程:突破常规,拥抱最佳实践,铸就编程巅峰!
【8月更文挑战第7天】希望通过以上的探讨,能让您对 RESTful API 设计有更深入的理解和认识。
57 5
|
5月前
|
JSON API 数据库
神秘编程力量来袭!Rails 究竟隐藏着怎样的魔力,能构建出强大的 RESTful API?快来一探究竟!
【8月更文挑战第31天】《构建 RESTful API:使用 Rails 进行服务端开发》介绍了如何利用 Ruby on Rails 框架高效构建可扩展的 RESTful API。Rails 采用“约定优于配置”,简化开发流程,通过示例展示了路由定义、控制器设计及模型层交互等内容,帮助开发者快速搭建稳定可靠的服务端。无论小型项目还是大型应用,Rails 均能提供强大支持,提升开发效率。
44 0
|
6月前
|
网络协议 开发者 Python
深度探索Python Socket编程:从理论到实践,进阶篇带你领略网络编程的魅力!
【7月更文挑战第25天】在网络编程中, Python Socket编程因灵活性强而广受青睐。本文采用问答形式深入探讨其进阶技巧。**问题一**: Socket编程基于TCP/IP,通过创建Socket对象实现通信,支持客户端和服务器间的数据交换。**问题二**: 提升并发处理能力的方法包括多线程(适用于I/O密集型任务)、多进程(绕过GIL限制)和异步IO(asyncio)。**问题三**: 提供了一个使用asyncio库实现的异步Socket服务器示例,展示如何接收及响应客户端消息。通过这些内容,希望能激发读者对网络编程的兴趣并引导进一步探索。
70 4
|
6月前
|
开发者 Python
Python Socket编程:不只是基础,更有进阶秘籍,让你的网络应用飞起来!
【7月更文挑战第25天】在网络应用蓬勃发展的数字时代,Python凭借其简洁的语法和强大的库支持成为开发高效应用的首选。本文通过实时聊天室案例,介绍了Python Socket编程的基础与进阶技巧,包括服务器与客户端的建立、数据交换等基础篇内容,以及使用多线程和异步IO提升性能的进阶篇。基础示例展示了服务器端监听连接请求、接收转发消息,客户端连接服务器并收发消息的过程。进阶部分讨论了如何利用Python的`threading`模块和`asyncio`库来处理多客户端连接,提高应用的并发处理能力和响应速度。掌握这些技能,能使开发者在网络编程领域更加游刃有余,构建出高性能的应用程序。
42 3
|
6月前
|
网络协议 Python
网络世界的建筑师:Python Socket编程基础与进阶,构建你的网络帝国!
【7月更文挑战第26天】在网络的数字宇宙中,Python Socket编程是开启网络世界大门的钥匙。本指南将引领你从基础到实战,成为网络世界的建筑师。
72 2
|
6月前
|
网络协议 程序员 视频直播