1. 程式人生 > >一、基於linux下TCP\IP協議套接字(socket)初識

一、基於linux下TCP\IP協議套接字(socket)初識

在網際網路的世界中,不同的電腦之間需要進行資料交流,那麼他們就需要一個統一的規範,來確定怎麼樣進行交流。根據國際標準化組織ISO定義的標準,網路結構按照不同的功能分為7層,分別是物理層、資料鏈路層、網路層、傳輸層、會話層、表示層和應用層。在TCP/IP協體系中,將網路結構按照功能劃分為5層,即物理層、資料鏈路層、網路層、傳輸層和應用層。
套接字(socket)則是封裝了資料鏈路層、網路層、傳輸層和應用層,程式猿使用socket封裝的介面,就可以在不同的網際網路裝置之間通訊了。

基於TCP伺服器端的函式呼叫順序

socket:建立套接字bind:分配套接字地址listen:等待連線請求狀態accept:允許連線
read\write:資料交換close:斷開連線

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);
}