一、基於linux下TCP\IP協議套接字(socket)初識
在網際網路的世界中,不同的電腦之間需要進行資料交流,那麼他們就需要一個統一的規範,來確定怎麼樣進行交流。根據國際標準化組織ISO定義的標準,網路結構按照不同的功能分為7層,分別是物理層、資料鏈路層、網路層、傳輸層、會話層、表示層和應用層。在TCP/IP協體系中,將網路結構按照功能劃分為5層,即物理層、資料鏈路層、網路層、傳輸層和應用層。
套接字(socket)則是封裝了資料鏈路層、網路層、傳輸層和應用層,程式猿使用socket封裝的介面,就可以在不同的網際網路裝置之間通訊了。
基於TCP伺服器端的函式呼叫順序
1.建立套接字
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
成功返回檔案描述符*,失敗返回-1;
domain 套接字中使用的協議族資訊
type 資料傳輸型別資訊
protocol 計算機通訊中使用的協議資訊
*檔案描述符:你可以把它理解成為window裡的控制代碼,通俗點來講,你可以認為它是給當前的檔案編的一個編碼名字。
1.1協議族
socket函式的第一個引數傳遞套接字使用的協議型別資訊,此型別資訊稱為協議族。
在<sys/socket.h>標頭檔案中宣告的協議族包括以下幾類。
名稱 | 協議族 |
---|---|
PF_INET | IPv4網際網路協議族 |
PF_INET | IPv6網際網路協議族 |
PF_LOCAL | 本地通訊的UNIX協議族 |
PF_PACKET | 底層套接字的協議族 |
PF_IPX | IPX Novell協議族 |
由於目前IPv4比較常用,所以將重點放在PF_INET協議族上。
實際上,套接字最終採用的協議時通過socket函式第三個引數決定的,但是需要通過第一個引數決定第三個引數。
1.2套接字型別
套接字型別是套接字的資料傳輸方式,通過socket函式的第二個引數傳遞。
套接字型別1:面向連線的套接字(SOCK_STREAM)。 它的特點是: 1.傳輸過程資料不會消失; 2.按序傳輸; 3.傳輸的資料不存在資料邊界。 套接字型別2:面向訊息的套接字(SOCK_DGRAM)。 它的特點是: 1.強調快速傳輸而非傳輸順序; 2.傳輸的資料可能丟失或損毀; 3.傳輸的資料有資料邊界; 4.每次傳輸的資料大小有限制。
1.3最終協議型別
根據以上兩個引數一般便可以確定套接字的型別了,所以通常情況下引數3為0。
但是當出現相同協議族存在多個數據傳輸方式相同的協議的時候,那麼第三個引數就會發揮作用。
//“IPv4協議族中面向連線的套接字”如下
int socket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
//“IPv4協議族中面向訊息的套接字”如下
int socket = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
2.分配套接字地址
#include <sys/socket.h>
int bind(int sockfd, struct *myaddr, socklen_t addrlen);
成功返回0,失敗返回-1;
sockfd 要分配地址資訊的套接字檔案描述符
myaddr 存有地址資訊的結構體變數地址值
addrlen 引數2的長度
2.1地址資訊的表示
檔案描述符就是上面socket的返回值,不用多講,我們先來看看引數myaddr,它是儲存地址的結構體變數。
struct sockaddr_in
{
sa_family_t sin_family; //地址族
u_int16_t sin_port; //16位埠號
struct in_addr sin_addr; //32位IP地址
char sin_zero[0]; //不使用
}
//sockaddr_in結構體裡in_addr結構體是如下定義的
struct in_addr
{
In_addr_t s_addr; //32位IPv4地址
}
sin_family用來儲存地址族資訊,AF_INET表示IPv4協議,AF_INET6表示IPv6協議,AF_LOCAL表示本地通訊協議;
sin_port儲存16位埠號;
sin_addr儲存32位地址資訊;
sin_zero無特殊含義,一般為0。
bind函式呼叫成功後,將第二個引數的地址資訊分配給第一個引數中的套接字。
3.等待連線請求狀態
我們已經給套接字綁定了地址資訊,接下來呼叫listen函式進入等待連線請求狀態。
#include <sys/socket.h>
int listen(int sock, int backlog);
成功返回0,失敗返回-1;
sock 希望進入等待連線請求狀態的套接字檔案描述符,傳遞的檔案描述符套接字將成為伺服器套接字;
backlog 連線請求狀態佇列的長度;
當客戶端請求連線時,在受理請求之前,伺服器端處於等待請求狀態,而連線請求狀態佇列的長度則限制了等待的客戶端套接字的數量。
4.允許連線
呼叫listen函式後,當有客戶端請求,伺服器端按序受理。當呼叫accept函式時,成功後自動建立新的套接字用來和客戶端套接字進行資料交換,其返回值就是新建的套接字的檔案描述符。
#include <sys/socket.h>
int accept(int sock, struct sockaddr *addr, socklen_t *addrlen);
成功返回檔案描述符,失敗返回-1;
sock 伺服器套接字檔案描述符
addr 儲存發起請求的客戶端地址資訊的變數地址
addrlen 儲存第二個引數的長度的地址
5.資料交換
當accept函式呼叫成功後,伺服器端和客戶端就連線上了。接下來就是資料交換。
5.1寫入檔案
#include <unistd.h>
ssize_t write(int fd, const void * buf, size_t nbytes);
成功返回寫入的位元組數,失敗返回-1;
fd 顯示資料傳輸物件的檔案描述符
buf 儲存要傳輸的緩衝地址值
nbytes 要傳輸的位元組數
5.2讀取檔案
#include <unistd.h>
ssize_t read(int fd, void * buf, size_t nbytes);
成功返回接收的位元組數,失敗返回-1;
fd 顯示資料接收物件的檔案描述符
buf 儲存接收資料的緩衝地址值
nbytes 要接受資料的最大位元組
6.斷開連線
資料交換結束後,呼叫close函式關閉套接字。
#include <unistd.h>
int close(int fd);
成功返回0,失敗返回-1;
fd 要關閉的檔案描述符
相關所有內容來自本人對《TCP/IP網路程式設計》的學習理解,這是一本很詳細很適合新人讀的書,我認真研讀了第三遍,希望大家也能夠看看。
下面給出完整客戶端程式碼
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#define BUF_SIZE 1024
void error_handling(char *message);
int main(int argc, char *argv[]){
int serv_sock, clnt_sock; //serv_sock:檔案描述符; clnt_sock:accept檔案描述符
char message[BUF_SIZE]; //資料流長度
int str_len,i; //str_len:讀取接收檔案的位元組數
struct sockaddr_in serv_adr, clnt_adr;
socklen_t clnt_adr_sz;
if(argc!=2){
printf("Usage:%s <port>\n",argv[0]);
exit(1);
}
serv_sock = socket(PF_INET, SOCK_STREAM, 0);
if(serv_sock == -1){
error_handling("socket() error");
}
memset(&serv_adr,0,sizeof(serv_adr));
serv_adr.sin_family = AF_INET;
serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);
serv_adr.sin_port = htons(atoi(argv[1]));
if(bind(serv_sock,(struct sockaddr*)&serv_adr,sizeof(serv_adr)) == -1 ){
error_handling("bind() error");
}
if(listen(serv_sock, 5) == -1 ){
error_handling("listen() error");
}
clnt_adr_sz = sizeof(clnt_adr);
for( i <0; i<5; i++){
clnt_sock = accept(serv_sock,(struct sockaddr*)&clnt_adr,&clnt_adr_sz);
if(clnt_sock == -1)
error_handling("accept() error");
else
printf("Conneted client %d \n", i+1);
while((str_len = read(clnt_sock,message, BUF_SIZE)) != 0)
write(clnt_sock,message,str_len);
close(clnt_sock);
}
close(serv_sock);
return 0;
}
void error_handling(char *message){
fputs(message, stderr);
fputc('\n',stderr);
exit(1);
}