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

目录
相关文章
|
16天前
|
安全 Java 数据处理
Python网络编程基础(Socket编程)多线程/多进程服务器编程
【4月更文挑战第11天】在网络编程中,随着客户端数量的增加,服务器的处理能力成为了一个重要的考量因素。为了处理多个客户端的并发请求,我们通常需要采用多线程或多进程的方式。在本章中,我们将探讨多线程/多进程服务器编程的概念,并通过一个多线程服务器的示例来演示其实现。
|
1月前
|
弹性计算 监控 数据可视化
ECS网络流量监控
ECS网络流量监控
63 2
|
1月前
|
域名解析 关系型数据库 MySQL
怎样用自己电脑搭建网络服务器!不花一分钱
怎样用自己电脑搭建网络服务器!不花一分钱
53 1
|
1月前
|
Linux Shell 网络安全
【Shell 命令集合 网络通讯 】Linux 与SMB服务器进行交互 smbclient命令 使用指南
【Shell 命令集合 网络通讯 】Linux 与SMB服务器进行交互 smbclient命令 使用指南
52 1
|
19天前
|
Python
Python网络编程基础(Socket编程)UDP服务器编程
【4月更文挑战第8天】Python UDP服务器编程使用socket库创建UDP套接字,绑定到特定地址(如localhost:8000),通过`recvfrom`接收客户端数据报,显示数据长度、地址和内容。无连接的UDP协议使得服务器无法主动发送数据,通常需应用层实现请求-响应机制。当完成时,用`close`关闭套接字。
|
1月前
|
Shell Linux 网络安全
【Shell 命令集合 网络通讯 】Linux 管理Apache HTTP服务器 httpd命令 使用指南
【Shell 命令集合 网络通讯 】Linux 管理Apache HTTP服务器 httpd命令 使用指南
32 0
|
1月前
|
Shell Linux Apache
【Shell 命令集合 网络通讯 】Linux 管理Apache HTTP服务器 apachectl命令 使用教程
【Shell 命令集合 网络通讯 】Linux 管理Apache HTTP服务器 apachectl命令 使用教程
162 1
|
1月前
|
Ubuntu JavaScript 关系型数据库
在阿里云Ubuntu 20.04服务器中搭建一个 Ghost 博客
在阿里云Ubuntu 20.04服务器上部署Ghost博客的步骤包括创建新用户、安装Nginx、MySQL和Node.js 18.x。首先,通过`adduser`命令创建非root用户,然后安装Nginx和MySQL。接着,设置Node.js环境,下载Nodesource GPG密钥并安装Node.js 18.x。之后,使用`npm`安装Ghost-CLI,创建Ghost安装目录并进行安装。配置过程中需提供博客URL、数据库连接信息等。最后,测试访问前台首页和后台管理页面。确保DNS设置正确,并根据提示完成Ghost博客的配置。
在阿里云Ubuntu 20.04服务器中搭建一个 Ghost 博客
|
1月前
|
存储 弹性计算 数据可视化
要将ECS中的文件直接传输到阿里云网盘与相册(
【2月更文挑战第31天】要将ECS中的文件直接传输到阿里云网盘与相册(
420 4
|
22天前
|
弹性计算
阿里云ECS使用体验
在申请高校学生免费体验阿里云ECS云服务器后的一些使用体验和感受。