TCP網路程式設計API----socket
- 網路層的IP地址可以唯一標識網路中的主機,而傳輸層的“協議+埠”可以唯一標識主機中的應用程式(程序)。這樣可以利用三元組(
IP地址,協議,埠
)就可以標識網路的程序了,網路中的程序通訊就可以利用這個標誌(socket
)與其它程序進行互動- 網路中的程序通過
socket
進行通訊,那麼什麼是socket
呢?socket
其實就是一種特殊的檔案,一些socket函式
即是對其進行的操作(讀/寫、開啟、關閉)- 就目前而言,幾乎所有的應用程式都是採用socket
下面就來簡單瞭解一下在Tcp協議通訊的socket
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介面函式
- 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()時隨機分配一個埠
- 每個程序在自己的程序空間裡都有一個套接字描述符表,但是套接字資料結構都存放在作業系統的核心緩衝裡
- 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:對應的地址長度
- 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伺服器的連線
- accept函式
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
accept函式的第一個引數為伺服器的socket描述字,第二個引數為指向struct sockaddr*的指標,用於返回客戶端的協議地址;第三個引數為協議地址的長度。如果accept成功,那麼其返回值是由核心自動生成的一個全新的描述字,代表與返回客戶的Tcp連線
- 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表示緩衝區長度
- close函式
#include <unistd.h> //標頭檔案
int close(int fd);
close一個TCP socket的預設行為時,會把該socket標記為已關閉,然後立即返回到呼叫程序。該描述字不能再由呼叫程序使用,也就是說不能再作為read或write的第一個引數
close操作只是使相應的socket描述字的引用計數-1,只有當引用計數為0時,才會觸發TCP的客戶端向伺服器傳送終止連線請求
--------------------------------------------get------------------------------------------------------
瞭解TCP互動流程
簡單編寫一個TCP server