国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

Linux網(wǎng)絡(luò)編程之TCP/IP實(shí)現(xiàn)高并發(fā)網(wǎng)絡(luò)服務(wù)器設(shè)計指南

這篇具有很好參考價值的文章主要介紹了Linux網(wǎng)絡(luò)編程之TCP/IP實(shí)現(xiàn)高并發(fā)網(wǎng)絡(luò)服務(wù)器設(shè)計指南。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點(diǎn)擊"舉報違法"按鈕提交疑問。

目錄

引言:

多進(jìn)程服務(wù)器

例程分享:

多線程服務(wù)器

?例程分享:

I/O多路復(fù)用服務(wù)器

select

例程分享:

poll

例程分享:

epoll

例程分享:

總結(jié)建議


引言:

????????隨著互聯(lián)網(wǎng)的迅猛發(fā)展,服務(wù)器面臨著越來越多的并發(fā)請求。如何設(shè)計一個能夠高效處理大量并發(fā)請求的服務(wù)器成為了一個關(guān)鍵問題。本文將介紹幾種常見的高并發(fā)服務(wù)器設(shè)計方案,包括多進(jìn)程服務(wù)器、多線程服務(wù)器、I/O多路復(fù)用服務(wù)器和epoll服務(wù)器,并分析它們的優(yōu)缺點(diǎn),以便讀者能夠選擇適合自己需求的設(shè)計方案。

多進(jìn)程服務(wù)器

利用fork創(chuàng)建子進(jìn)程處理每個連接請求。

優(yōu)點(diǎn):充分利用多核CPU的計算能力,隔離不同連接之間的資源。

?缺點(diǎn):父進(jìn)程需要設(shè)置較大的文件描述符限制,進(jìn)程創(chuàng)建和切換開銷較大。

相關(guān)API函數(shù):fork、waitpid、socket、bind、listen、accept、read、write、close

實(shí)現(xiàn)要點(diǎn):

  • 父進(jìn)程close子進(jìn)程socket,避免泄漏。
  • 信號處理回收子進(jìn)程。
  • 每個子進(jìn)程處理一個連接請求。

例程分享:

/*
服務(wù)器監(jiān)聽端口,接收客戶端連接。
對每個連接fork子進(jìn)程處理請求。
子進(jìn)程循環(huán)接收客戶端數(shù)據(jù),轉(zhuǎn)換大小寫后返回。
父進(jìn)程關(guān)閉連接socket,信號函數(shù)回收子進(jìn)程。
客戶端連接后循環(huán)發(fā)送接收數(shù)據(jù)。
使用多進(jìn)程處理連接請求,充分利用多核CPU。
fork創(chuàng)建進(jìn)程,waitpid和信號處理回收子進(jìn)程。
父子進(jìn)程同步處理,避免混亂。
*/

/* server.c */

#include <stdio.h>   // 標(biāo)準(zhǔn)IO頭文件

#include <string.h>  // 字符串處理頭文件

#include <netinet/in.h> // socket編程頭文件

#include <arpa/inet.h> // IP地址轉(zhuǎn)換頭文件

#include <signal.h> // 信號處理頭文件

#include <sys/wait.h> // 等待子進(jìn)程頭文件

#include <sys/types.h> // 數(shù)據(jù)類型頭文件

#include "wrap.h" // socket函數(shù)封裝頭文件

#define MAXLINE 80  // 最大讀寫字節(jié)數(shù)

#define SERV_PORT 800 // 服務(wù)器端口號

// 信號處理函數(shù),回收子進(jìn)程
void do_sigchild(int num) {

	while (waitpid(0, NULL, WNOHANG) > 0);
}

int main(void) {

  // socket地址結(jié)構(gòu)
  struct sockaddr_in servaddr, cliaddr;

  // 客戶端地址長度

  socklen_t cliaddr_len;

  // 監(jiān)聽和連接socket
  int listenfd, connfd;

  // 數(shù)據(jù)緩沖區(qū)
  char buf[MAXLINE];

  // 客戶端IP字符串

  char str[INET_ADDRSTRLEN];

  int i, n; // 循環(huán)變量和讀字節(jié)數(shù)

  pid_t pid; // 進(jìn)程ID

  // 安裝信號處理函數(shù)
  struct sigaction newact;
  newact.sa_handler = do_sigchild;
  sigemptyset(&newact.sa_mask);
  newact.sa_flags = 0;
  sigaction(SIGCHLD, &newact, NULL);

  // 創(chuàng)建監(jiān)聽socket
  listenfd = Socket(AF_INET, SOCK_STREAM, 0);

  // 初始化服務(wù)器地址結(jié)構(gòu)
  bzero(&servaddr, sizeof(servaddr));
  servaddr.sin_family = AF_INET;
  servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
  servaddr.sin_port = htons(SERV_PORT);

  // 綁定地址和端口

  Bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));

  // 設(shè)置監(jiān)聽隊列長度
  Listen(listenfd, 20);

  // 循環(huán)接收客戶端連接請求
  while (1) {

  Copy code

  cliaddr_len = sizeof(cliaddr);

  // 接收一個客戶端連接
  connfd = Accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);

  // Fork一個子進(jìn)程處理連接
  pid = fork(); 

  if (pid == 0) {

    // 子進(jìn)程關(guān)閉監(jiān)聽socket
    Close(listenfd);

    // 處理客戶端請求
    while (1) {

      // 接收客戶端數(shù)據(jù) 
      n = Read(connfd, buf, MAXLINE);

      // 判斷客戶端是否關(guān)閉
      if (n == 0) {
        printf("the other side has been closed.\n");
        break;
      }

      // 打印客戶端信息
      printf("received from %s at PORT %d\n",  
              inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
              ntohs(cliaddr.sin_port));

      // 轉(zhuǎn)換為大寫
      for (i = 0; i < n; i++)
        buf[i] = toupper(buf[i]);

      // 發(fā)送轉(zhuǎn)換后數(shù)據(jù)
      Write(connfd, buf, n);
    }

  // 關(guān)閉連接 
  Close(connfd);

  return 0;

} else if (pid > 0) {
  
  // 父進(jìn)程關(guān)閉連接socket
  Close(connfd); 

} else
  perr_exit("fork");
}

// 關(guān)閉監(jiān)聽socket
Close(listenfd);

return 0;
}

/* client.c */

#include <stdio.h>

#include <string.h>

#include <unistd.h>

#include <netinet/in.h>

#include "wrap.h"

#define MAXLINE 80
#define SERV_PORT 6666

int main(int argc, char *argv[]) {

  // 服務(wù)器地址結(jié)構(gòu)
  struct sockaddr_in servaddr;

  // 數(shù)據(jù)緩沖區(qū)
  char buf[MAXLINE];

  // socket和讀字節(jié)數(shù)
  int sockfd, n;

  // 創(chuàng)建socket
  sockfd = Socket(AF_INET, SOCK_STREAM, 0);

  // 初始化服務(wù)器地址結(jié)構(gòu)
  bzero(&servaddr, sizeof(servaddr));
  servaddr.sin_family = AF_INET;
  inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
  servaddr.sin_port = htons(SERV_PORT);

  // 連接服務(wù)器
  Connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));

  // 循環(huán)發(fā)送接收數(shù)據(jù)
  while (fgets(buf, MAXLINE, stdin) != NULL) {

  Copy code

  // 發(fā)送數(shù)據(jù)到服務(wù)器
  Write(sockfd, buf, strlen(buf));  

  // 從服務(wù)器讀取數(shù)據(jù)
  n = Read(sockfd, buf, MAXLINE);

  // 判斷服務(wù)器是否關(guān)閉
  if (n == 0) {
    printf("the other side has been closed.\n");
    break;
  } else
    // 輸出服務(wù)器返回數(shù)據(jù)
    Write(STDOUT_FILENO, buf, n);
  }

  // 關(guān)閉連接
  Close(sockfd);

  return 0;
}

多線程服務(wù)器

一個進(jìn)程內(nèi)創(chuàng)建線程處理每個連接請求。

?優(yōu)點(diǎn):高效利用多核CPU,創(chuàng)建和銷毀線程開銷較小。

?缺點(diǎn):需要調(diào)整進(jìn)程的文件描述符限制,需要進(jìn)行線程同步,線程退出時需要進(jìn)行資源清理。

?相關(guān)API函數(shù):pthread_create、pthread_detach、pthread_join、pthread_mutex_init、pthread_mutex_lock、pthread_mutex_unlock、socket、bind、listen、accept、read、write、close

要點(diǎn):

  • 調(diào)整進(jìn)程文件描述符限制
  • 共享數(shù)據(jù)同步
  • 線程退出處理,防止資源泄漏
  • 過多線程會降低性能

?例程分享:

/*
服務(wù)器端創(chuàng)建監(jiān)聽套接字,綁定地址并監(jiān)聽。
主循環(huán)調(diào)用accept接收客戶端連接,為每個客戶端創(chuàng)建線程do_work處理請求。
do_work通過read接收客戶端數(shù)據(jù),轉(zhuǎn)換為大寫后返回。
客戶端創(chuàng)建連接后,循環(huán)發(fā)送數(shù)據(jù)和讀取服務(wù)器返回數(shù)據(jù)。
服務(wù)器使用線程處理每個連接請求,可以處理大量連接。
通過傳遞參數(shù),線程可以獲取客戶端信息。
設(shè)置線程分離態(tài),自動回收資源,實(shí)現(xiàn)高效的多線程服務(wù)器。
*/

/* server.c */

#include <stdio.h> // 標(biāo)準(zhǔn)輸入輸出頭文件

#include <string.h> // 字符串處理頭文件

#include <netinet/in.h> // socket編程頭文件

#include <arpa/inet.h> // inet地址轉(zhuǎn)換頭文件

#include <pthread.h> // 線程編程頭文件

#include "wrap.h" // 封裝的socket函數(shù)頭文件

#define MAXLINE 80 //最大讀取字符數(shù)

#define SERV_PORT 6666 //服務(wù)器端口

// 用于傳遞給線程的客戶信息
struct s_info {
struct sockaddr_in cliaddr; // 客戶socket地址
int connfd; // 客戶端連接套接字
};

// 線程處理函數(shù)
void *do_work(void *arg) {
  int n,i;
  struct s_info *ts = (struct s_info*)arg; // 獲取傳遞的參數(shù)

  char buf[MAXLINE]; // 數(shù)據(jù)緩沖區(qū)
  char str[INET_ADDRSTRLEN]; // 存儲socket地址的字符串

  // 設(shè)置線程為分離態(tài),線程結(jié)束時自動釋放資源
  pthread_detach(pthread_self());

  while (1) {
    // 獲取客戶端發(fā)送的數(shù)據(jù)
    n = Read(ts->connfd, buf, MAXLINE);  

    if (n == 0) { // 對端關(guān)閉連接
      printf("the other side has been closed.\n");
      break;
    }

    // 打印客戶端信息
    printf("received from %s at PORT %d\n",
        inet_ntop(AF_INET, &(*ts).cliaddr.sin_addr, str, sizeof(str)),
        ntohs((*ts).cliaddr.sin_port));

    // 將數(shù)據(jù)轉(zhuǎn)換為大寫
    for (i = 0; i < n; i++)
      buf[i] = toupper(buf[i]);

    // 發(fā)送轉(zhuǎn)換后的數(shù)據(jù)
    Write(ts->connfd, buf, n);
  }

  // 關(guān)閉客戶端連接
  Close(ts->connfd);
}

int main(void) {
  struct sockaddr_in servaddr, cliaddr; // 本地和客戶端的socket地址
  socklen_t cliaddr_len; // 客戶端socket地址長度
  int listenfd, connfd; // 監(jiān)聽和連接套接字
  int i = 0; 
  pthread_t tid; // 線程id
  struct s_info ts[256]; // 存儲所有客戶端信息的數(shù)組

  // 創(chuàng)建監(jiān)聽套接字
  listenfd = Socket(AF_INET, SOCK_STREAM, 0);

  // 初始化本地socket地址結(jié)構(gòu)
  bzero(&servaddr, sizeof(servaddr));
  servaddr.sin_family = AF_INET; 
  servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
  servaddr.sin_port = htons(SERV_PORT);

  // 綁定監(jiān)聽套接字到本地地址
  Bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));

  // 設(shè)置監(jiān)聽隊列長度
  Listen(listenfd, 20);

  printf("Accepting connections ...\n");

  // 循環(huán)接收客戶端連接
  while (1) {  

    // 接收一個客戶端連接
    cliaddr_len = sizeof(cliaddr);
    connfd = Accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);

    // 保存客戶信息
    ts[i].cliaddr = cliaddr;  
    ts[i].connfd = connfd;

    // 為客戶端創(chuàng)建線程處理請求
    pthread_create(&tid, NULL, do_work, (void*)&ts[i]);

    i++;
  }

  return 0;
}

/* client.c */

#include <stdio.h>

#include <string.h>

#include <unistd.h>

#include <netinet/in.h>

#include "wrap.h"

#define MAXLINE 80
#define SERV_PORT 6666

int main(int argc, char *argv[])
  struct sockaddr_in servaddr; // 服務(wù)器端地址結(jié)構(gòu)

  char buf[MAXLINE]; // 數(shù)據(jù)緩沖區(qū)
  int sockfd, n; // 套接字和讀返回值

  // 創(chuàng)建流式套接字
  sockfd = Socket(AF_INET, SOCK_STREAM, 0);

  // 初始化服務(wù)器地址結(jié)構(gòu)
  bzero(&servaddr, sizeof(servaddr));
  servaddr.sin_family = AF_INET;
  inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr); 
  servaddr.sin_port = htons(SERV_PORT);

  // 連接服務(wù)器
  Connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));

  // 循環(huán)發(fā)送數(shù)據(jù)和讀取服務(wù)器返回數(shù)據(jù)
  while (fgets(buf, MAXLINE, stdin) != NULL) {   

    // 向服務(wù)器發(fā)送數(shù)據(jù)
    Write(sockfd, buf, strlen(buf));  

    // 從服務(wù)器讀取數(shù)據(jù)
    n = Read(sockfd, buf, MAXLINE);

    // 判斷服務(wù)器是否關(guān)閉
    if (n == 0)
      printf("the other side has been closed.\n");
    else
      // 輸出服務(wù)器返回數(shù)據(jù)
      Write(STDOUT_FILENO, buf, n);
  }

  // 關(guān)閉socket連接
  Close(sockfd);
  return 0;
}

I/O多路復(fù)用服務(wù)器

select/poll/epoll使單線程可以同時處理多個連接請求。

select

優(yōu)點(diǎn): 可移植,使用簡單。

缺點(diǎn): 連接數(shù)受限,監(jiān)聽效率低。

要點(diǎn):

  • select監(jiān)聽讀寫事件
  • 每次循環(huán)重置監(jiān)聽描述符
  • 根據(jù)返回就緒數(shù)遍歷處理事件
  • 根據(jù)描述符狀態(tài)處理連接關(guān)閉等
例程分享:
/*
服務(wù)器端初始化socket地址,創(chuàng)建監(jiān)聽套接字。
使用select()監(jiān)聽套接字可讀事件。
調(diào)用accept()接收客戶端連接請求。
添加新的連接到select監(jiān)聽的文件描述符集。
循環(huán)掃描就緒文件描述符,調(diào)用read()接收客戶端數(shù)據(jù)。
對數(shù)據(jù)進(jìn)行處理后,調(diào)用write()返回給客戶端。
客戶端創(chuàng)建連接后,循環(huán)read()和write()實(shí)現(xiàn)雙向通信。
服務(wù)器使用select()處理多個連接,但依然是同步阻塞模型
*/

/* server.c */

/* server.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "wrap.h" // 這是一個自定義的頭文件,封裝了socket函數(shù)

#define MAXLINE 80 // 緩沖區(qū)的最大長度
#define SERV_PORT 6666 // 服務(wù)器端口號

int main(int argc, char *argv[])
{
    int i, maxi, maxfd, listenfd, connfd, sockfd;
    int nready, client[FD_SETSIZE]; // FD_SETSIZE通常為1024
    ssize_t n;
    fd_set rset, allset; // 用于select()的文件描述符集
    char buf[MAXLINE]; // 數(shù)據(jù)緩沖區(qū)
    char str[INET_ADDRSTRLEN]; // 地址字符串緩沖區(qū)
    socklen_t cliaddr_len;
    struct sockaddr_in cliaddr, servaddr; // 客戶端和服務(wù)器地址結(jié)構(gòu)

    listenfd = Socket(AF_INET, SOCK_STREAM, 0); // 創(chuàng)建一個socket

    bzero(&servaddr, sizeof(servaddr)); // 清零服務(wù)器地址結(jié)構(gòu)
    servaddr.sin_family = AF_INET; // 設(shè)置地址族為IPv4
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY); // 監(jiān)聽任何接口
    servaddr.sin_port = htons(SERV_PORT); // 設(shè)置端口號

    Bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); // 將socket綁定到地址

    Listen(listenfd, 20); // 監(jiān)聽連接,隊列長度為20

    maxfd = listenfd; // 初始化maxfd
    maxi = -1; // 初始化maxi

    for (i = 0; i < FD_SETSIZE; i++)
        client[i] = -1; // 初始化client[]

    FD_ZERO(&allset); // 清空allset
    FD_SET(listenfd, &allset); // 將listenfd添加到allset

    for ( ; ; ) { // 主服務(wù)器循環(huán)
        rset = allset; // 每次循環(huán)都重置rset
        nready = select(maxfd+1, &rset, NULL, NULL, NULL); // 調(diào)用select()

        if (nready < 0)
            perr_exit("select error"); // 如果select()返回錯誤,退出

        if (FD_ISSET(listenfd, &rset)) { // 如果有新的客戶端正在連接
            cliaddr_len = sizeof(cliaddr);
            connfd = Accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len); // 接受新的連接

            printf("received from %s at PORT %d\n",
                    inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
                    ntohs(cliaddr.sin_port)); // 打印客戶端的地址和端口

            for (i = 0; i < FD_SETSIZE; i++) {
                if (client[i] < 0) {
                    client[i] = connfd; // 將接受的文件描述符保存在client[]中
                    break;
                }
            }

            if (i == FD_SETSIZE) {
                fputs("too many clients\n", stderr); // 如果連接的客戶端過多,打印錯誤并退出
                exit(1);
            }

            FD_SET(connfd, &allset); // 將新的文件描述符添加到allset

            if (connfd > maxfd)
                maxfd = connfd; // 如果需要,更新maxfd

            if (i > maxi)
                maxi = i; // 如果需要,更新maxi

            if (--nready == 0)
                continue; // 如果沒有更多的就緒文件描述符,繼續(xù)下一次循環(huán)
        }

        for (i = 0; i <= maxi; i++) { // 檢查哪些客戶端有數(shù)據(jù)準(zhǔn)備好讀取
            if ( (sockfd = client[i]) < 0)
                continue;

            if (FD_ISSET(sockfd, &rset)) {
                if ( (n = Read(sockfd, buf, MAXLINE)) == 0) {
                    Close(sockfd); // 如果客戶端已經(jīng)關(guān)閉了連接,也關(guān)閉服務(wù)器端
                    FD_CLR(sockfd, &allset); // 從allset中移除文件描述符
                    client[i] = -1; // 從client[]中移除文件描述符
                } else {
                    int j;
                    for (j = 0; j < n; j++)
                        buf[j] = toupper(buf[j]); // 將數(shù)據(jù)轉(zhuǎn)換為大寫
                    Write(sockfd, buf, n); // 將數(shù)據(jù)寫回客戶端
                }

                if (--nready == 0)
                    break; // 如果沒有更多的就緒文件描述符,跳出循環(huán)
            }
        }
    }

    close(listenfd); // 關(guān)閉監(jiān)聽的socket
    return 0;
}

/* client.c */

/* client.c */
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include "wrap.h" // 這是一個自定義的頭文件,封裝了socket函數(shù)

#define MAXLINE 80 // 緩沖區(qū)的最大長度
#define SERV_PORT 6666 // 服務(wù)器端口號

int main(int argc, char *argv[])
{
    struct sockaddr_in servaddr; // 服務(wù)器地址結(jié)構(gòu)
    char buf[MAXLINE]; // 數(shù)據(jù)緩沖區(qū)
    int sockfd, n; // Socket文件描述符和讀取的字節(jié)數(shù)

    sockfd = Socket(AF_INET, SOCK_STREAM, 0); // 創(chuàng)建一個socket

    bzero(&servaddr, sizeof(servaddr)); // 清零服務(wù)器地址結(jié)構(gòu)
    servaddr.sin_family = AF_INET; // 設(shè)置地址族為IPv4
    inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr); // 設(shè)置服務(wù)器的IP地址
    servaddr.sin_port = htons(SERV_PORT); // 設(shè)置端口號

    Connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); // 連接到服務(wù)器

    while (fgets(buf, MAXLINE, stdin) != NULL) { // 主客戶端循環(huán)
        Write(sockfd, buf, strlen(buf)); // 將輸入寫入服務(wù)器
        n = Read(sockfd, buf, MAXLINE); // 讀取服務(wù)器的響應(yīng)

        if (n == 0)
            printf("the other side has been closed.\n"); // 如果服務(wù)器已經(jīng)關(guān)閉了連接,打印一條消息
        else
            Write(STDOUT_FILENO, buf, n); // 將服務(wù)器的響應(yīng)寫入stdout
    }

    Close(sockfd); // 關(guān)閉socket
    return 0;
}

poll

優(yōu)點(diǎn): 沒有連接數(shù)限制。

缺點(diǎn): 依然輪詢模型,效率低。

要點(diǎn):

  • pollfd結(jié)構(gòu)體監(jiān)聽事件
  • 每次循環(huán)遍歷pollfd處理就緒事件
  • 根據(jù)返回事件和錯誤處理連接狀態(tài)
例程分享:
/*
服務(wù)器端:
創(chuàng)建監(jiān)聽socket,綁定地址并監(jiān)聽
使用pollfd數(shù)組保存所有連接的文件描述符
調(diào)用poll監(jiān)聽socket上的事件,主要是POLLRDNORM讀事件
當(dāng)監(jiān)聽socket有事件時,表示有新連接,調(diào)用accept獲取新連接
將新連接的文件描述符添加到pollfd數(shù)組中,繼續(xù)監(jiān)聽讀事件
當(dāng)連接socket有讀事件時,調(diào)用read讀取客戶端數(shù)據(jù)
對數(shù)據(jù)進(jìn)行轉(zhuǎn)換處理,調(diào)用write將數(shù)據(jù)返回給客戶端
根據(jù)返回事件和錯誤情況判斷連接是否正常
客戶端:
創(chuàng)建socket,連接服務(wù)器地址
循環(huán)調(diào)用read讀取用戶輸入
調(diào)用write將用戶輸入發(fā)送給服務(wù)器
調(diào)用read讀取服務(wù)器返回數(shù)據(jù)
將服務(wù)器數(shù)據(jù)輸出到標(biāo)準(zhǔn)輸出
關(guān)閉連接
*/

/* server.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <poll.h>
#include <errno.h>
#include "wrap.h" // 這是一個自定義的頭文件,封裝了socket函數(shù)

#define MAXLINE 80 // 緩沖區(qū)的最大長度
#define SERV_PORT 6666 // 服務(wù)器端口號
#define OPEN_MAX 1024 // 最大的打開文件描述符數(shù)量

int main(int argc, char *argv[])
{
    int i, j, maxi, listenfd, connfd, sockfd;
    int nready;
    ssize_t n;
    char buf[MAXLINE], str[INET_ADDRSTRLEN];
    socklen_t clilen;
    struct pollfd client[OPEN_MAX]; // pollfd結(jié)構(gòu)體數(shù)組,用于存儲多個文件描述符
    struct sockaddr_in cliaddr, servaddr; // 客戶端和服務(wù)器地址結(jié)構(gòu)

    listenfd = Socket(AF_INET, SOCK_STREAM, 0); // 創(chuàng)建一個socket

    bzero(&servaddr, sizeof(servaddr)); // 清零服務(wù)器地址結(jié)構(gòu)
    servaddr.sin_family = AF_INET; // 設(shè)置地址族為IPv4
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY); // 監(jiān)聽任何接口
    servaddr.sin_port = htons(SERV_PORT); // 設(shè)置端口號

    Bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); // 將socket綁定到地址

    Listen(listenfd, 20); // 監(jiān)聽連接,隊列長度為20

    client[0].fd = listenfd;
    client[0].events = POLLRDNORM; // listenfd監(jiān)聽普通讀事件

    for (i = 1; i < OPEN_MAX; i++)
        client[i].fd = -1; // 用-1初始化client[]里剩下元素
    maxi = 0; // client[]數(shù)組有效元素中最大元素下標(biāo)

    for ( ; ; ) { // 主服務(wù)器循環(huán)
        nready = poll(client, maxi+1, -1); // 調(diào)用poll()函數(shù),阻塞等待文件描述符就緒

        if (client[0].revents & POLLRDNORM) { // 有客戶端鏈接請求
            clilen = sizeof(cliaddr);
            connfd = Accept(listenfd, (struct sockaddr *)&cliaddr, &clilen); // 接受新的連接

            printf("received from %s at PORT %d\n",
                    inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
                    ntohs(cliaddr.sin_port)); // 打印客戶端的地址和端口

            for (i = 1; i < OPEN_MAX; i++) {
                if (client[i].fd < 0) {
                    client[i].fd = connfd; // 找到client[]中空閑的位置,存放accept返回的connfd
                    break;
                }
            }

            if (i == OPEN_MAX)
                perr_exit("too many clients"); // 如果連接的客戶端過多,打印錯誤并退出

            client[i].events = POLLRDNORM; // 設(shè)置剛剛返回的connfd,監(jiān)控讀事件

            if (i > maxi)
                maxi = i; // 更新client[]中最大元素下標(biāo)

            if (--nready <= 0)
                continue; // 沒有更多就緒事件時,繼續(xù)回到poll阻塞
        }

        for (i = 1; i <= maxi; i++) { // 檢測client[]
            if ((sockfd = client[i].fd) < 0)
                continue;

            if (client[i].revents & (POLLRDNORM | POLLERR)) { // 如果有數(shù)據(jù)可讀或者有錯誤發(fā)生
                if ((n = Read(sockfd, buf, MAXLINE)) < 0) {
                    if (errno == ECONNRESET) { // 當(dāng)收到 RST標(biāo)志時
                        /* connection reset by client */
                        printf("client[%d] aborted connection\n", i);
                        Close(sockfd); // 關(guān)閉socket
                        client[i].fd = -1; // 從client[]中移除文件描述符
                    } else {
                        perr_exit("read error"); // 如果讀取錯誤,退出
                    }
                } else if (n == 0) {
                    /* connection closed by client */
                    printf("client[%d] closed connection\n", i);
                    Close(sockfd); // 關(guān)閉socket
                    client[i].fd = -1; // 從client[]中移除文件描述符
                } else {
                    for (j = 0; j < n; j++)
                        buf[j] = toupper(buf[j]); // 將數(shù)據(jù)轉(zhuǎn)換為大寫
                    Writen(sockfd, buf, n); // 將數(shù)據(jù)寫回客戶端
                }

                if (--nready <= 0)
                    break; // 如果沒有更多的就緒文件描述符,跳出循環(huán)
            }
        }
    }
    return 0;
}

/* client.c */
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include "wrap.h" // 這是一個自定義的頭文件,封裝了socket函數(shù)

#define MAXLINE 80 // 緩沖區(qū)的最大長度
#define SERV_PORT 6666 // 服務(wù)器端口號

int main(int argc, char *argv[])
{
    struct sockaddr_in servaddr; // 服務(wù)器地址結(jié)構(gòu)
    char buf[MAXLINE]; // 數(shù)據(jù)緩沖區(qū)
    int sockfd, n; // Socket文件描述符和讀取的字節(jié)數(shù)

    sockfd = Socket(AF_INET, SOCK_STREAM, 0); // 創(chuàng)建一個socket

    bzero(&servaddr, sizeof(servaddr)); // 清零服務(wù)器地址結(jié)構(gòu)
    servaddr.sin_family = AF_INET; // 設(shè)置地址族為IPv4
    inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr); // 設(shè)置服務(wù)器的IP地址
    servaddr.sin_port = htons(SERV_PORT); // 設(shè)置端口號

    Connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); // 連接到服務(wù)器

    while (fgets(buf, MAXLINE, stdin) != NULL) { // 主客戶端循環(huán)
        Write(sockfd, buf, strlen(buf)); // 將輸入寫入服務(wù)器
        n = Read(sockfd, buf, MAXLINE); // 讀取服務(wù)器的響應(yīng)

        if (n == 0)
            printf("the other side has been closed.\n"); // 如果服務(wù)器已經(jīng)關(guān)閉了連接,打印一條消息
        else
            Write(STDOUT_FILENO, buf, n); // 將服務(wù)器的響應(yīng)寫入stdout
    }

    Close(sockfd); // 關(guān)閉socket
    return 0;
}

epoll

優(yōu)點(diǎn):提高程序在大量并發(fā)連接中的系統(tǒng)CPU利用率,能夠高效處理大量并發(fā)請求。

缺點(diǎn):需要調(diào)整進(jìn)程的文件描述符限制,需要進(jìn)行連接管理。

要點(diǎn):

  • epoll_create創(chuàng)建句柄
  • epoll_ctl注冊和控制事件
  • epoll_wait等待就緒事件
  • 根據(jù)就緒事件處理請求
例程分享:
/*
服務(wù)器端:
服務(wù)器端的代碼主要完成以下任務(wù):

創(chuàng)建一個TCP socket并綁定到指定的IP地址和端口。
使用epoll創(chuàng)建一個事件監(jiān)聽列表,并將監(jiān)聽socket添加到這個列表中。
進(jìn)入一個無限循環(huán),使用epoll_wait()函數(shù)等待事件的發(fā)生。
當(dāng)新的客戶端連接時,接受連接并將新的socket添加到epoll的監(jiān)聽列表中。
當(dāng)已連接的客戶端發(fā)送數(shù)據(jù)時,讀取數(shù)據(jù),將數(shù)據(jù)轉(zhuǎn)換為大寫,然后將數(shù)據(jù)回寫到客戶端。
當(dāng)客戶端關(guān)閉連接時,從epoll的監(jiān)聽列表中移除這個socket,并關(guān)閉這個socket。
客戶端端:
客戶端的代碼主要完成以下任務(wù):

創(chuàng)建一個TCP socket并連接到服務(wù)器。
進(jìn)入一個無限循環(huán),從stdin讀取輸入,將輸入寫入服務(wù)器,然后讀取服務(wù)器的響應(yīng)并將響應(yīng)寫入stdout。
當(dāng)服務(wù)器關(guān)閉連接時,打印一條消息并退出循環(huán)。
*/

/* server.c */
// ...省略部分代碼...

// 創(chuàng)建一個TCP socket
listenfd = Socket(AF_INET, SOCK_STREAM, 0);

// 清零服務(wù)器地址結(jié)構(gòu)
bzero(&servaddr, sizeof(servaddr));
// 設(shè)置地址族為IPv4
servaddr.sin_family = AF_INET;
// 監(jiān)聽任何接口
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
// 設(shè)置端口號
servaddr.sin_port = htons(SERV_PORT);

// 將socket綁定到地址
Bind(listenfd, (struct sockaddr *) &servaddr, sizeof(servaddr));

// 監(jiān)聽連接,隊列長度為20
Listen(listenfd, 20);

// 創(chuàng)建epoll實(shí)例
efd = epoll_create(OPEN_MAX);
if (efd == -1)
	perr_exit("epoll_create");

// 設(shè)置監(jiān)聽事件為EPOLLIN(可讀事件),并將listenfd添加到epoll的監(jiān)聽列表中
tep.events = EPOLLIN; tep.data.fd = listenfd;
res = epoll_ctl(efd, EPOLL_CTL_ADD, listenfd, &tep);
if (res == -1)
	perr_exit("epoll_ctl");

// 主服務(wù)器循環(huán)
while (1) {
	// 調(diào)用epoll_wait()函數(shù),阻塞等待文件描述符就緒
	nready = epoll_wait(efd, ep, OPEN_MAX, -1);
	if (nready == -1)
		perr_exit("epoll_wait");

	// 遍歷就緒的文件描述符
	for (i = 0; i < nready; i++) {
		// 如果不是可讀事件,跳過
		if (!(ep[i].events & EPOLLIN))
			continue;

		// 如果是新的客戶端連接
		if (ep[i].data.fd == listenfd) {
			// 接受新的連接
			clilen = sizeof(cliaddr);
			connfd = Accept(listenfd, (struct sockaddr *)&cliaddr, &clilen);
			printf("received from %s at PORT %d\n", 
					inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)), 
					ntohs(cliaddr.sin_port));

			// 將新的socket添加到epoll的監(jiān)聽列表中
			tep.events = EPOLLIN; 
			tep.data.fd = connfd;
			res = epoll_ctl(efd, EPOLL_CTL_ADD, connfd, &tep);
			if (res == -1)
				perr_exit("epoll_ctl");
		} else {
			// 如果是已連接的客戶端發(fā)送的數(shù)據(jù)
			sockfd = ep[i].data.fd;
			n = Read(sockfd, buf, MAXLINE);
			if (n == 0) {
				// 如果客戶端關(guān)閉了連接,從epoll的監(jiān)聽列表中移除這個socket,并關(guān)閉這個socket
				res = epoll_ctl(efd, EPOLL_CTL_DEL, sockfd, NULL);
				if (res == -1)
					perr_exit("epoll_ctl");

				Close(sockfd);
				printf("client[%d] closed connection\n", j);
			} else {
				// 如果接收到數(shù)據(jù),將數(shù)據(jù)轉(zhuǎn)換為大寫,然后將數(shù)據(jù)回寫到客戶端
				for (j = 0; j < n; j++)
					buf[j] = toupper(buf[j]);
				Writen(sockfd, buf, n);
			}
		}
	}
}
// 關(guān)閉監(jiān)聽socket和epoll實(shí)例
close(listenfd);
close(efd);
return 0;

/* client.c */
// ...省略部分代碼...

// 創(chuàng)建一個TCP socket
sockfd = Socket(AF_INET, SOCK_STREAM, 0);

// 清零服務(wù)器地址結(jié)構(gòu)
bzero(&servaddr, sizeof(servaddr));
// 設(shè)置地址族為IPv4
servaddr.sin_family = AF_INET;
// 設(shè)置服務(wù)器的IP地址
inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
// 設(shè)置端口號
servaddr.sin_port = htons(SERV_PORT);

// 連接到服務(wù)器
Connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));

// 主客戶端循環(huán)
while (fgets(buf, MAXLINE, stdin) != NULL) {
	// 將輸入寫入服務(wù)器
	Write(sockfd, buf, strlen(buf));
	// 讀取服務(wù)器的響應(yīng)
	n = Read(sockfd, buf, MAXLINE);
	if (n == 0)
		// 如果服務(wù)器已經(jīng)關(guān)閉了連接,打印一條消息
		printf("the other side has been closed.\n");
	else
		// 將服務(wù)器的響應(yīng)寫入stdout
		Write(STDOUT_FILENO, buf, n);
}

// 關(guān)閉socket
Close(sockfd);
return 0;

總結(jié)建議

????????epoll服務(wù)器根據(jù)不同的需求和場景,我們可以選擇不同的高并發(fā)服務(wù)器設(shè)計方案。多進(jìn)程服務(wù)器、多線程服務(wù)器、I/O多路復(fù)用服務(wù)器和epoll服務(wù)器都有各自的優(yōu)缺點(diǎn)和適用場景。通過分享的例程和相關(guān)API函數(shù)的介紹,讀者可以更好地理解和選擇適合自己需求的設(shè)計方案,從而高效處理大量并發(fā)請求,滿足互聯(lián)網(wǎng)快速發(fā)展的需求。文章來源地址http://www.zghlxwxcb.cn/news/detail-828451.html

到了這里,關(guān)于Linux網(wǎng)絡(luò)編程之TCP/IP實(shí)現(xiàn)高并發(fā)網(wǎng)絡(luò)服務(wù)器設(shè)計指南的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請點(diǎn)擊違法舉報進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

  • Socket網(wǎng)絡(luò)編程(TCP/IP)實(shí)現(xiàn)服務(wù)器/客戶端通信。

    Socket網(wǎng)絡(luò)編程(TCP/IP)實(shí)現(xiàn)服務(wù)器/客戶端通信。

    一.前言 回顧之前進(jìn)程間通信(無名管道,有名管道,消息隊列,共享內(nèi)存,信號,信號量),都是在同一主機(jī)由內(nèi)核來完成的通信。 那不同主機(jī)間該怎么通信呢? 可以使用Socket編程來實(shí)現(xiàn)。 Socket編程可以通過網(wǎng)絡(luò)來實(shí)現(xiàn)實(shí)現(xiàn)不同主機(jī)之間的通訊。 二.Socket編程的網(wǎng)絡(luò)模型如

    2024年02月08日
    瀏覽(37)
  • Linux socket網(wǎng)絡(luò)編程實(shí)戰(zhàn)(tcp)實(shí)現(xiàn)雙方聊天

    Linux socket網(wǎng)絡(luò)編程實(shí)戰(zhàn)(tcp)實(shí)現(xiàn)雙方聊天

    在上節(jié)已經(jīng)系統(tǒng)介紹了大致的流程和相關(guān)的API,這節(jié)就開始寫代碼! 回顧上節(jié)的流程: 創(chuàng)建一個NET文件夾 來存放網(wǎng)絡(luò)編程相關(guān)的代碼: 這部分先實(shí)現(xiàn)服務(wù)器的連接部分的代碼并進(jìn)行驗(yàn)證 server1.c: 代碼驗(yàn)證: 先編譯并運(yùn)行這部分代碼: 可見,此時沒有客戶端進(jìn)行連接,程

    2024年02月03日
    瀏覽(23)
  • 【探索Linux】P.29(網(wǎng)絡(luò)編程套接字 —— 簡單的TCP網(wǎng)絡(luò)程序模擬實(shí)現(xiàn))

    【探索Linux】P.29(網(wǎng)絡(luò)編程套接字 —— 簡單的TCP網(wǎng)絡(luò)程序模擬實(shí)現(xiàn))

    在前一篇文章中,我們詳細(xì)介紹了UDP協(xié)議和TCP協(xié)議的特點(diǎn)以及它們之間的異同點(diǎn)。 本文將延續(xù)上文內(nèi)容,重點(diǎn)討論簡單的TCP網(wǎng)絡(luò)程序模擬實(shí)現(xiàn) 。通過本文的學(xué)習(xí),讀者將能夠深入了解TCP協(xié)議的實(shí)際應(yīng)用,并掌握如何編寫簡單的TCP網(wǎng)絡(luò)程序。讓我們一起深入探討TCP網(wǎng)絡(luò)程序的

    2024年04月14日
    瀏覽(98)
  • 《TCP IP網(wǎng)絡(luò)編程》

    《TCP IP網(wǎng)絡(luò)編程》

    ? ? ? ? 2023.6.28 正式開始學(xué)習(xí)網(wǎng)絡(luò)編程。 每一章每一節(jié)的筆記都會記錄在博客中以便復(fù)習(xí)。 ? ? ? ? 網(wǎng)絡(luò)編程又叫套接字編程。所謂網(wǎng)絡(luò)編程,就是編寫程序使兩臺連網(wǎng)的計算機(jī)相互交換數(shù)據(jù)。 為什么叫套接字編程? 我們平常將插頭插入插座上就能從電網(wǎng)中獲取電力,同

    2024年02月11日
    瀏覽(25)
  • 《TCP/IP網(wǎng)絡(luò)編程》閱讀筆記--基于Windows實(shí)現(xiàn)Hello Word服務(wù)器端和客戶端

    《TCP/IP網(wǎng)絡(luò)編程》閱讀筆記--基于Windows實(shí)現(xiàn)Hello Word服務(wù)器端和客戶端

    目錄 1--Hello Word服務(wù)器端 2--客戶端 3--編譯運(yùn)行 3-1--編譯服務(wù)器端 3-2--編譯客戶端 3-3--運(yùn)行 運(yùn)行結(jié)果:

    2024年02月10日
    瀏覽(63)
  • TCP/IP網(wǎng)絡(luò)編程(三)

    TCP/IP網(wǎng)絡(luò)編程(三)

    多播(Multicast)方式的數(shù)據(jù)傳輸是 基于 UDP 完成的 。因此 ,與 UDP 服務(wù)器端/客戶端的實(shí)現(xiàn)方式非常接近。區(qū)別在于,UDP 數(shù)據(jù)傳輸以單一目標(biāo)進(jìn)行,而多播數(shù)據(jù) 同時傳遞到加入(注冊)特定組的大量主機(jī) 。換言之, 采用多播方式時,可以同時向多個主機(jī)傳遞數(shù)據(jù) 。 14.1.1 多

    2024年02月03日
    瀏覽(36)
  • TCP/IP網(wǎng)絡(luò)編程(一)

    TCP/IP網(wǎng)絡(luò)編程(一)

    1.1.1 構(gòu)建打電話套接字 以電話機(jī)打電話的方式來理解套接字。 **調(diào)用 socket 函數(shù)(安裝電話機(jī))時進(jìn)行的對話:**有了電話機(jī)才能安裝電話,于是就要準(zhǔn)備一個電話機(jī),下面函數(shù)相當(dāng)于電話機(jī)的套接字。 **調(diào)用 bind 函數(shù)(分配電話號碼)時進(jìn)行的對話:**套接字同樣如此。就想

    2024年02月03日
    瀏覽(26)
  • TCP/IP網(wǎng)絡(luò)編程(二)

    TCP/IP網(wǎng)絡(luò)編程(二)

    本章將討論如何優(yōu)雅地斷開相互連接的套接字。之前用的方法不夠優(yōu)雅是因?yàn)椋覀兪钦{(diào)用 close 或 closesocket 函數(shù)單方面斷開連接的。 TCP中的斷開連接過程比建立連接過程更重要,因?yàn)檫B接過程中一般不會出現(xiàn)大的變數(shù),但斷開過程有可能發(fā)生預(yù)想不到的情況,因此應(yīng)準(zhǔn)確掌

    2024年02月03日
    瀏覽(30)
  • [Linux] 網(wǎng)絡(luò)編程 - 初見TCP套接字編程: 實(shí)現(xiàn)簡單的單進(jìn)程、多進(jìn)程、多線程、線程池tcp服務(wù)器

    [Linux] 網(wǎng)絡(luò)編程 - 初見TCP套接字編程: 實(shí)現(xiàn)簡單的單進(jìn)程、多進(jìn)程、多線程、線程池tcp服務(wù)器

    網(wǎng)絡(luò)的上一篇文章, 我們介紹了網(wǎng)絡(luò)變成的一些重要的概念, 以及 UDP套接字的編程演示. 還實(shí)現(xiàn)了一個簡單更簡陋的UDP公共聊天室. [Linux] 網(wǎng)絡(luò)編程 - 初見UDP套接字編程: 網(wǎng)絡(luò)編程部分相關(guān)概念、TCP、UDP協(xié)議基本特點(diǎn)、網(wǎng)絡(luò)字節(jié)序、socket接口使用、簡單的UDP網(wǎng)絡(luò)及聊天室實(shí)現(xiàn)…

    2024年02月16日
    瀏覽(32)
  • TCP/IP網(wǎng)絡(luò)編程(一) 理解網(wǎng)絡(luò)編程和套接字

    網(wǎng)絡(luò)編程和套接字概要 網(wǎng)絡(luò)編程就是編寫程序使兩臺聯(lián)網(wǎng)的計算機(jī)相互交換數(shù)據(jù) 為了與遠(yuǎn)程計算機(jī)進(jìn)行數(shù)據(jù)傳輸,需要連接因特網(wǎng),而編程種的套接字就是用來連接該網(wǎng)絡(luò)的工具。 構(gòu)建套接字 1.調(diào)用soecket函數(shù)創(chuàng)建套接字 2.調(diào)用bind函數(shù)給套接字分配地址 3.調(diào)用listen函數(shù)將套

    2024年02月11日
    瀏覽(857)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包