Linux-socket使用

socket

產生的緣由

進程通訊的概念最初來源於單機系統。因爲每一個進程都在本身的地址範圍內運行,爲保證兩個相互通訊的進程之間既互不干擾又協調一致工做,操做系統爲進程通訊提供了相應設施,如html

  • UNIX BSD有:管道(pipe)、命名管道(named pipe)軟中斷信號(signal)
  • UNIX system V有:消息(message)、共享存儲區(shared memory)和信號量(semaphore)等.

他們都僅限於用在本機進程之間通訊。網間進程通訊要解決的是不一樣主機進程間的相互通訊問題(可把同機進程通訊看做是其中的特例)。爲此,首先要解決的是網間進程標識問題。同一主機上,不一樣進程可用進程號(process ID)惟一標識。但在網絡環境下,各主機獨立分配的進程號不能惟一標識該進程。例如,主機A賦於某進程號5,在B機中也能夠存在5號進程,所以,「5號進程」這句話就沒有意義了。 其次,操做系統支持的網絡協議衆多,不一樣協議的工做方式不一樣,地址格式也不一樣。所以,網間進程通訊還要解決多重協議的識別問題。 編程

其實TCP/IP協議族已經幫咱們解決了這個問題,網絡層的「ip地址」能夠惟一標識網絡中的主機,而傳輸層的「協議+端口」能夠惟一標識主機中的應用程序(進程)。這樣利用三元組(ip地址,協議,端口)就能夠標識網絡的進程了,網絡中的進程通訊就能夠利用這個標誌與其它進程進行交互。設計模式

使用TCP/IP協議的應用程序一般採用應用編程接口:UNIX BSD的套接字(socket)和UNIX System V的TLI(已經被淘汰),來實現網絡進程之間的通訊。就目前而言,幾乎全部的應用程序都是採用socket,而如今又是網絡時代,網絡中進程通訊是無處不在,這就是我爲何說「一切皆socket」。服務器

定義

  • 網絡上的兩個程序經過一個雙向的通訊鏈接實現數據的交換,這個鏈接的一端稱爲一個socket。
  • socket起源於Unix,而Unix/Linux基本哲學之一就是「一切皆文件」,均可以用「打開open –> 讀寫write/read –> 關閉close」模式來操做。個人理解就是Socket就是該模式的一個實現,socket便是一種特殊的文件,一些socket函數就是對其進行的操做(讀/寫IO、打開、關閉)
  • socket的英文原義是「孔」或「插座」。做爲BSD UNIX的進程通訊機制,取後一種意思。一般也稱做"套接字",用於描述IP地址和端口,是一個通訊鏈的句柄,能夠用來實現不一樣虛擬機或不一樣計算機之間的通訊。在Internet上的主機通常運行了多個服務軟件,同時提供幾種服務。每種服務都打開一個Socket,並綁定到一個端口上,不一樣的端口對應於不一樣的服務。Socket正如其英文原義那樣,像一個多孔插座。一臺主機猶如佈滿各類插座的房間,每一個插座有一個編號,有的插座提供220伏交流電, 有的提供110伏交流電,有的則提供有線電視節目。 客戶軟件將插頭插到不一樣編號的插座,就能夠獲得不一樣的服務。
  • Socket是應用層與TCP/IP協議族通訊的中間軟件抽象層,它是一組接口。在設計模式中,Socket其實就是一個門面模式,它把複雜的TCP/IP協議族隱藏在Socket接口後面,對用戶來講,一組簡單的接口就是所有,讓Socket去組織數據,以符合指定的協議,而不須要讓用戶本身去定義何時須要指定哪一個協議哪一個函數。

分類

流式socket(SOCK_STREAM )

流式套接字提供可靠的、面向鏈接的通訊流;它使用TCP 協議,從而保證了數據傳輸的正確性和順序性。網絡

數據報socket(SOCK_DGRAM )

數據報套接字定義了一種無鏈接的服 ,數據經過相互獨立的報文進行傳輸,是無序的,而且不保證是可靠、無差錯的。它使用數據報協議UDP。數據結構

原始socket(SOCK_RAW)

原始套接字容許對底層協議如IP或ICMP進行直接訪問,功能強大但使用較爲不便,主要用於一些協議的開發。app

鏈接過程

根據鏈接啓動的方式以及本地套接字要鏈接的目標,套接字之間的鏈接過程能夠分爲三個步驟:服務器監聽,客戶端請求,鏈接確認。
  • 服務器監聽:是服務器端套接字並不定位具體的客戶端套接字,而是處於等待鏈接的狀態,實時監控網絡狀態。
  • 客戶端請求:是指由客戶端的套接字提出鏈接請求,要鏈接的目標是服務器端的套接字。爲此,客戶端的套接字必須首先描述它要鏈接的服務器的套接字,指出服務器端套接字的地址和端口號,而後就向服務器端套接字提出鏈接請求。
  • 鏈接確認:是指當服務器端套接字監聽到或者說接收到客戶端套接字的鏈接請求,它就響應客戶端套接字的請求,創建一個新的線程,把服務器端套接字的描述發給客戶端,一旦客戶端確認了此描述,鏈接就創建好了。而服務器端套接字繼續處於監聽狀態,繼續接收其餘客戶端套接字的鏈接請求。

Linux socket 函數

建立socket

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

參數

  • domain:協議域,又稱協議族(family)。經常使用的協議族有AF_INETAF_INET6AF_LOCAL(或稱AF_UNIX,Unix域Socket)、AF_ROUTE等。協議族決定了socket的地址類型,在通訊中必須採用對應的地址,如AF_INET決定了要用ipv4地址(32位的)與端口號(16位的)的組合、AF_UNIX決定了要用一個絕對路徑名做爲地址。
  • type:指定Socket類型。經常使用的socket類型有SOCK_STREAMSOCK_DGRAMSOCK_RAWSOCK_PACKETSOCK_SEQPACKET等。流式Socket(SOCK_STREAM)是一種面向鏈接的Socket,針對於面向鏈接的TCP服務應用。數據報式Socket(SOCK_DGRAM)是一種無鏈接的Socket,對應於無鏈接的UDP服務應用。
  • protocol:指定協議。經常使用協議有IPPROTO_TCPIPPROTO_UDPIPPROTO_STCPIPPROTO_TIPC等,分別對應TCP傳輸協議、UDP傳輸協議、STCP傳輸協議、TIPC傳輸協議。
注意:type和protocol不能夠隨意組合,如SOCK_STREAM不能夠跟IPPROTO_UDP組合。當第三個參數爲0時,會自動選擇第二個參數類型對應的默認協議。

返回值

  • 若是調用成功就返回新建立的套接字的描述符,若是失敗就返回INVALID_SOCKET(Linux下失敗返回-1)。套接字描述符是一個整數類型的值。每一個進程的進程空間裏都有一個套接字描述符表,該表中存放着套接字描述符和套接字數據結構的對應關係。該表中有一個字段存放新建立的套接字的描述符,另外一個字段存放套接字數據結構的地址,所以根據套接字描述符就能夠找到其對應的套接字數據結構。每一個進程在本身的進程空間裏都有一個套接字描述符表可是套接字數據結構都是在操做系統的內核緩衝裏。

綁定

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

參數

  • sockfd:即socket描述字,它是經過socket()函數建立了,惟一標識一個socket。bind()函數就是將給這個描述字綁定一個名字。
  • address:是一個sockaddr結構指針,該結構中包含了要結合的地址和端口號。dom

    //ipv4對應的是: 
    struct sockaddr_in {
    sa_family_t    sin_family; /* address family: AF_INET */
    in_port_t      sin_port;   /* port in network byte order */
    struct in_addr sin_addr;   /* internet address */
    };
    
    /* Internet address. */
    struct in_addr {
        uint32_t       s_addr;     /* address in network byte order */
    };
    //ipv6對應的是: 
    struct sockaddr_in6 { 
        sa_family_t     sin6_family;   /* AF_INET6 */ 
        in_port_t       sin6_port;     /* port number */ 
        uint32_t        sin6_flowinfo; /* IPv6 flow information */ 
        struct in6_addr sin6_addr;     /* IPv6 address */ 
        uint32_t        sin6_scope_id; /* Scope ID (new in 2.4) */ 
    };
    
    struct in6_addr { 
        unsigned char   s6_addr[16];   /* IPv6 address */ 
    };
    //Unix域對應的是: 
    #define UNIX_PATH_MAX    108
    
    struct sockaddr_un { 
        sa_family_t sun_family;               /* AF_UNIX */ 
        char        sun_path[UNIX_PATH_MAX];  /* pathname */ 
    };
  • address_len:肯定address緩衝區的長度。

返回值

  • 若是函數執行成功,返回值爲0,不然爲SOCKET_ERROR
一般服務器在啓動的時候都會綁定一個衆所周知的地址(如ip地址+端口號),用於提供服務,客戶就能夠經過它來接連服務器;而客戶端就不用指定,有系統自動分配一個端口號和自身的ip地址組合。這就是爲何一般服務器端在listen以前會調用bind(),而客戶端就不會調用,而是在connect()時由系統隨機生成一個。

監聽(listen) - listen for connections on a socket

int listen(int sockfd, int backlog);

參數

  • sockfd: socket描述字
  • backlog: The backlog argument defines the maximum length to which the queue of pending connections for sockfd may grow.
listen函數的第一個參數即爲要監聽的socket描述字,第二個參數爲相應socket能夠排隊的最大鏈接個數。socket()函數建立的socket默認是一個主動類型的,listen函數將socket變爲被動類型的,等待客戶的鏈接請求。

返回值

  • On success, zero is returned. On error, -1 is returned, and errno is set appropriately.

鏈接 (connect) - initiate a connection on a socket

int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);

參數

  • sockfd: socket描述字
  • addr: addr is the address to which datagrams are sent by default, and the only address from which datagrams are received.

返回值

  • If the connection or binding succeeds, zero is returned. On error, -1 is returned, and errno is set appropriately.
connect函數的第一個參數即爲客戶端的socket描述字,第二參數爲服務器的socket地址,第三個參數爲socket地址的長度。客戶端經過調用connect函數來創建與TCP服務器的鏈接。成功返回0,若鏈接失敗則返回-1。

接收(accept) - accept a connection on a socket

TCP服務器端依次調用socket()、bind()、listen()以後,就會監聽指定的socket地址了。TCP客戶端依次調用socket()、connect()以後就向TCP服務器發送了一個鏈接請求。TCP服務器監聽到這個請求以後,就會調用accept()函數取接收請求,這樣鏈接就創建好了。以後就能夠開始網絡I/O操做了,即類同於普通文件的讀寫I/O操做。
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

參數

  • sockfd:用來監聽一個端口,當有一個客戶與服務器鏈接時,它使用這個一個端口號,而此時這個端口號正與這個套接字關聯。固然客戶不知道套接字這些細節,它只知道一個地址和一個端口號。
  • addr:這是一個結果參數,它用來接受一個返回值,這返回值指定客戶端的地址,固然這個地址是經過某個地址結構來描述的,用戶應該知道這一個什麼樣的地址結構。若是對客戶的地址不感興趣,那麼能夠把這個值設置爲NULL。
  • addrlen:結果的參數,用來接受上述addr的結構的大小的,它指明addr結構所佔有的字節個數。一樣的,它也能夠被設置爲NULL。

返回值

  • 若是accept成功返回,則服務器與客戶已經正確創建鏈接了,此時服務器經過accept返回的套接字來完成與客戶的通訊。
accept默認會阻塞進程,直到有一個客戶鏈接創建後返回,它返回的是一個新可用的套接字,這個套接字是鏈接套接字。此時咱們須要區分兩種套接字,
- 監聽套接字: 監聽套接字正如accept的參數sockfd,它是監聽套接字,在調用listen函數以後,是服務器開始調用socket()函數生成的,稱爲監聽socket描述字(監聽套接字)
- 鏈接套接字:一個套接字會從主動鏈接的套接字變身爲一個監聽套接字;而accept函數返回的是已鏈接socket描述字(一個鏈接套接字),它表明着一個網絡已經存在的點點鏈接。  
一個服務器一般一般僅僅只建立一個監聽socket描述字,它在該服務器的生命週期內一直存在。內核爲每一個由服務器進程接受的客戶鏈接建立了一個已鏈接socket描述字,當服務器完成了對某個客戶的服務,相應的已鏈接socket描述字就被關閉。
鏈接套接字socketfd_new 並無佔用新的端口與客戶端通訊,依然使用的是與監聽套接字socketfd同樣的端口號

發送數據(send) - send a message on a socket

ssize_t send(int sockfd, const void *buf, size_t len, int flags);

參數

  • sockfd: 傳輸數據的socket描述符
  • buf : 一個指向要發送數據的指針
  • len: 以字節爲單位的數據的長度
  • flags: 0

返回值

  • On success, these calls return the number of bytes sent. On error, -1 is returned, and errno is set appropriately.

接受數據 - receive a message from a socket

ssize_t recv(int sockfd, void *buf, size_t len, int flags);

參數

  • sockfd: 傳輸數據的socket描述符
  • buf : 一個指向要發送數據的指針
  • len: 以字節爲單位的數據的長度
  • flags: 0

返回值

  • On success, these calls return the number of bytes sent. On error, -1 is returned, and errno is set appropriately.

關閉(close)

當全部的數據操做結束之後,你能夠調用close()函數來釋放該socket,從而中止在該socket上的任何數據操做:

參數

  • sockfd: 傳輸數據的socket描述符

返回值

  • returns zero on success. On error, -1 is returned, and errno is set appropriately.

shutdown

int shutdown(int sockfd, int how);

參數

  • sockfd:
  • how:socket

    • 0 不容許繼續接收數據
    • 1 不容許繼續發送數據
    • 2 不容許繼續發送和接收數據

返回值

  • On success, zero is returned. On error, -1 is returned, and errno is set appropriately.

Example

tcp

server

//
// Created by 張榮響 on 2018/2/1.
//

/*server.c*/

#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <netinet/in.h>

#define PORT            4321
#define BUFFER_SIZE        1024
#define MAX_QUE_CONN_NM    5

int main() {
  struct sockaddr_in server_sockaddr, client_sockaddr;
  int sin_size, recvbytes;
  int sockfd, client_fd;
  char buf[BUFFER_SIZE];

  /*創建socket鏈接*/
  if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
    perror("socket");
    exit(1);
  }
  printf("Socket id = %d\n", sockfd);

  /*設置sockaddr_in 結構體中相關參數*/
  server_sockaddr.sin_family = AF_INET;
  server_sockaddr.sin_port = htons(PORT);
  server_sockaddr.sin_addr.s_addr = INADDR_ANY;
  bzero(&(server_sockaddr.sin_zero), 8);

  int i = 1;/* 使得重複使用本地地址與套接字進行綁定 */
  setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i));

  /*綁定函數bind*/
  if (bind(sockfd, (struct sockaddr *) &server_sockaddr, sizeof(struct sockaddr)) == -1) {
    perror("bind");
    exit(1);
  }
  printf("Bind success!\n");

  /*調用listen函數*/
  if (listen(sockfd, MAX_QUE_CONN_NM) == -1) {
    perror("listen");
    exit(1);
  }
  printf("Listening....\n");

  /*調用accept函數,等待客戶端的鏈接*/
  if ((client_fd = accept(sockfd, (struct sockaddr *) &client_sockaddr, &sin_size)) == -1) {
    perror("accept");
    exit(1);
  }

  /*調用recv函數接收客戶端的請求*/
  memset(buf, 0, sizeof(buf));
  if ((recvbytes = recv(client_fd, buf, BUFFER_SIZE, 0)) == -1) {
    perror("recv");
    exit(1);
  }
  printf("Received a message: %s,length is %d\n", buf,recvbytes);
  close(sockfd);
  exit(0);
}

client

//
// Created by 張榮響 on 2018/2/1.
//

/*client.c*/

#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <netdb.h>
#include <netinet/in.h>

#define PORT    4321
#define BUFFER_SIZE 1024

int main(int argc, char *argv[]) {
  int sockfd, sendbytes;
  char buf[BUFFER_SIZE];
  struct hostent *host;
  struct sockaddr_in serv_addr;

  if (argc < 3) {
    fprintf(stderr, "USAGE: ./client Hostname(or ip address) Text\n");
    exit(1);
  }

  /*地址解析函數*/
  if ((host = gethostbyname(argv[1])) == NULL) {
    perror("gethostbyname");
    exit(1);
  }

  memset(buf, 0, sizeof(buf));
  sprintf(buf, "%s", argv[2]);

  /*建立socket*/
  if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
    perror("socket");
    exit(1);
  }

  /*設置sockaddr_in 結構體中相關參數*/
  serv_addr.sin_family = AF_INET;
  serv_addr.sin_port = htons(PORT);
  serv_addr.sin_addr = *((struct in_addr *) host->h_addr);
  bzero(&(serv_addr.sin_zero), 8);

  /*調用connect函數主動發起對服務器端的鏈接*/
  if (connect(sockfd, (struct sockaddr *) &serv_addr, sizeof(struct sockaddr)) == -1) {
    perror("connect");
    exit(1);
  }

  /*發送消息給服務器端*/
  if ((sendbytes = send(sockfd, buf, strlen(buf), 0)) == -1) {
    perror("send");
    exit(1);
  }
  close(sockfd);
  exit(0);
}

udp

server

//
// Created by 張榮響 on 2018/2/1.
//

#include <stdio.h>
#include <stdlib.h>

#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <zconf.h>

int port = 6789;

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

  int sin_len;
  char message[256];

  int socket_descriptor;
  struct sockaddr_in sin;
  printf("Waiting for data form sender \n");

  bzero(&sin, sizeof(sin));
  sin.sin_family = AF_INET;
  sin.sin_addr.s_addr = htonl(INADDR_ANY);
  sin.sin_port = htons(port);
  sin_len = sizeof(sin);

  socket_descriptor = socket(AF_INET, SOCK_DGRAM, 0);
  bind(socket_descriptor, (struct sockaddr *) &sin, sizeof(sin));

  while (1) {
    recvfrom(socket_descriptor, message, sizeof(message), 0, (struct sockaddr *) &sin, &sin_len);
    printf("Response from server:%s\n", message);
    if (strncmp(message, "stop", 4) == 0)//接受到的消息爲 「stop」
    {

      printf("Sender has told me to end the connection\n");
      break;
    }
  }

  close(socket_descriptor);
  return (EXIT_SUCCESS);
}

client

//
// Created by 張榮響 on 2018/2/1.
//


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <zconf.h>

int port = 6789;
int main(int argc, char **argv) {
  int socket_descriptor; //套接口描述字
  int iter = 0;
  char buf[80];
  struct sockaddr_in address;//處理網絡通訊的地址

  bzero(&address, sizeof(address));
  address.sin_family = AF_INET;
  address.sin_addr.s_addr = inet_addr("127.0.0.1");//這裏不同
  address.sin_port = htons(port);

  //建立一個 UDP socket

  socket_descriptor = socket(AF_INET, SOCK_DGRAM, 0);//IPV4  SOCK_DGRAM 數據報套接字(UDP協議)

  for (iter = 0; iter <= 20; iter++) {
    /*
    * sprintf(s, "%8d%8d", 123, 4567); //產生:" 123 4567"
    * 將格式化後到 字符串存放到s當中
    */
    sprintf(buf, "data packet with ID %d\n", iter);

    /*int PASCAL FAR sendto( SOCKET s, const char FAR* buf, int len, int flags,const struct sockaddr FAR* to, int tolen);  
     * s:一個標識套接口的描述字。 
     * buf:包含待發送數據的緩衝區。  
     * len:buf緩衝區中數據的長度。 
     * flags:調用方式標誌位。  
     * to:(可選)指針,指向目的套接口的地址。 
     * tolen:to所指地址的長度。
        */
    sendto(socket_descriptor, buf, sizeof(buf), 0, (struct sockaddr *) &address, sizeof(address));
  }

  sprintf(buf, "stop\n");
  sendto(socket_descriptor, buf, sizeof(buf), 0, (struct sockaddr *) &address, sizeof(address));//發送stop 命令
  close(socket_descriptor);
  printf("Messages Sent,terminating\n");

  return (EXIT_SUCCESS);
}

文章參考

轉載註明出處

相關文章
相關標籤/搜索