亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 服務器 > Web服務器 > 正文

Linux Socket 編程簡介和實現

2024-09-01 13:53:58
字體:
來源:轉載
供稿:網友

在 TCP/IP 協議中,"IP地址 + TCP或UDP端口號" 可以唯一標識網絡通訊中的一個進程,"IP地址+端口號" 就稱為 socket。本文以一個簡單的 TCP 協議為例,介紹如何創建基于 TCP 協議的網絡程序。

TCP 協議通訊流程

下圖描述了 TCP 協議的通訊流程(此圖來自互聯網):

Linux,Socket,編程

下圖則描述 TCP 建立連接的過程(此圖來自互聯網):

Linux,Socket,編程

服務器調用 socket()、bind()、listen() 函數完成初始化后,調用 accept() 阻塞等待,處于監聽端口的狀態,客戶端調用 socket() 初始化后,調用 connect() 發出 SYN 段并阻塞等待服務器應答,服務器應答一個SYN-ACK 段,客戶端收到后從 connect() 返回,同時應答一個 ACK 段,服務器收到后從 accept() 返回。

TCP 連接建立后數據傳輸的過程:

建立連接后,TCP 協議提供全雙工的通信服務,但是一般的客戶端/服務器程序的流程是由客戶端主動發起請求,服務器被動處理請求,一問一答的方式。因此,服務器從 accept() 返回后立刻調用 read(),讀 socket 就像讀管道一樣,如果沒有數據到達就阻塞等待,這時客戶端調用 write() 發送請求給服務器,服務器收到后從 read() 返回,對客戶端的請求進行處理,在此期間客戶端調用 read() 阻塞等待服務器的應答,服務器調用 write() 將處理結果發回給客戶端,再次調用 read() 阻塞等待下一條請求,客戶端收到后從 read() 返回,發送下一條請求,如此循環下去。

下圖描述了關閉 TCP 連接的過程:

Linux,Socket,編程

如果客戶端沒有更多的請求了,就調用 close() 關閉連接,就像寫端關閉的管道一樣,服務器的 read() 返回 0,這樣服務器就知道客戶端關閉了連接,也調用 close() 關閉連接。注意,任何一方調用 close() 后,連接的兩個傳輸方向都關閉,不能再發送數據了。如果一方調用 shutdown() 則連接處于半關閉狀態,仍可接收對方發來的數據。

在學習 socket 編程時要注意應用程序和 TCP 協議層是如何交互的:

  1. 應用程序調用某個 socket 函數時 TCP 協議層完成什么動作,比如調用 connect() 會發出 SYN 段
  2. 應用程序如何知道 TCP 協議層的狀態變化,比如從某個阻塞的 socket 函數返回就表明 TCP 協議收到了某些段,再比如 read() 返回 0 就表明收到了 FIN 段

下面通過一個簡單的 TCP 網絡程序來理解相關概念。程序分為服務器端和客戶端兩部分,它們之間通過 socket 進行通信。

服務器端程序

下面是一個非常簡單的服務器端程序,它從客戶端讀字符,然后將每個字符轉換為大寫并回送給客戶端:

#include <stdio.h>#include <stdlib.h>#include <string.h>#include <unistd.h>#include <netinet/in.h>#define MAXLINE 80#define SERV_PORT 8000int main(void){  struct sockaddr_in servaddr, cliaddr;  socklen_t cliaddr_len;  int listenfd, connfd;  char buf[MAXLINE];  char str[INET_ADDRSTRLEN];  int i, n;  // socket() 打開一個網絡通訊端口,如果成功的話,  // 就像 open() 一樣返回一個文件描述符,  // 應用程序可以像讀寫文件一樣用 read/write 在網絡上收發數據。  listenfd = socket(AF_INET, SOCK_STREAM, 0);  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 和 servaddr 綁定在一起,  // 使 listenfd 這個用于網絡通訊的文件描述符監聽 servaddr 所描述的地址和端口號。  bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));  // listen() 聲明 listenfd 處于監聽狀態,  // 并且最多允許有 20 個客戶端處于連接待狀態,如果接收到更多的連接請求就忽略。  listen(listenfd, 20);  printf("Accepting connections .../n");  while (1)  {    cliaddr_len = sizeof(cliaddr);    // 典型的服務器程序可以同時服務于多個客戶端,    // 當有客戶端發起連接時,服務器調用的 accept() 返回并接受這個連接,    // 如果有大量的客戶端發起連接而服務器來不及處理,尚未 accept 的客戶端就處于連接等待狀態。    connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);       n = read(connfd, buf, MAXLINE);    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 < n; i++)    {      buf[i] = toupper(buf[i]);    }          write(connfd, buf, n);    close(connfd);  }}

把上面的代碼保存到文件 server.c 文件中,并執行下面的命令編譯:

$ gcc server.c -o server

然后運行編譯出來的 server 程序:

$ ./server

此時我們可以通過 ss 命令來查看主機上的端口監聽情況:

Linux,Socket,編程

如上圖所示,server 程序已經開始監聽主機的 8000 端口了。

下面讓我們介紹一下這段程序中用到的 socket 相關的 API。

int socket(int family, int type, int protocol);

socket() 打開一個網絡通訊端口,如果成功的話,就像 open() 一樣返回一個文件描述符,應用程序可以像讀寫文件一樣用 read/write 在網絡上收發數據。對于IPv4,family 參數指定為 AF_INET。對于 TCP 協議,type 參數指定為 SOCK_STREAM,表示面向流的傳輸協議。如果是 UDP 協議,則 type 參數指定為 SOCK_DGRAM,表示面向數據報的傳輸協議。protocol 指定為 0 即可。

int bind(int sockfd, const struct sockaddr *myaddr, socklen_t addrlen);

服務器需要調用 bind 函數綁定一個固定的網絡地址和端口號。bind() 的作用是將參數 sockfd 和 myaddr 綁定在一起,使 sockfd 這個用于網絡通訊的文件描述符監聽 myaddr 所描述的地址和端口號。struct sockaddr *是一個通用指針類型,myaddr 參數實際上可以接受多種協議的 sockaddr 結構體,而它們的長度各不相同,所以需要第三個參數 addrlen 指定結構體的長度。

程序中對 myaddr 參數的初始化為:

bzero(&servaddr, sizeof(servaddr));servaddr.sin_family = AF_INET;servaddr.sin_addr.s_addr = htonl(INADDR_ANY);servaddr.sin_port = htons(SERV_PORT);

首先將整個結構體清零,然后設置地址類型為 AF_INET,網絡地址為 INADDR_ANY,這個宏表示本地的任意 IP 地址,因為服務器可能有多個網卡,每個網卡也可能綁定多個 IP 地址,這樣設置可以在所有的 IP 地址上監聽,直到與某個客戶端建立了連接時才確定下來到底用哪個 IP 地址,端口號為 SERV_PORT,我們定義為 8000。

int listen(int sockfd, int backlog);

listen() 聲明 sockfd 處于監聽狀態,并且最多允許有 backlog 個客戶端處于連接待狀態,如果接收到更多的連接請求就忽略。

int accept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen);

三方握手完成后,服務器調用 accept() 接受連接,如果服務器調用 accept() 時還沒有客戶端的連接請求,就阻塞等待直到有客戶端連接上來。cliaddr 是一個傳出參數,accept() 返回時傳出客戶端的地址和端口號。addrlen 參數是一個傳入傳出參數(value-result argument),傳入的是調用者提供的緩沖區 cliaddr 的長度以避免緩沖區溢出問題,傳出的是客戶端地址結構體的實際長度(有可能沒有占滿調用者提供的緩沖區)。如果給 cliaddr 參數傳 NULL,表示不關心客戶端的地址。

服務器程序的主要結構如下:

while (1){  cliaddr_len = sizeof(cliaddr);  connfd = accept(listenfd,      (struct sockaddr *)&cliaddr, &cliaddr_len);  n = read(connfd, buf, MAXLINE);  ......  close(connfd);}

整個是一個 while 死循環,每次循環處理一個客戶端連接。由于 cliaddr_len 是傳入傳出參數,每次調用 accept( ) 之前應該重新賦初值。accept() 的參數 listenfd 是先前的監聽文件描述符,而 accept() 的返回值是另外一個文件描述符 connfd,之后與客戶端之間就通過這個 connfd 通訊,最后關閉 connfd 斷開連接,而不關閉 listenfd,再次回到循環開頭 listenfd 仍然用作 accept 的參數。

客戶端程序

下面是客戶端程序,它從命令行參數中獲得一個字符串發給服務器,然后接收服務器返回的字符串并打印:

#include <stdio.h>#include <stdlib.h>#include <string.h>#include <unistd.h>#include <netinet/in.h>#include <arpa/inet.h>#define MAXLINE 80#define SERV_PORT 8000int main(int argc, char *argv[]){  struct sockaddr_in servaddr;  char buf[MAXLINE];  int sockfd, n;  char *str;    if (argc != 2)  {    fputs("usage: ./client message/n", stderr);    exit(1);  }  str = argv[1];    sockfd = socket(AF_INET, SOCK_STREAM, 0);  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);    // 由于客戶端不需要固定的端口號,因此不必調用 bind(),客戶端的端口號由內核自動分配。  // 注意,客戶端不是不允許調用 bind(),只是沒有必要調用 bind() 固定一個端口號,  // 服務器也不是必須調用 bind(),但如果服務器不調用 bind(),內核會自動給服務器分配監聽端口,  // 每次啟動服務器時端口號都不一樣,客戶端要連接服務器就會遇到麻煩。  connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));  write(sockfd, str, strlen(str));  n = read(sockfd, buf, MAXLINE);  printf("Response from server:/n");  write(STDOUT_FILENO, buf, n);  printf("/n");  close(sockfd);  return 0;}

把上面的代碼保存到文件 client.c 文件中,并執行下面的命令編譯:

$ gcc client.c -o client

然后運行編譯出來的 client 程序:

$ ./client hello

此時服務器端會收到請求并返回轉換為大寫的字符串,并輸出相應的信息:

Linux,Socket,編程

而客戶端在發送請求后會收到轉換過的字符串:

Linux,Socket,編程

在客戶端的代碼中有兩點需要注意:

1. 由于客戶端不需要固定的端口號,因此不必調用 bind(),客戶端的端口號由內核自動分配。
2. 客戶端需要調用 connect() 連接服務器,connect 和 bind 的參數形式一致,區別在于 bind 的參數是自己的地址,而 connect 的參數是對方的地址。

至此我們已經使用 socket 技術完成了一個最簡單的客戶端服務器程序,雖然離實際應用還非常遙遠,但就學習而言已經足夠了。

提升服務器端的響應能力

雖然我們的服務器程序可以響應客戶端的請求,但是這樣的效率太低了。一般情況下服務器程序需要能夠同時處理多個客戶端的請求??梢酝ㄟ^ fork 系統調用創建子進程來處理每個請求,下面是大體的實現思路:

listenfd = socket(...);bind(listenfd, ...);listen(listenfd, ...);while (1){  connfd = accept(listenfd, ...);  n = fork();  if (n == -1)  {    perror("call to fork");    exit(1);  }  else if (n == 0)  {    // 在子進程中處理客戶端的請求。    close(listenfd);    while (1)    {      read(connfd, ...);      ...      write(connfd, ...);    }    close(connfd);    exit(0);  }  else  {    close(connfd);  }  }

此時父進程的任務就是不斷的創建子進程,而由子進程去響應客戶端的具體請求。通過這種方式,可以極大的提升服務器端的響應能力。

總結

本文通過一個簡單的建基于 TCP 協議的網絡程序介紹了 linux socket 編程中的基本概念。通過它我們可以了解到 socket 程序工作的基本原理,以及一些解決性能問題的思路。

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持VEVB武林網。


發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
亚洲丁香婷深爱综合| 日韩中文字幕网址| 久久精品国产欧美激情| 91视频国产一区| 国产亚洲成精品久久| 久久久www成人免费精品张筱雨| 国产丝袜一区二区三区| 亚洲欧美日韩直播| 欧美日韩国产成人| 亚洲国产另类久久精品| 国产亚洲精品久久久久久777| 中国日韩欧美久久久久久久久| 日韩经典一区二区三区| 亚洲黄色www网站| 欧美日韩国产页| 国产91网红主播在线观看| 国产男人精品视频| 国产乱肥老妇国产一区二| 国产一区二区久久精品| 久久视频中文字幕| 欧美视频在线免费看| 亚洲最大的免费| 色偷偷888欧美精品久久久| 亚洲一区二区三区香蕉| 久久久亚洲福利精品午夜| 亚洲国产女人aaa毛片在线| 96pao国产成视频永久免费| 亚洲国产高清高潮精品美女| 琪琪亚洲精品午夜在线| 欧美一区深夜视频| 91精品国产91久久久久久吃药| 97视频免费观看| 国产精品精品视频一区二区三区| 日本中文字幕久久看| 精品中文视频在线| 中文字幕av日韩| 国产精品欧美风情| 亚洲国产成人精品一区二区| 日韩激情视频在线播放| 91sao在线观看国产| 庆余年2免费日韩剧观看大牛| 久久伊人精品视频| 国产在线拍揄自揄视频不卡99| 久久久久久一区二区三区| 亚洲人成欧美中文字幕| 国产91精品不卡视频| 亚洲人成网站777色婷婷| 国产精品久久久久久久久借妻| 亚洲久久久久久久久久久| 爱福利视频一区| 午夜精品久久久久久久99黑人| 国产精品自拍小视频| 国产精品高清网站| 久久久久亚洲精品成人网小说| 久久亚洲影音av资源网| 国产精品日韩精品| 一本色道久久88综合日韩精品| www.日韩av.com| 亚洲成人网久久久| 成人看片人aa| 欧美老女人性视频| 国语自产精品视频在线看抢先版图片| 韩国欧美亚洲国产| 国产成人精品在线| 91国产精品电影| 91在线观看免费高清完整版在线观看| 日韩美女免费视频| 日韩av免费在线观看| 国产精品欧美亚洲777777| 亚洲日本成人女熟在线观看| 国产一区二区三区在线免费观看| 国产精品美女无圣光视频| 日韩中文字幕在线观看| 亚洲人成免费电影| 国产精品久久久久久久久免费看| 川上优av一区二区线观看| 国产精品最新在线观看| 亚洲人成在线观看网站高清| 色琪琪综合男人的天堂aⅴ视频| 欧美性高跟鞋xxxxhd| 日韩欧美大尺度| 在线电影中文日韩| 国产精品久久久久久久久久久久| 久久视频在线看| 国产精品a久久久久久| 国产福利视频一区| 日韩欧美国产视频| 日韩大陆欧美高清视频区| 日韩中文字幕在线视频播放| 亚洲精品短视频| 国产精品99蜜臀久久不卡二区| 一区二区三区动漫| 欧美激情精品久久久久久变态| 日本一区二区三区四区视频| 91精品国产综合久久香蕉| 日韩美女写真福利在线观看| 欧美午夜激情在线| 欧美区二区三区| 免费91麻豆精品国产自产在线观看| 在线免费观看羞羞视频一区二区| 欧美最猛黑人xxxx黑人猛叫黄| 国产精品黄页免费高清在线观看| 2019中文字幕免费视频| 亚洲成人黄色在线| 一区国产精品视频| 日韩av电影手机在线观看| 久久久久久久久综合| 91国自产精品中文字幕亚洲| 精品视频久久久久久| 亚洲在线观看视频| 日韩一区二区在线视频| 国产成人短视频| 中文字幕免费精品一区高清| 久久夜色精品亚洲噜噜国产mv| 亚洲国产精品专区久久| 欧美最猛黑人xxxx黑人猛叫黄| 国产精品劲爆视频| 亚洲日韩欧美视频一区| 色婷婷av一区二区三区在线观看| 亚洲成人三级在线| 亚洲久久久久久久久久久| 亚洲男人第一av网站| 91国内产香蕉| 亚洲最新av网址| 欧美亚洲国产日韩2020| 亚洲激情久久久| 性色av一区二区咪爱| 欧美激情在线观看视频| 日本成人免费在线| 一区二区中文字幕| 亚洲v日韩v综合v精品v| 久久久久久com| 欧美有码在线视频| 国产一区二区成人| 成人伊人精品色xxxx视频| 亚洲精品久久久久中文字幕欢迎你| 一本色道久久综合狠狠躁篇的优点| 91中文字幕一区| 欧美精品成人91久久久久久久| 国产精品一二三视频| 欧美人在线观看| 美日韩精品视频免费看| 国产精品日韩久久久久| 国产精品成人av在线| 亚洲欧洲国产一区| 亚洲欧美另类国产| 91在线观看免费| 日韩精品视频中文在线观看| 国产亚洲一区二区在线| 亚洲高清不卡av| 亚洲免费av电影| 欧美午夜美女看片| 日韩亚洲在线观看| 欧美精品在线看| 91精品视频在线免费观看| 久久99久国产精品黄毛片入口| 欧美中文在线免费| 欧美美最猛性xxxxxx| 亚洲亚裔videos黑人hd| 午夜精品久久久久久久久久久久久| 欧美一区二区三区精品电影| 精品亚洲精品福利线在观看| 久久久久久亚洲精品不卡| 国产z一区二区三区|