当前位置:首页 > 行业动态 > 正文

c 网络编程模型

常见的网络编程模型有同步阻塞模型(如 TCP 的 BIO)、异步非阻塞模型(如 TCP 的 NIO、epoll 等),还有事件驱动模型等。

C语言网络编程模型主要包括以下几种:

1、基本TCP Socket编程模型

创建Socket:使用socket()函数创建一个套接字,指定协议族(如AF_INET表示IPv4)、套接字类型(如SOCK_STREAM表示TCP流式套接字)和协议(通常为0,表示使用默认协议)。

 int sockfd = socket(AF_INET, SOCK_STREAM, 0);
      if (sockfd < 0) {
          perror("socket creation failed");
          exit(EXIT_FAILURE);
      }

绑定地址:使用bind()函数将套接字与本地地址(IP地址和端口号)绑定,需要先定义一个struct sockaddr_in结构体来存储地址信息,并设置其成员变量,然后将其指针传递给bind()函数。

 struct sockaddr_in address;
      address.sin_family = AF_INET;
      address.sin_addr.s_addr = INADDR_ANY;
      address.sin_port = htons(PORT);
      if (bind(sockfd, (struct sockaddr *)&address, sizeof(address)) < 0) {
          perror("bind failed");
          exit(EXIT_FAILURE);
      }

监听连接:对于服务器端,使用listen()函数使套接字进入监听状态,等待客户端的连接请求,需要指定监听套接字的文件描述符和等待连接的队列长度。

 if (listen(sockfd, 3) < 0) {
          perror("listen");
          exit(EXIT_FAILURE);
      }

接受连接:服务器端调用accept()函数从监听套接字的连接队列中接受一个连接请求,返回一个新的套接字描述符用于与客户端通信。

c 网络编程模型

 int new_socket;
      struct sockaddr_in client_addr;
      socklen_t client_addr_len = sizeof(client_addr);
      new_socket = accept(sockfd, (struct sockaddr *)&client_addr, &client_addr_len);
      if (new_socket < 0) {
          perror("accept");
          exit(EXIT_FAILURE);
      }

发送数据:使用send()write()函数向已连接的客户端发送数据,需要指定套接字描述符、要发送的数据缓冲区及其长度等参数。

 char *message = "Hello from server";
      send(new_socket, message, strlen(message), 0);

接收数据:使用recv()read()函数接收客户端发送的数据,同样需要指定套接字描述符、接收数据的缓冲区及其长度等参数。

 char buffer[1024] = {0};
      int valread = read(new_socket, buffer, 1024);
      printf("%s
", buffer);

关闭套接字:在通信结束后,使用close()函数关闭套接字,释放资源。

 close(new_socket);
      close(sockfd);

2、基于多进程的客户端 服务器模型

服务器端:主进程负责监听客户端的连接请求,每当有新的客户端连接时,通过fork()系统调用创建一个子进程来处理该客户端的请求,子进程继承父进程的监听套接字,并在处理完客户端请求后退出,这样可以实现多个客户端同时与服务器进行通信,提高服务器的并发处理能力。

c 网络编程模型

 int main() {
          int server_fd, new_socket;
          struct sockaddr_in address;
          int opt = 1;
          int addrlen = sizeof(address);
          char buffer[1024] = {0};
          // 创建监听套接字
          if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
              perror("socket failed");
              exit(EXIT_FAILURE);
          }
          // 设置套接字选项
          if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) {
              perror("setsockopt");
              exit(EXIT_FAILURE);
          }
          address.sin_family = AF_INET;
          address.sin_addr.s_addr = INADDR_ANY;
          address.sin_port = htons(PORT);
          // 绑定套接字
          if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
              perror("bind failed");
              exit(EXIT_FAILURE);
          }
          // 开始监听
          if (listen(server_fd, 3) < 0) {
              perror("listen");
              exit(EXIT_FAILURE);
          }
          printf("Server is listening on port %d...
", PORT);
          while (1) {
              if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) {
                  perror("accept");
                  exit(EXIT_FAILURE);
              }
              // 创建子进程处理客户端请求
              pid_t child_pid = fork();
              if (child_pid == 0) { // 子进程
                  close(server_fd); // 关闭监听套接字
                  // 处理客户端请求...
                  char *response = "Hello from server";
                  send(new_socket, response, strlen(response), 0);
                  close(new_socket); // 关闭与客户端的连接套接字
                  exit(0); // 子进程退出
              } else if (child_pid < 0) { // fork失败
                  perror("fork");
                  exit(EXIT_FAILURE);
              } else { // 父进程
                  close(new_socket); // 父进程关闭与客户端的连接套接字
              }
          }
      }

客户端:与基本TCP Socket编程模型中的客户端类似,创建套接字、连接到服务器、发送和接收数据等,只是在与服务器建立连接后,可以与对应的服务器子进程进行通信。

3、基于多线程的客户端 服务器模型

服务器端:与基于多进程的模型类似,主线程负责监听客户端的连接请求,当有新连接时,创建一个新线程来处理该客户端的请求,可以使用pthread_create()函数创建线程,并将客户端套接字描述符传递给线程函数作为参数。

 void *thread_callback(void *args) {
          int client_socket = *((int *)args);
          // 处理客户端请求...
          char *response = "Hello from server";
          send(client_socket, response, strlen(response), 0);
          close(client_socket); // 关闭与客户端的连接套接字
          return NULL;
      }
      int main() {
          int server_fd, new_socket;
          struct sockaddr_in address;
          int opt = 1;
          int addrlen = sizeof(address);
          char buffer[1024] = {0};
          // 创建监听套接字...
          // 绑定、监听...
          while (1) {
              if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) {
                  perror("accept");
                  exit(EXIT_FAILURE);
              }
              pthread_t thread_id;
              pthread_create(&thread_id, NULL, thread_callback, (void *)&new_socket);
          }
      }

客户端:同样与基本TCP Socket编程模型中的客户端类似,这种模型相对于多进程模型,线程的创建和销毁开销相对较小,但在高并发情况下,可能会受到操作系统对线程数量的限制。

4、基于IO多路复用的客户端 服务器模型

c 网络编程模型

select模型:使用select()函数监视多个文件描述符的状态变化,判断是否有文件描述符可读、可写或异常,服务器端将监听套接字和所有已连接的客户端套接字都添加到select()函数的监视集合中,当有客户端套接字可读时,表示有客户端发送了数据,服务器可以读取并进行相应的处理。

 #define MAX_CLIENTS 30
      int main() {
          int server_fd, new_socket, client_socket[MAX_CLIENTS], max_sd, activity, i, valread, sd;
          int opt = 1;
          struct sockaddr_in address;
          int addrlen = sizeof(address);
          char buffer[1024];
          // 创建监听套接字...
          // 绑定、监听...
          // 初始化客户端套接字数组
          for (i = 0; i < MAX_CLIENTS; i++) {
              client_socket[i] = 0;
          }
          while (1) {
              // 清空集合
              fd_set readfds;
              FD_ZERO(&readfds);
              // 添加监听套接字到集合
              FD_SET(server_fd, &readfds);
              max_sd = server_fd;
              // 添加客户端套接字到集合
              for (i = 0; i < MAX_CLIENTS; i++) {
                  sd = client_socket[i];
                  if (sd > 0) {
                      FD_SET(sd, &readfds);
                      if (sd > max_sd) {
                         max_sd = sd;
                     }
                }
            }
          // 等待活动发生
          activity = select(max_sd + 1, &readfds, NULL, NULL, NULL);
          if ((activity < 0) && (errno != EINTR)) {
               perror("select error");
              }
          // 如果监听套接字可读,表示有新的连接请求
          if (FD_ISSET(server_fd, &readfds)) {
              if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) {
                  perror("accept");
                } else {
                  for (i = 0; i < MAX_CLIENTS; i++) {
                        if (client_socket[i] == 0) {
                             client_socket[i] = new_socket;
                             break;
                         }
                    }
                }
            }
          // 检查客户端套接字的活动情况
          for (i = 0; i < MAX_CLIENTS; i++) {
              sd = client_socket[i];
              if (FD_ISSET(sd, &readfds)) {
                  // 读取客户端数据...
                  valread = read(sd, buffer, 1024);
                  if (valread == 0) {
                      close(sd);
                      client_socket[i] = 0;
                  } else {
                      // 处理客户端数据...
                  }
              }
          }
      }

poll模型:与select()函数类似,但poll()函数没有文件描述符数量的限制,并且可以为每个文件描述符设置不同的事件和超时时间,它使用一个struct pollfd结构体数组来存储要监视的文件描述符及其事件信息。

 #define MAX_CLIENTS 30
      int main() {
          int server_fd, new_socket, client_socket[MAX_CLIENTS], i, valread, sd;
          int opt = 1;
          struct sockaddr_in address;
          int addrlen = sizeof(address);
          char buffer[1024];
          struct pollfd pollfds[MAX_CLIENTS];
          // 创建监听套接字...
          // 绑定、监听...
          // 初始化客户端套接字数组和pollfds数组
          for (i = 0; i < MAX_CLIENTS; i++) {
              client_socket[i] = 0;
              pollfds[i].fd = -1;
          }
          while (1) {
              // 清空pollfds数组中的revents字段
              for (i = 0; i < MAX_CLIENTS; i++) {
                  pollfds[i].revents = 0;
              }
              // 设置监听套接字的事件为POLLIN(可读)
              // 设置客户端套接字的事件为POLLIN(可读)和POLLERR(错误)
              for (i = 0; i < MAX_CLIENTS; i++) {
                  if (client_socket[i] > 0) {
                        pollfds[i].fd = client_socket[i];
                        pollfds[i].events = POLLIN | POLLERR;
                     }
                }
            }
          // 等待事件发生
          int activity = poll(pollfds, MAX_CLIENTS, -1);
          if (activity < 0) {
              perror("poll error");
              return -1;
          }
          for (i = 0; i < MAX_CLIENTS; i++) {
              if (pollfds[i].revents & POLLIN) {
                  sd = pollfds[i].fd;
                  if ((sd == server_fd) || (client_socket[i] == -1)) {
                      // 接受新的连接请求...
                  } else {
                      // 读取客户端数据...
                  }
              } else if (pollfds[i].revents & POLLERR) {
                  close(pollfds[i].fd);
                  client_socket[i] = -1;
              }
          }
      }

epoll模型:是Linux下高效的IO多路复用模型,它使用epoll_create()函数创建一个epoll实例,然后使用epoll_ctl()函数将文件描述符添加到epoll实例的监视列表中,并设置相应的事件类型(如EPOLLIN表示可读),最后使用epoll_wait()函数等待事件发生,它会返回就绪的文件描述符列表。

 #define MAX_EVENTS 1000
      #include <sys/epoll.h>
      int main() {
          int server_fd, epollfd, new_socket, client_socket[MAX_EVENTS], i, valread, sd;
          int opt = 1;
          struct sockaddr_in address;
          int addrlen = sizeof(address);
          char buffer[1024];
          struct epoll_event ev, events[MAX_EVENTS];
          // 创建监听套接字...
          // 绑定、监听...
          // 创建epoll实例
          epollfd = epoll_create1(0);
          if (epollfd == -1) {
              perror("epoll_create1");
              exit(EXIT_FAILURE);
          }
          // 将监听套接字添加到epoll实例的监视列表中
          ev.events = EPOLLIN;
          ev.data.fd = server_fd;
          if (epoll_ctl(epollfd, EPOLL_CTL_ADD, server_fd, &ev) == -1) {
              perror("epoll_ctl: server_fd");
              exit(EXIT_FAILURE);
          }
          while (1) {
              // 等待事件发生
              int nfds = epoll_wait(epollfd, events, MAX_EVENTS, -1);
              if (nfds == -1) {
                  perror("epoll_wait");
                  exit(EXIT_FAILURE);
              }
              for (i = 0; i < nfds; i++) {
                  if (events[i].data.fd == server_fd) {
                      // 接受新的连接请求...
                  } else {
                      // 读取客户端数据...
                  }
              }
          }
      }