1. 程式人生 > >TCP網路程式設計API----socket

TCP網路程式設計API----socket

  • 網路層的IP地址可以唯一標識網路中的主機,而傳輸層的“協議+埠”可以唯一標識主機中的應用程式(程序)。這樣可以利用三元組(IP地址,協議,埠)就可以標識網路的程序了,網路中的程序通訊就可以利用這個標誌(socket)與其它程序進行互動
  • 網路中的程序通過socket進行通訊,那麼什麼是socket呢?socket其實就是一種特殊的檔案,一些socket函式即是對其進行的操作(讀/寫、開啟、關閉)
  • 就目前而言,幾乎所有的應用程式都是採用socket

下面就來簡單瞭解一下在Tcp協議通訊的socket

TCP互動流程

TCP互動流程

上述展示的互動流程,具體如下:

  • 1.伺服器根據地址型別(ipv4,ipv6)、socket型別、協議建立socket
  • 2.伺服器為socket繫結IP地址和埠號
  • 3.伺服器socket監聽埠號請求,隨時準備接收客戶端發來的請求,這時候伺服器socket並沒有開啟
  • 4.客戶端建立socket
  • 5.客戶端開啟socket,根據伺服器IP地址和埠號試圖連線伺服器socket
  • 6.伺服器socket接收到客戶端socket請求,被動開啟,開始接受客戶端請求,直到客戶端返回連線資訊。這時候socket進入阻塞狀態,所謂阻塞即accept()方法一直到客戶端返回連線資訊後才返回,開始接收下一個客戶端請求
  • 7.客戶端連線成功,向伺服器傳送連線狀態資訊
  • 8.伺服器accept方法返回,連線成功
  • 9.客戶端向socket寫入資訊
  • 10.伺服器讀取資訊
  • 11.客戶端關閉
  • 12.伺服器端關閉

可以看出,伺服器端socket與客戶端socket建立連線的部分其實就是TCP的三次握手

socket介面函式

  1. socket函式
int socket(int domain, int type, int protocol);
  • socket函式對應於普通檔案的開啟操作。普通檔案的開啟操作返回一個檔案描述字,而socket()用於建立一個socket描述符,它唯一標識一個socket
  • 建立socket時,指定不同的引數建立不同的socket描述符,socket函式的3個引數:
  • 1.domain:即協議域,又稱為協議族。常用的協議族有:AF_INET、AF_INET6、AF_LOCAL(或稱AF_UNIX,unix域socket)、AF_ROUTE等。協議族決定了socket的地址型別
  • 2.type:指定socket的型別。常用的socket型別有:SOCK_STREAM(Tcp協議)、SOCK_DGRAM、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET等
  • 3.protocol:指定協議。常用的協議有:IPPROTO_TCP、IPPTOTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等,分別對應Tcp傳輸協議、UDP傳輸協議、STCP傳輸協議、TIPC傳輸協議

protocol為0時,會自動選擇type型別對應的預設協議

  • 當呼叫一個socket函式用來建立一個socket時,返回的socket描述字它存在於協議族中,但沒有一個確定的地址。可以利用bind()函式賦予給它一個地址,否則系統將在呼叫connect()、listen()時隨機分配一個埠
  • 每個程序在自己的程序空間裡都有一個套接字描述符表,但是套接字資料結構都存放在作業系統的核心緩衝裡
  1. bind函式
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
  • (1).sockfd:即socket描述字,它是通過socket()函式建立來唯一標識一個socket的。bind()函式是給這個描述字繫結一個名字
  • (2).addr:一個const struct sockaddr*指標,指向要繫結給sockfd的協議地址,有以下幾種:

ipv4對應的如下程式碼:

struct sockaddr_in {
	sa_family_t  sin_family;  //address familry: AF_INET
	in_port_t  sin_port;     //port in network byte order 
	struct  in_addr  sin_addr; //internet address
};
struct  in_addr {
	uint32_t  s_addr;  //address in network byte order
};

ipv6對應的程式碼:

struct sockaddr_in {
	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];      //Ipv6address
};

UNIX域對應的程式碼:

#define UNIX_PATH_MAX  108
struct sockaddr_un  {
	sa_family_t  sun_family;      //AF_UNIX
	char  sun_path[UNIX_PATH_MAX];     //pathname
};
  • (3).addrlen:對應的地址長度
  1. listen和connect函式
int listen(int sockfd, int backlog);
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
  • listen函式的第一個引數即為要監聽的socket描述字,第二個引數為相應的socket可以排隊的最大連線個數。socket函式建立的socket預設是一個主動型別的,listen函式將其變為被動的,等待客戶的連線請求
  • connect函式的第一個引數為客戶端的socket描述字,第二引數為伺服器的socket地址,第三個引數為socket地址的長度。客戶端通過呼叫connect函式來建立與TCP伺服器的連線
  1. accept函式
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

accept函式的第一個引數為伺服器的socket描述字,第二個引數為指向struct sockaddr*的指標,用於返回客戶端的協議地址;第三個引數為協議地址的長度。如果accept成功,那麼其返回值是由核心自動生成的一個全新的描述字,代表與返回客戶的Tcp連線

  1. read和write函式

網路I/O操作有下面幾組:

read()/write()
recv()/writev()
readv()/writev()
recvmsg()/sendmsg()
recvfrom()/sendto()

最常用的則是write()和read(),read原型如下:

ssize_t read(int fd, void *buf, size_t count);

read()函式負責從fd中讀取內容。當讀取成功時,read()返回實際所讀的位元組數,如果返回的值是0表示已經讀到檔案的結束了,小於0表示出現了錯誤。三個引數分別表示(1)socket描述字fd (2)緩衝區buf (3)緩衝區長度count

write()原型:

ssize_t write(int fd, const void *buf, size_t count);

write函式將buf中的nbytes寫入檔案描述字fd成功時返回寫的位元組數。失敗時返回-1,並設定errno變數,當我們向套接字檔案描述符寫時有兩種可能:(1)write的返回值大於0,表示寫了部分或者是全部的資料 (2)返回的值小於0,此時出現了錯誤。三個引數分別表示:(1)fd表示socket描述字 (2)buf表示緩衝區 (3)count表示緩衝區長度

  1. close函式
#include <unistd.h>  //標頭檔案
int close(int fd);  

close一個TCP socket的預設行為時,會把該socket標記為已關閉,然後立即返回到呼叫程序。該描述字不能再由呼叫程序使用,也就是說不能再作為read或write的第一個引數

close操作只是使相應的socket描述字的引用計數-1,只有當引用計數為0時,才會觸發TCP的客戶端向伺服器傳送終止連線請求

--------------------------------------------get------------------------------------------------------

瞭解TCP互動流程
簡單編寫一個TCP server