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天前
|
安全 Java 数据处理
Python网络编程基础(Socket编程)多线程/多进程服务器编程
【4月更文挑战第11天】在网络编程中,随着客户端数量的增加,服务器的处理能力成为了一个重要的考量因素。为了处理多个客户端的并发请求,我们通常需要采用多线程或多进程的方式。在本章中,我们将探讨多线程/多进程服务器编程的概念,并通过一个多线程服务器的示例来演示其实现。
|
29天前
|
程序员 开发者 Python
Python网络编程基础(Socket编程) 错误处理和异常处理的最佳实践
【4月更文挑战第11天】在网络编程中,错误处理和异常管理不仅是为了程序的健壮性,也是为了提供清晰的用户反馈以及优雅的故障恢复。在前面的章节中,我们讨论了如何使用`try-except`语句来处理网络错误。现在,我们将深入探讨错误处理和异常处理的最佳实践。
|
29天前
|
Python
Python网络编程基础(Socket编程) 使用try-except处理网络错误
【4月更文挑战第11天】在网络编程中,错误处理和异常管理是非常重要的部分。网络操作经常因为各种原因而失败,比如网络断开、服务器无响应、地址不正确等。因此,学会如何使用Python的异常处理机制来捕获和处理这些错误,是编写健壮的网络应用的关键。
|
1月前
|
网络协议 网络安全 Python
Python网络编程基础(Socket编程) 错误处理和异常
【4月更文挑战第10天】网络编程涉及到很多复杂的操作和潜在的风险,如连接失败、数据丢失、超时等问题。因此,正确的错误处理和异常捕获是确保网络程序稳定性和可靠性的关键。本章将介绍网络编程中常见的错误和异常,并探讨如何在Python中进行有效的错误处理。
|
1月前
|
存储 Python
Python网络编程基础(Socket编程) UDP 发送和接收数据
【4月更文挑战第10天】对于UDP客户端而言,发送数据是一个相对简单的过程。首先,你需要构建一个要发送的数据报,这通常是一个字节串(bytes)。然后,你可以调用socket对象的`sendto`方法,将数据报发送到指定的服务器地址和端口。
|
1月前
|
存储 Python
Python网络编程基础(Socket编程)UDP客户端编程
【4月更文挑战第9天】在UDP通信中,客户端负责发送数据到服务器,并接收来自服务器的响应。与服务器不同,客户端通常不需要绑定到特定的地址和端口,因为它可以临时使用任何可用的端口来发送数据。下面,我们将详细讲解UDP客户端编程的基本步骤。
|
1月前
|
Python
Python网络编程基础(Socket编程)绑定地址和端口
【4月更文挑战第9天】在UDP服务器编程中,我们首先需要创建一个UDP套接字,然后绑定一个本地地址和端口,以便客户端可以通过这个地址和端口与我们的服务器进行通信。下面,我们将详细讲解如何绑定地址和端口。
|
28天前
|
网络协议 Linux Python
Python网络编程基础(Socket编程)epoll在Linux下的使用
【4月更文挑战第12天】在上一节中,我们介绍了使用`select`模块来实现非阻塞IO的方法。然而,`select`模块在处理大量并发连接时可能会存在性能问题。在Linux系统中,`epoll`机制提供了更高效的IO多路复用方式,能够更好地处理大量并发连接。
|
1天前
|
网络协议 Dubbo Java
【网络编程】理解客户端和服务器并使用Java提供的api实现回显服务器
【网络编程】理解客户端和服务器并使用Java提供的api实现回显服务器
8 0
|
3天前
|
存储 算法 网络协议
【探索Linux】P.26(网络编程套接字基本概念—— socket编程接口 | socket编程接口相关函数详细介绍 )
【探索Linux】P.26(网络编程套接字基本概念—— socket编程接口 | socket编程接口相关函数详细介绍 )
10 0