欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Linux Socket 編程簡介和實(shí)現(xiàn)

 更新時(shí)間:2018年02月27日 10:22:13   作者:sparkdev  
這篇文章主要介紹了Linux Socket 編程簡介和實(shí)現(xiàn),小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧

在 TCP/IP 協(xié)議中,"IP地址 + TCP或UDP端口號(hào)" 可以唯一標(biāo)識(shí)網(wǎng)絡(luò)通訊中的一個(gè)進(jìn)程,"IP地址+端口號(hào)" 就稱為 socket。本文以一個(gè)簡單的 TCP 協(xié)議為例,介紹如何創(chuàng)建基于 TCP 協(xié)議的網(wǎng)絡(luò)程序。

TCP 協(xié)議通訊流程

下圖描述了 TCP 協(xié)議的通訊流程(此圖來自互聯(lián)網(wǎng)):

下圖則描述 TCP 建立連接的過程(此圖來自互聯(lián)網(wǎng)):

服務(wù)器調(diào)用 socket()、bind()、listen() 函數(shù)完成初始化后,調(diào)用 accept() 阻塞等待,處于監(jiān)聽端口的狀態(tài),客戶端調(diào)用 socket() 初始化后,調(diào)用 connect() 發(fā)出 SYN 段并阻塞等待服務(wù)器應(yīng)答,服務(wù)器應(yīng)答一個(gè)SYN-ACK 段,客戶端收到后從 connect() 返回,同時(shí)應(yīng)答一個(gè) ACK 段,服務(wù)器收到后從 accept() 返回。

TCP 連接建立后數(shù)據(jù)傳輸?shù)倪^程:

建立連接后,TCP 協(xié)議提供全雙工的通信服務(wù),但是一般的客戶端/服務(wù)器程序的流程是由客戶端主動(dòng)發(fā)起請(qǐng)求,服務(wù)器被動(dòng)處理請(qǐng)求,一問一答的方式。因此,服務(wù)器從 accept() 返回后立刻調(diào)用 read(),讀 socket 就像讀管道一樣,如果沒有數(shù)據(jù)到達(dá)就阻塞等待,這時(shí)客戶端調(diào)用 write() 發(fā)送請(qǐng)求給服務(wù)器,服務(wù)器收到后從 read() 返回,對(duì)客戶端的請(qǐng)求進(jìn)行處理,在此期間客戶端調(diào)用 read() 阻塞等待服務(wù)器的應(yīng)答,服務(wù)器調(diào)用 write() 將處理結(jié)果發(fā)回給客戶端,再次調(diào)用 read() 阻塞等待下一條請(qǐng)求,客戶端收到后從 read() 返回,發(fā)送下一條請(qǐng)求,如此循環(huán)下去。

下圖描述了關(guān)閉 TCP 連接的過程:

如果客戶端沒有更多的請(qǐng)求了,就調(diào)用 close() 關(guān)閉連接,就像寫端關(guān)閉的管道一樣,服務(wù)器的 read() 返回 0,這樣服務(wù)器就知道客戶端關(guān)閉了連接,也調(diào)用 close() 關(guān)閉連接。注意,任何一方調(diào)用 close() 后,連接的兩個(gè)傳輸方向都關(guān)閉,不能再發(fā)送數(shù)據(jù)了。如果一方調(diào)用 shutdown() 則連接處于半關(guān)閉狀態(tài),仍可接收對(duì)方發(fā)來的數(shù)據(jù)。

在學(xué)習(xí) socket 編程時(shí)要注意應(yīng)用程序和 TCP 協(xié)議層是如何交互的:

  1. 應(yīng)用程序調(diào)用某個(gè) socket 函數(shù)時(shí) TCP 協(xié)議層完成什么動(dòng)作,比如調(diào)用 connect() 會(huì)發(fā)出 SYN 段
  2. 應(yīng)用程序如何知道 TCP 協(xié)議層的狀態(tài)變化,比如從某個(gè)阻塞的 socket 函數(shù)返回就表明 TCP 協(xié)議收到了某些段,再比如 read() 返回 0 就表明收到了 FIN 段

下面通過一個(gè)簡單的 TCP 網(wǎng)絡(luò)程序來理解相關(guān)概念。程序分為服務(wù)器端和客戶端兩部分,它們之間通過 socket 進(jìn)行通信。

服務(wù)器端程序

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

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>

#define MAXLINE 80
#define SERV_PORT 8000

int 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() 打開一個(gè)網(wǎng)絡(luò)通訊端口,如果成功的話,
  // 就像 open() 一樣返回一個(gè)文件描述符,
  // 應(yīng)用程序可以像讀寫文件一樣用 read/write 在網(wǎng)絡(luò)上收發(fā)數(shù)據(jù)。
  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() 的作用是將參數(shù) listenfd 和 servaddr 綁定在一起,
  // 使 listenfd 這個(gè)用于網(wǎng)絡(luò)通訊的文件描述符監(jiān)聽 servaddr 所描述的地址和端口號(hào)。
  bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));

  // listen() 聲明 listenfd 處于監(jiān)聽狀態(tài),
  // 并且最多允許有 20 個(gè)客戶端處于連接待狀態(tài),如果接收到更多的連接請(qǐng)求就忽略。
  listen(listenfd, 20);

  printf("Accepting connections ...\n");
  while (1)
  {
    cliaddr_len = sizeof(cliaddr);
    // 典型的服務(wù)器程序可以同時(shí)服務(wù)于多個(gè)客戶端,
    // 當(dāng)有客戶端發(fā)起連接時(shí),服務(wù)器調(diào)用的 accept() 返回并接受這個(gè)連接,
    // 如果有大量的客戶端發(fā)起連接而服務(wù)器來不及處理,尚未 accept 的客戶端就處于連接等待狀態(tài)。
    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 文件中,并執(zhí)行下面的命令編譯:

$ gcc server.c -o server

然后運(yùn)行編譯出來的 server 程序:

$ ./server

此時(shí)我們可以通過 ss 命令來查看主機(jī)上的端口監(jiān)聽情況:

如上圖所示,server 程序已經(jīng)開始監(jiān)聽主機(jī)的 8000 端口了。

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

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

socket() 打開一個(gè)網(wǎng)絡(luò)通訊端口,如果成功的話,就像 open() 一樣返回一個(gè)文件描述符,應(yīng)用程序可以像讀寫文件一樣用 read/write 在網(wǎng)絡(luò)上收發(fā)數(shù)據(jù)。對(duì)于IPv4,family 參數(shù)指定為 AF_INET。對(duì)于 TCP 協(xié)議,type 參數(shù)指定為 SOCK_STREAM,表示面向流的傳輸協(xié)議。如果是 UDP 協(xié)議,則 type 參數(shù)指定為 SOCK_DGRAM,表示面向數(shù)據(jù)報(bào)的傳輸協(xié)議。protocol 指定為 0 即可。

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

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

程序中對(duì) myaddr 參數(shù)的初始化為:

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

首先將整個(gè)結(jié)構(gòu)體清零,然后設(shè)置地址類型為 AF_INET,網(wǎng)絡(luò)地址為 INADDR_ANY,這個(gè)宏表示本地的任意 IP 地址,因?yàn)榉?wù)器可能有多個(gè)網(wǎng)卡,每個(gè)網(wǎng)卡也可能綁定多個(gè) IP 地址,這樣設(shè)置可以在所有的 IP 地址上監(jiān)聽,直到與某個(gè)客戶端建立了連接時(shí)才確定下來到底用哪個(gè) IP 地址,端口號(hào)為 SERV_PORT,我們定義為 8000。

int listen(int sockfd, int backlog);

listen() 聲明 sockfd 處于監(jiān)聽狀態(tài),并且最多允許有 backlog 個(gè)客戶端處于連接待狀態(tài),如果接收到更多的連接請(qǐng)求就忽略。

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

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

服務(wù)器程序的主要結(jié)構(gòu)如下:

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

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

客戶端程序

下面是客戶端程序,它從命令行參數(shù)中獲得一個(gè)字符串發(fā)給服務(wù)器,然后接收服務(wù)器返回的字符串并打?。?/p>

#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 8000

int 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);
  
  // 由于客戶端不需要固定的端口號(hào),因此不必調(diào)用 bind(),客戶端的端口號(hào)由內(nèi)核自動(dòng)分配。
  // 注意,客戶端不是不允許調(diào)用 bind(),只是沒有必要調(diào)用 bind() 固定一個(gè)端口號(hào),
  // 服務(wù)器也不是必須調(diào)用 bind(),但如果服務(wù)器不調(diào)用 bind(),內(nèi)核會(huì)自動(dòng)給服務(wù)器分配監(jiān)聽端口,
  // 每次啟動(dòng)服務(wù)器時(shí)端口號(hào)都不一樣,客戶端要連接服務(wù)器就會(huì)遇到麻煩。
  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 文件中,并執(zhí)行下面的命令編譯:

$ gcc client.c -o client

然后運(yùn)行編譯出來的 client 程序:

$ ./client hello

此時(shí)服務(wù)器端會(huì)收到請(qǐng)求并返回轉(zhuǎn)換為大寫的字符串,并輸出相應(yīng)的信息:

而客戶端在發(fā)送請(qǐng)求后會(huì)收到轉(zhuǎn)換過的字符串:

在客戶端的代碼中有兩點(diǎn)需要注意:

1. 由于客戶端不需要固定的端口號(hào),因此不必調(diào)用 bind(),客戶端的端口號(hào)由內(nèi)核自動(dòng)分配。
2. 客戶端需要調(diào)用 connect() 連接服務(wù)器,connect 和 bind 的參數(shù)形式一致,區(qū)別在于 bind 的參數(shù)是自己的地址,而 connect 的參數(shù)是對(duì)方的地址。

至此我們已經(jīng)使用 socket 技術(shù)完成了一個(gè)最簡單的客戶端服務(wù)器程序,雖然離實(shí)際應(yīng)用還非常遙遠(yuǎn),但就學(xué)習(xí)而言已經(jīng)足夠了。

提升服務(wù)器端的響應(yīng)能力

雖然我們的服務(wù)器程序可以響應(yīng)客戶端的請(qǐng)求,但是這樣的效率太低了。一般情況下服務(wù)器程序需要能夠同時(shí)處理多個(gè)客戶端的請(qǐng)求。可以通過 fork 系統(tǒng)調(diào)用創(chuàng)建子進(jìn)程來處理每個(gè)請(qǐng)求,下面是大體的實(shí)現(xiàn)思路:

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)
  {
    // 在子進(jìn)程中處理客戶端的請(qǐng)求。
    close(listenfd);
    while (1)
    {
      read(connfd, ...);
      ...
      write(connfd, ...);
    }
    close(connfd);
    exit(0);
  }
  else
  {
    close(connfd);
  }  
}

此時(shí)父進(jìn)程的任務(wù)就是不斷的創(chuàng)建子進(jìn)程,而由子進(jìn)程去響應(yīng)客戶端的具體請(qǐng)求。通過這種方式,可以極大的提升服務(wù)器端的響應(yīng)能力。

總結(jié)

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

以上就是本文的全部內(nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。

相關(guān)文章

  • 詳解CentOS 6.4 添加永久靜態(tài)路由所有方法匯總

    詳解CentOS 6.4 添加永久靜態(tài)路由所有方法匯總

    這篇文章主要介紹了詳解CentOS 6.4 添加永久靜態(tài)路由所有方法匯總,非常具有實(shí)用價(jià)值,需要的朋友可以參考下。
    2016-12-12
  • Linux Socket 編程簡介和實(shí)現(xiàn)

    Linux Socket 編程簡介和實(shí)現(xiàn)

    這篇文章主要介紹了Linux Socket 編程簡介和實(shí)現(xiàn),小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2018-02-02
  • linux中$符號(hào)的基礎(chǔ)用法總結(jié)

    linux中$符號(hào)的基礎(chǔ)用法總結(jié)

    這篇文章主要給大家介紹了關(guān)于linux中$符號(hào)的基礎(chǔ)用法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用linux系統(tǒng)具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-11-11
  • linux中sed命令的使用與注意小結(jié)

    linux中sed命令的使用與注意小結(jié)

    sed本身也是一個(gè)管道命令,可以分析standard input的,sed可以將數(shù)據(jù)進(jìn)行替換、刪除、新增、選取特定行等。下面這篇文章主要介紹了linux中sed命令的用法和注意事項(xiàng),需要的朋友可以參考借鑒,下面來一起看看吧。
    2017-02-02
  • linux多線程編程(五)

    linux多線程編程(五)

    使用多線程的理由之一是和進(jìn)程相比,它是一種非常“節(jié)儉”的多任務(wù)操作方式。我們知道,在Linux系統(tǒng)下,啟動(dòng)一個(gè)新的進(jìn)程必須分配給它獨(dú)立的地址空間,建立眾多的數(shù)據(jù)表來維護(hù)它的代碼段、堆棧段和數(shù)據(jù)段,這是一種“昂貴”的多任務(wù)工作方式。
    2014-08-08
  • Vim中查找替換及正則表達(dá)式的使用詳解

    Vim中查找替換及正則表達(dá)式的使用詳解

    Vim中的正則表達(dá)式功能很強(qiáng)大,如果能自由運(yùn)用,則可以完成很多難以想象的操作。下面這篇文章主要給大家介紹了關(guān)于Vim中查找替換及正則表達(dá)式使用的相關(guān)資料,文中介紹的非常詳細(xì),需要的朋友可以參考借鑒,下面來一起看看吧。
    2017-07-07
  • CentOS7如何執(zhí)行PHP定時(shí)任務(wù)詳解

    CentOS7如何執(zhí)行PHP定時(shí)任務(wù)詳解

    這篇文章主要給大家介紹了關(guān)于在CentOS7中如何執(zhí)行PHP定時(shí)任務(wù)的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2018-11-11
  • 歷史Linux鏡像處理及修復(fù)方案

    歷史Linux鏡像處理及修復(fù)方案

    在本文中我們給大家整理了關(guān)于如何對(duì)于歷史Linux鏡像的問題進(jìn)行修復(fù)處理的相關(guān)內(nèi)容,有需要的朋友們參考下。
    2018-09-09
  • 詳解CentOS7防火墻管理firewalld

    詳解CentOS7防火墻管理firewalld

    本篇文章主要介紹了CentOS7防火墻管理firewalld,centos 7中防火墻是一個(gè)非常的強(qiáng)大的功能了,有興趣的可以了解一下。
    2016-12-12
  • Centos7的apache網(wǎng)站環(huán)境搭建wordpress

    Centos7的apache網(wǎng)站環(huán)境搭建wordpress

    本篇文章給大家詳細(xì)分析了在Centos7的apache網(wǎng)站環(huán)境搭建wordpress的詳細(xì)操作方法,有興趣的朋友參考下。
    2018-02-02

最新評(píng)論