淺談 Linux C語言 socket 網絡編程

Linux C語言socket網絡編程


需要Linux C 服務器開發視頻學習資料的朋友請後臺私信【架構】獲取

淺談 Linux C語言 socket 網絡編程

注意:本文是按照 TCP、UDP的工作過程進行總結的

  1. TCP套 socket 接口編程: 基於TCP的 客戶/服務器(C/S)模式的工作過程如下:
淺談 Linux C語言 socket 網絡編程

服務器進程中的一些函數:


  1. socket():

/* 函數所需頭文件及其原型 */

#include

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

socketfd = soket(AF_INET, SOCK_STREAM, 0);

/* socketfd 作為返回值,可以記作描述符。

若 socketfd 非負則表示成功,為負則表示失敗。

參數:

family -> 指明協議族

type -> 字節流類型

protocol -> 一般置0.

參數 family 的取值範圍是:

AF_LOCAL UNIX 協議族

AF_ROUTE 路由器接口

AF_INET IPv4 協議

AF_INET6 IPv6 協議 AF_KEY 密鑰套接口

參數 type 的取值範圍:

SOCK_STREAM TCP 套接口

SOCK_DGRAM UDP 套接口

SOCK_PACKET 支持數據鏈路訪問

SOCK_RAM 原始套接口

*/

生成套接口描述字(套接字)後,要為套接口的地址數據結構進行賦初值。

通用套接口地址的數據結構中,struct sockaddr_in 需要掌握:

struct in_addr {

in_addr_t s_addr;

/*32 位 IP 地址,網絡字節序*/

};

struct sockaddr_in {

uint8 sin_len;

sa_family_t sin_family;

in_port_t sin_port;

/*16 位端口號,網絡字節序*/

struct in_addr sin_addr;

char sin_zero[8];

/*備用的域,未使用*/

};

PS:需要注意的是,一般在 socket() 之後,我們會填寫 sockaddr 的相關內容。

/* Fill the local socket address struct */

memset (&servaddr,0,sizeof(servaddr));

servaddr.sin_family = AF_INET; // Protocol Family

servaddr.sin_port = htons (PORT); // Port number

servaddr.sin_addr.s_addr = htonl (INADDR_ANY); // AutoFill local address

  1. bind():
<code> // 函數原型:
#include
int bind(int sockfd,const struct sockaddr *myaddr,socklen_t addrlen);
/*
參數 sockfd :套接字描述符。
參數 my_addr:指向 sockaddr 結構體的指針(該結構體中保存有端口和 IP 地址 信息)。
參數 addlen:結構體 sockaddr 的長度。

返回:0──成功, -1──失敗
*/

ret = bind(sockfd,(struct sockaddr *)&my_addr,sizeof(struct sockaddr));

/* 功能:當調用 socket 函數創建套接字後,該套接字並沒有與 本機地址和端口等 信息相連,
bind 函數將完成這些工作。
*/
/<code>
  1. listen():
<code> //  函數原型:
#include
#include
// #define BACKLOG 10
int listen(int sockfd,int backlog);

/*
參數 sockfd :套接字描述符。
參數 backlog :規定內核為此套接口排隊的最大選擇個數。
*/
ret = listen(sockfd,BACKLOG);

// 通常採用一下的異常處理:
if(listen(listenfd,BACKLOG) == -1){
printf("ERROR: Failed to listen Port %d.\\n", PORT);
return (0);
}
else{
printf("OK: Listening the Port %d sucessfully.\\n", PORT);
}
/<code>

處在監聽模式下後,程序就需要一個循環來實現掛起等待客戶機請求。所以接下來的一步就是 接受客戶機的請求。

  1. accept():

先來了解一下 accept() 這個函數:

<code>//   函數原型:
#include
#include
int accept(int sockfd,struct sockaddr *cliaddr,socklen_t *addrlen);

/*
sockfd 參數:監聽的 套接字描述符。
cliaddr 參數:指向結構體 sockaddr 的指針。
addrlen 參數:cliaddr 參數指向的內存空間的長度。
*/

sin_size = sizeof(struct sockaddr_in);
connect_fd = accept(sockfd,( struct sockaddr *)&their_addr,&sin_size);

/<code>

accept() 函數用於面向連接類型的套接字類型。 accept() 函數將從連接請求隊列中獲得連接信息,創建新的套 接字,並返回該套接字的文件描述符。 新創建的套接字用於服務器與客戶機的通信,而原來的套接字仍然處於監聽狀態。 它們的區別在於:監聽套接口描述字 只有一個,而且一直存在, 每一個連接都有一個已連接套接口描述字,當連接斷開 時就關閉該描述字。

注意:bind 函數和 accept 函數的第三個參數是不一樣的。

  1. close():
<code>//   函數原型:
#include <unistd.h>
int close(int sockfd);
// 成功則返回 0,否則返回-1。
// 功能:關閉套接口 其中參數 sockfd 是關閉的套接口描述字。
// 當對一個套接口調用 close()時, 關閉該套接口描述字,並停止連接。

以後這個套接口不能再使用,也不能再執行 任何讀寫操作,但關閉時已經排隊準備發送的數據仍會被髮出 使用完一個套接口後,一定要記得將它關掉,任何一個文件讀寫操作完畢之後, 都要關閉它的描述字。

/<unistd.h>/<code>

客戶機進程中的一些函數:


  1. socket(): 這個函數前面提過,這裡不必多說。 創建套接字後,同理,也需要對套接口進行設置: (這是在客戶端填充的服務器 端的資料)...... bzero(&server_addr,sizeof(server_addr)); // 初始化,置 0 server_addr.sin_family=AF_INET; // IPV4 server_addr.sin_port=htons(portnumber); // (將本機器上的 short 數據轉化為網絡上的 short 數據)端口號,與服務器端 的端口號相同 server_addr.sin_addr=*((struct in_addr *)host->h_addr_list); // IP 地址
  2. connect():
<code> connect(sockfd,(struct sockaddr *)(&server_addr), sizeof(structsockaddr)); 
函數原型:
#include
#include
int connect(int sockfd,const struct sockaddr *serv_addr,int addrlen);

/*
返回值:成功:返回 0 錯誤:返回-1,並將全局變量 errno 設置為相應的錯誤號。
參數 sockfd :數據發送的套接字,解決從哪裡發送的問題,ockfd 是先前 socket 返回的值
參數 serv_addr:據發送的目的地,也就是服務器端的地址
參數 addrlen:指定 server_addr 結構體的長度
*/

函數功能:
創建了一個套接口之後,使客戶端和服務器連接。其實就是完成一個 有連接協議 的連接過程,
對於 TCP 來說就是那個三段握手過程。
/<code>

關於三段握手:( 《計算機網絡》謝希仁編著 第七版中 將其定名為:" 三報文握手 "):

​ 客戶端先用 connect() 向服務器發出一個要求連接的信號 SYN1;

​ 服務器 進程接收到這個信號後,發回應答信號 ack1,同時這也是一個要求回答的信號 SYN2;

​ 客戶端收到信號 ack1SYN2 後,再次應答 ack2; 服務器收到應答信號 ack2,一次連接才算建立完成。

​ 從上面過程可以看出,服務器會收到兩次信 號 SYN1ack2,因此服務器進程需要兩個隊列保存不同狀態的連接。剛接收 到 SYN1 信號時,連接還未完成,這時的連接放在一個名為“未完成連接”的隊列中。接收到 ack2

信號後,三段握手完成,這時的連接放在名為“已完成連接” 的隊列中,等待 accept() 調用。

關於 recv() 、send() 和 recvfrom() 、sendto() :


  1. 先說前兩個:

recv()send() 都是基於 TCP 協議。

不論是客戶還是服務器應用程序都用send函數來向TCP連接的另一端發送數據。

客戶程序一般用send函數向服務器發送請求,而服務器則通常用send函數來向客戶程序發送應答。

同樣,不論是客戶還是服務器應用程序都用recv函數從TCP連接的另一端接收數據。

// 函數原型:

int send( SOCKET s, const char *buf, int len, int flags );

int recv( SOCKET s, char *buf, int len, int flags );

(1)recv 先等待 s 的發送緩衝中的數據被協議傳送完畢,如果協議在傳送 s 的發送緩衝中的數據時出現網絡錯誤,那麼recv函數返回 SOCKET_ERROR

(2)如果 s 的發送緩衝中沒有數據或者數據被協議成功發送完畢後,recv 先檢查套接字 s 的接收緩衝區,如果 s 接收緩衝區中沒有數據或者協議正在接收數據,那麼 recv 就一直等待,直到協議把數據接收完畢。 ​

當協議把數據接收完畢,recv 函數就把 s 的接收緩衝中的數據 copybuf 中(注意協議接收到的數據可能大於 buf 的長度,所以在這種情況下要調用幾次

recv 函數才能把s的接收緩衝中的數據 copy 完。recv 函數僅僅是 copy 數據,真正的接收數據是協議來完成的);

​ 其中,recv 函數返回其實際 copy 的字節數。如果 recvcopy 時出錯,那麼它返回 SOCKET_ERROR;如果recv函數在等待協議接收數據時網絡中斷了,那麼它返回 0。

  1. 然後是後兩個:

recvfrom()sendto() 都是基於 UDP 協議。

​ 不同於 TCP 協議,UDP 提供的是一種無連接的、不可靠的數據包協議。它不對數據進行確認、出錯重傳、排序等可靠性處理,但是它卻具有

代碼小、速度快和系統開銷小等優點。對於某些應用程序,使用 UDP 來實現,將帶來更大效率。

​ 與基於 TCP 協議的客戶機/服務器模式的工作流程圖相比較,它們的主要區別 在於: ​

使用 TCP 套接口必須先建立連接(例如客戶進程的 connect() ,服務器進程 的 **listen() **和 accept() ) 。

​ 而 UDP 套接口不需預先連接,它在調用 socket()生成一個套接口後, ​

-> 在服務器端調用 bind() 綁定眾所周知的端口後, 服務器阻塞於 recvfrom() 調用,

​ -> 客戶端調用 sendto() 發送數據請求,阻塞於 recvfrom() 調用,

​ -> 服務器端調用 recvfrom() 接收數據,服務器端也調用 sendto()

向客戶發送數據作為應答,然後阻塞於 recvfrom() 調用,

​ -> 客戶端 調用 recvfrom() 接收數據...... ​

當數據傳輸完成以後,UDP 套接口中的客戶端調用 close() 斷開連接,而 TCP 套接口中的客戶端不必再發出“斷開連接信號”來通知服務器端關閉連接。 ​

一些重要的應用程序,如域名服務系統 DNS、網絡文件 系統 NFS 都使用 UDP 套接口。

// 函數原型:

#include

int recvfrom(int sockfd, void *buff, int len,int flags, struct sockaddr *fromaddr, int *addrlen);

/*

參數 sockfd 為套接口描述字;

參數 buff 為指向讀緩衝的指針;

參數 len 為讀的字節數;

參數 flags 一般設置為 0;

參數 fromaddr 為指向數據接收的套接口地址結構的指針;

參數 addrlen 為套接口結構長度。

函數返回實際讀的字節數,可以為 0,如果出錯,則返回-1。

*/

int sendto(int sockfd, void *mes,int len, int flags, struct sockaddr *toaddr, int *addrlen);

/*

參數 mes 為指向寫緩衝的指針;

參數 toaddr 為指向數據發送的套接口地址結構的指針;

函數返回實際寫的字節數,可以為 0,如果出錯,則返回-1。

*/

當真正開始學習的時候難免不知道從哪入手,學習時頻繁踩坑,導致效率低下影響繼續學習的信心,最終浪費大量時間。為了讓學習變得輕鬆、高效!今天給大家免費分享一套教學資源,幫助大家在成為架構師的道路上披荊斬棘。查看我的主頁即可~

分享主要有C/C++,Linux,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒體,CDN,P2P,K8S,Docker,TCP/IP,協程,DPDK技術,面試技巧方面的資料技術討論。


分享到:


相關文章: