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

目录
相关文章
|
2月前
|
数据安全/隐私保护
Haskell网络编程:代理服务器的高级使用技巧
Haskell网络编程:代理服务器的高级使用技巧
|
2月前
|
网络协议 数据处理 C语言
利用C语言基于poll实现TCP回声服务器的多路复用模型
此代码仅为示例,展示了如何基于 `poll`实现多路复用的TCP回声服务器的基本框架。在实际应用中,你可能需要对其进行扩展或修改,以满足具体的需求。
81 0
|
4月前
|
安全 Java Linux
(七)Java网络编程-IO模型篇之从BIO、NIO、AIO到内核select、epoll剖析!
IO(Input/Output)方面的基本知识,相信大家都不陌生,毕竟这也是在学习编程基础时就已经接触过的内容,但最初的IO教学大多数是停留在最基本的BIO,而并未对于NIO、AIO、多路复用等的高级内容进行详细讲述,但这些却是大部分高性能技术的底层核心,因此本文则准备围绕着IO知识进行展开。
165 1
|
3月前
|
监控
【网络编程】select函数
【网络编程】select函数
63 0
|
4月前
|
存储 Java Unix
(八)Java网络编程之IO模型篇-内核Select、Poll、Epoll多路复用函数源码深度历险!
select/poll、epoll这些词汇相信诸位都不陌生,因为在Redis/Nginx/Netty等一些高性能技术栈的底层原理中,大家应该都见过它们的身影,接下来重点讲解这块内容。
|
4月前
|
网络协议 安全 Python
我们将使用Python的内置库`http.server`来创建一个简单的Web服务器。虽然这个示例相对简单,但我们可以围绕它展开许多讨论,包括HTTP协议、网络编程、异常处理、多线程等。
我们将使用Python的内置库`http.server`来创建一个简单的Web服务器。虽然这个示例相对简单,但我们可以围绕它展开许多讨论,包括HTTP协议、网络编程、异常处理、多线程等。
|
4月前
|
网络协议 安全 Java
Java中的网络编程:Socket编程详解
Java中的网络编程:Socket编程详解
|
4月前
|
Java 大数据
如何在Java中进行网络编程:Socket与NIO
如何在Java中进行网络编程:Socket与NIO
|
4月前
|
Java API 网络安全
Java网络编程入门
Java网络编程入门
|
4月前
|
网络协议 安全 Java
Java中的网络编程:Socket编程详解
Java中的网络编程:Socket编程详解

热门文章

最新文章