网络编程与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

目录
相关文章
|
12天前
|
机器学习/深度学习 人工智能 运维
企业内训|LLM大模型在服务器和IT网络运维中的应用-某日企IT运维部门
本课程是为某在华日资企业集团的IT运维部门专门定制开发的企业培训课程,本课程旨在深入探讨大型语言模型(LLM)在服务器及IT网络运维中的应用,结合当前技术趋势与行业需求,帮助学员掌握LLM如何为运维工作赋能。通过系统的理论讲解与实践操作,学员将了解LLM的基本知识、模型架构及其在实际运维场景中的应用,如日志分析、故障诊断、网络安全与性能优化等。
37 2
|
18天前
|
存储 安全 数据可视化
提升网络安全防御有效性,服务器DDoS防御软件解读
提升网络安全防御有效性,服务器DDoS防御软件解读
33 1
提升网络安全防御有效性,服务器DDoS防御软件解读
|
5天前
|
存储 关系型数据库 MySQL
查询服务器CPU、内存、磁盘、网络IO、队列、数据库占用空间等等信息
查询服务器CPU、内存、磁盘、网络IO、队列、数据库占用空间等等信息
95 1
|
26天前
|
Kubernetes 应用服务中间件 nginx
搭建Kubernetes v1.31.1服务器集群,采用Calico网络技术
在阿里云服务器上部署k8s集群,一、3台k8s服务器,1个Master节点,2个工作节点,采用Calico网络技术。二、部署nginx服务到k8s集群,并验证nginx服务运行状态。
291 1
|
29天前
|
网络协议 Java API
【网络】TCP回显服务器和客户端的构造,以及相关bug解决方法
【网络】TCP回显服务器和客户端的构造,以及相关bug解决方法
57 2
|
29天前
|
存储 网络协议 Java
【网络】UDP回显服务器和客户端的构造,以及连接流程
【网络】UDP回显服务器和客户端的构造,以及连接流程
49 2
|
29天前
|
存储 网络协议 Java
【网络】UDP和TCP之间的差别和回显服务器
【网络】UDP和TCP之间的差别和回显服务器
56 1
|
18天前
|
安全 区块链 数据库
|
28天前
|
测试技术
评测 AlibabaCloud 阿里云国际版 香港轻量云服务器的性能和网络怎么样
评测 AlibabaCloud 阿里云国际版 香港轻量云服务器的性能和网络怎么样
|
30天前
|
存储 监控 NoSQL
Redis的实现二: c、c++的网络通信编程技术,让服务器处理多个client
本文讨论了在C/C++中实现服务器处理多个客户端的技术,重点介绍了事件循环和非阻塞IO的概念,以及如何在Linux上使用epoll来高效地监控和管理多个文件描述符。
23 0