网络编程与select/poll/epoll服务器的实现(1)

简介: 什么是网络编程?    本部分主要是介绍socket网络编程的基本API——并展示一个服务器与客户端连接的具体流程是如何的实现一个一对一的网络服务器程序

什么是网络编程?

    本部分主要是介绍socket网络编程的基本API——并展示一个服务器与客户端连接的具体流程是如何的实现一个一对一的网络服务器程序

要对网络编程进行一个较为深入的学习,还是要费不少笔墨的,具体的详细内容可以查看博主以前写过的一个专栏进行学习:socket编程或者网络编程。相信看完博主之前的介绍大家会对网络编程有一定的认识,所以我们就紧接着之前知识点继续介绍,之前时实现了一个回声服务器,现在将其升级为更高效的服务器。

网络编程效果演示

本部分要演示的效果如下:


演示什么是阻塞什么是非阻塞——socket设置非阻塞的差别演示为何要加while——反复读与读一次

演示多个client连接一个server一个接口发数据,其他client会被卡主的情况演示一请求一线程的情况

演示server不close的状态——不停地打印信息

阻塞与非阻塞的区别

下面将用代码来展示在socket编程中阻塞与非阻塞的区别。

阻塞状态一个server对应一个client

#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#define BUFFER_LENGTH 1024
int main()
{
  // 创建socket
  int sockfd = socket(AF_INET, SOCK_STREAM, 0); 
  struct sockaddr_in servaddr;
  memset(&servaddr, 0, sizeof(struct sockaddr_in)); // 清空
  servaddr.sin_family = AF_INET;  // 设置协议族为 IPV4
  servaddr.sin_addr.s_addr = htonl(INADDR_ANY); // 0.0.0.0
  servaddr.sin_port = htons(9999);  // 监听端口为 9999
  // 绑定好初始化的信息,出错就打印出错信息
  if (-1 == bind(sockfd, (struct sockaddr *)&servaddr, sizeof(struct sockaddr)))
  {
    printf("bind failed: %s", strerror(errno));
    return -1;
  }
  // 监听绑定好的信息
  listen(sockfd, 10);
  // 为了保存 client的信息锁创建的结构
  struct sockaddr_in clientaddr;
  socklen_t len = sizeof(clientaddr);
  // 此时的 clientfd就是用来和 client通信的socket
  int clientfd = accept(sockfd, (struct sockaddr *)&clientaddr, &len);
  printf("accept,the clientfd is %d\n",clientfd);
  char buffer[BUFFER_LENGTH] = {0};
  int ret = recv(clientfd, buffer, BUFFER_LENGTH, 0); // 接收client发来的信息
  printf("ret: %d, buffer: %s\n", ret, buffer); // 本地打印client
  send(clientfd, buffer, ret, 0); // 将信息传给client
  return 0;
}

运行结果

连接之前

可以看到当没有client连接到server端的时候,server程序会自动阻塞起来不进行输出。

7c0b32f531ed4b9d881d88b57fb7e5b4.png

点击连接之后,并发送信息

可以看到当client连接到server之后,server端会打印连接的信息,并将连接的信息返回给client端,并结束程序,自动关闭程序。

313185ec037f444692ffe2f8d82ac7e0.png

非阻塞状态一个server对应一个client

此时不会等待accept连接
核心代码
 // 将socket设置为非阻塞
   int flags = fcntl(sockfd, F_GETFL, 0);
   flags |= O_NONBLOCK;
   fcntl(sockfd, F_SETFL, flags);
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <fcntl.h>
#include <pthread.h>
#include <sys/poll.h>
#include <sys/epoll.h>
#define BUFFER_LENGTH   1024
#define POLL_SIZE     1024
int main() {
   // 创建socket
   int sockfd = socket(AF_INET, SOCK_STREAM, 0); 
   struct sockaddr_in servaddr;
   memset(&servaddr, 0, sizeof(struct sockaddr_in)); // 清空
   servaddr.sin_family = AF_INET; // 设置协议族为 IPV4
   servaddr.sin_addr.s_addr = htonl(INADDR_ANY); // 0.0.0.0
   servaddr.sin_port = htons(9999); // 监听端口为 9999
   // 绑定好初始化的信息,出错就打印出错信息
   if (-1 == bind(sockfd, (struct sockaddr *)&servaddr, sizeof(struct sockaddr)))
   {
    printf("bind failed: %s", strerror(errno));
    return -1;
   }
   // 监听绑定好的信息
   listen(sockfd, 10);
   // 将socket设置为非阻塞
   int flags = fcntl(sockfd, F_GETFL, 0);
   flags |= O_NONBLOCK;
   fcntl(sockfd, F_SETFL, flags);
   // 为了保存 client的信息锁创建的结构
   struct sockaddr_in clientaddr;
   socklen_t len = sizeof(clientaddr);
   // 此时的 clientfd就是用来和 client通信的socket
   int clientfd = accept(sockfd, (struct sockaddr *)&clientaddr, &len);
   printf("accept,the clientfd is %d\n",clientfd);
   char buffer[BUFFER_LENGTH] = {0};
   int ret = recv(clientfd, buffer, BUFFER_LENGTH, 0);  // 接收client发来的信息
   printf("ret: %d, buffer: %s\n", ret, buffer);  // 本地打印client
   send(clientfd, buffer, ret, 0);  // 将信息传给client
   getchar(); //block
   return 0;
}

运行结果

可以看到此时还没有对服务器进行连接此时的服务器就已经输出内容了,这就是设置socket阻塞与非阻塞的差别之处。

7ee64f3d6f6b47f0a1b164d64e051cff.png

此时点击连接,然后client再向server输出信息也是没有任何的用处了。

77bda6ad3a284b43b32ff053a10aa3a2.png

为了后续的方便信息展示,后面的程序都将设置socket非阻塞进行注释。

为什么要使用while循环来反复读取数据

可以看到上述两个程序在第一次读取信息之后(不论成功),server端就断开整个程序的连接了,正常的服务器程序肯定不可能只接收client的一条信息吧,所以我们需要增加一个while循环来一直进行读取数据,这也就是为什么服务器是7*24小时运行的关键之处

#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <fcntl.h>
#include <pthread.h>
#include <sys/poll.h>
#include <sys/epoll.h>
#define BUFFER_LENGTH 1024
#define POLL_SIZE 1024
int main()
{
    // 创建socket
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    struct sockaddr_in servaddr;
    memset(&servaddr, 0, sizeof(struct sockaddr_in)); // 清空
    servaddr.sin_family = AF_INET;                    // 设置协议族为 IPV4
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);     // 0.0.0.0
    servaddr.sin_port = htons(9999);                  // 监听端口为 9999
    // 绑定好初始化的信息,出错就打印出错信息
    if (-1 == bind(sockfd, (struct sockaddr *)&servaddr, sizeof(struct sockaddr)))
    {
        printf("bind failed: %s", strerror(errno));
        return -1;
    }
    // 监听绑定好的信息
    listen(sockfd, 10);
    // 将socket设置为非阻塞
    int flags = fcntl(sockfd, F_GETFL, 0);
    flags |= O_NONBLOCK;
    fcntl(sockfd, F_SETFL, flags);
    // 为了保存 client的信息锁创建的结构
    struct sockaddr_in clientaddr;
    socklen_t len = sizeof(clientaddr);
    // 此时的 clientfd就是用来和 client通信的socket
    int clientfd = accept(sockfd, (struct sockaddr *)&clientaddr, &len);
    printf("accept,the clientfd is %d\n", clientfd);
    while (1)
    {
        char buffer[BUFFER_LENGTH] = {0};
        int ret = recv(clientfd, buffer, BUFFER_LENGTH, 0); // 接收client发来的信息
        printf("ret: %d, buffer: %s\n", ret, buffer);       // 本地打印client
        send(clientfd, buffer, ret, 0); // 将信息传给client
    }
    getchar(); // block
    return 0;
}

运行结果

运行之前

此时是阻塞模式需要等待client连接到server才会打印信息。

2d58bce8549d4840a7ac8c3df23a064d.png

连接之后

可以看到此时可以随意的对此程序进行读写操作

8375a22132224859a4f8c666d4fafddf.png

server不写close函数,当client调用close时server端会发生的情况

依旧是这一套代码,此时会发现server端会疯狂打印如下的信息,要解决此问题很简单,只需要在recv返回值为0(此时代表client主动关闭连接)时server在调用close函数即可。

4370f4b300e847a4ab499c9051423c3e.png

多个client连接一个server

依旧使用这个程序,使用两个client连接server,此时可以发现只有一个client可以发数据,其余client发数据会被卡住

04279b6ce62347a3a19776edaeaa4d9a.png

要想解决这个问题,就需要在while循环之中增加accept函数,但是此时又会出现一个问题,每次循环都会卡在accept之中,这就很值得思考了。

accept放在while循环中导致每次读写被accept阻塞解决思路

  此时就需要用到多线程了。server每接收到一个client的连接就可以创建一个线程去对哪一个client进行读写操作。

#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <fcntl.h>
#include <pthread.h>
#include <sys/poll.h>
#include <sys/epoll.h>
#define BUFFER_LENGTH 1024
#define POLL_SIZE 1024
// 1 connection 1 thread
void *client_thread(void *arg)
{
    int clientfd = *(int *)arg;
    while (1)
    { // slave
        char buffer[BUFFER_LENGTH] = {0};
        int ret = recv(clientfd, buffer, BUFFER_LENGTH, 0);
        if (ret == 0)
        {
            close(clientfd);
            break;
        }
        printf("ret: %d, buffer: %s\n", ret, buffer);
        send(clientfd, buffer, ret, 0);
    }
}
int main()
{
    // 创建socket
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    struct sockaddr_in servaddr;
    memset(&servaddr, 0, sizeof(struct sockaddr_in)); // 清空
    servaddr.sin_family = AF_INET;                    // 设置协议族为 IPV4
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);     // 0.0.0.0
    servaddr.sin_port = htons(9999);                  // 监听端口为 9999
    // 绑定好初始化的信息,出错就打印出错信息
    if (-1 == bind(sockfd, (struct sockaddr *)&servaddr, sizeof(struct sockaddr)))
    {
        printf("bind failed: %s", strerror(errno));
        return -1;
    }
    // 监听绑定好的信息
    listen(sockfd, 10);
#if 0
    // 将socket设置为非阻塞
    int flags = fcntl(sockfd, F_GETFL, 0);
    flags |= O_NONBLOCK;
    fcntl(sockfd, F_SETFL, flags);
#endif
    // 为了保存 client的信息锁创建的结构
    struct sockaddr_in clientaddr;
    socklen_t len = sizeof(clientaddr);
    while (1)
    { // master
        int clientfd = accept(sockfd, (struct sockaddr *)&clientaddr, &len);
#if 0 
    char buffer[BUFFER_LENGTH] = {0};
    int ret = recv(clientfd, buffer, BUFFER_LENGTH, 0);
    printf("ret: %d, buffer: %s\n", ret, buffer);
    send(clientfd, buffer, ret, 0);
#else
        pthread_t threadid;
        pthread_create(&threadid, NULL, client_thread, &clientfd);
#endif
    }
      getchar(); // block
        return 0;
}

此时可以看到连续三个client连接server,随意进行通信都能够完成正常的通信。

ff80d90629164bfe9b84169f57e084f0.png

网络编程与select/poll/epoll服务器的实现(2):https://developer.aliyun.com/article/1415913?spm=a2c6h.13148508.setting.18.16254f0exsfwiz

目录
相关文章
|
30天前
|
负载均衡 监控 网络协议
CentOS搭建内部网络NTP时间服务器
未尽的音符,等待你去创造,如同未配置的服务器等待着你。环顾四周,这就是你的舞台,你的音乐会,你的 CentOS 内部网络 NTP 时间服务器,为所欲为吧!
363 30
|
1月前
|
存储 安全 Linux
Dell OpenManage Enterprise 4.4 - Dell 服务器、存储和网络设备集中管理软件
Dell OpenManage Enterprise 4.4 - Dell 服务器、存储和网络设备集中管理软件
46 4
Dell OpenManage Enterprise 4.4 - Dell 服务器、存储和网络设备集中管理软件
|
1月前
|
存储 消息中间件 弹性计算
阿里云服务器ECS计算型c7和通用算力型u1在适用场景、计算性能、网络与存储性能等方面的对比
阿里云ECS服务器u1和c7实例在适用场景、性能、处理器特性等方面存在显著差异。u1为通用算力型,性价比高,适合中小企业及对性能要求不高的场景;c7为企业级计算型,采用最新Intel处理器,性能稳定且强大,适用于高性能计算需求。u1支持多种CPU内存配比,但性能一致性可能受底层平台影响;c7固定调度模式,确保高性能与稳定性。选择时可根据预算与性能需求决定。
100 23
|
2月前
|
SQL 数据采集 人工智能
“服务器老被黑?那是你没上AI哨兵!”——聊聊基于AI的网络攻击检测那些事儿
“服务器老被黑?那是你没上AI哨兵!”——聊聊基于AI的网络攻击检测那些事儿
110 12
|
2月前
|
安全 网络安全 定位技术
网络通讯技术:HTTP POST协议用于发送本地压缩数据到服务器的方案。
总的来说,无论你是一名网络开发者,还是普通的IT工作人员,理解并掌握POST方法的运用是非常有价值的。它就像一艘快速,稳定,安全的大船,始终为我们在网络海洋中的冒险提供了可靠的支持。
98 22
|
2月前
|
存储 缓存 弹性计算
阿里云经济型e实例云服务器评测:企业官网搭建的性价比之选
阿里云服务器经济型e实例可以用来搭建企业网站吗?云服务器作为搭建企业官网的基础设施,其性能、稳定性、成本等因素直接影响着官网的运营效果。阿里云经济型e实例云服务器作为一款性价比较高的产品,备受用户关注。许多企业在选择云服务器搭建官网时,都会将其纳入考虑范围。本文将详细探讨阿里云经济型e实例云服务器的特点、性能表现、稳定性与可靠性,以及成本考量,最终解答是否适合用它来搭建企业官网。
|
3月前
|
存储 缓存 网络协议
阿里云特惠云服务器99元与199元配置与性能和适用场景解析:高性价比之选
2025年,阿里云长效特惠活动继续推出两款极具吸引力的特惠云服务器套餐:99元1年的经济型e实例2核2G云服务器和199元1年的通用算力型u1实例2核4G云服务器。这两款云服务器不仅价格亲民,而且性能稳定可靠,为入门级用户和普通企业级用户提供了理想的选择。本文将对这两款云服务器进行深度剖析,包括配置介绍、实例规格、使用场景、性能表现以及购买策略等方面,帮助用户更好地了解这两款云服务器,以供参考和选择。
|
3月前
|
域名解析 人工智能 弹性计算
DeepSeek服务器繁忙解决方法:使用阿里云一键部署DeepSeek个人网站!
通过阿里云一键部署DeepSeek个人网站,解决服务器繁忙问题。学生用户可领取300元代金券实现0成本部署,普通用户则可用99元/年的服务器。教程涵盖从选择套餐、设置密码到获取百炼API-KEY的全流程,助您快速搭建专属大模型主页,体验DeepSeek、Qwen-max、Llama等多款模型,无需代码,最快5分钟完成部署。支持绑定个人域名,共享亲友使用,日均成本仅约1元。
258 10
|
20天前
|
物联网
(手把手)在华为云、阿里云搭建自己的物联网MQTT消息服务器,免费IOT平台
本文介绍如何在阿里云搭建自己的物联网MQTT消息服务器,并使用 “MQTT客户端调试工具”模拟MQTT设备,接入平台进行消息收发。
483 8
|
2月前
|
安全 Linux
阿里云linux服务器使用脚本通过安全组屏蔽异常海外访问ip
公网网站可能会遭受黑客攻击导致访问异常,使用此脚本可以屏蔽掉异常IP 恢复访问。也可自行设置定时任务定期检测屏蔽。
211 28