Socket 網絡通信簡述

 

socket函數簡述

 

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

函數功能 :socket函數對應於普通文件的打開操作。普通文件的打開操作返回一個文件描述字,而socket()用於創建一個socket描述符(socket descriptor),它唯一標識一個socket。這個socket描述字跟文件描述字一樣,後續的操作都有用到它,把它作爲參數,通過它來進行一些讀寫操作。

返回值: sockfd     sockfd是描述符,也就是你所創建的套接字,類似一個門牌號
函數參數  : protofamily:即協議域,又稱爲協議族(family)。常用的協議族有,AF_INET(IPV4)、AF_INET6(IPV6)、AF_LOCAL(或稱AF_UNIX,Unix域socket)、AF_ROUTE等等。協議族決定了socket的地址類型,在通信中必須採用對應的地址,如AF_INET決定了要用ipv4地址(32位的)與端口號(16位的)的組合、AF_UNIX決定了要用一個絕對路徑名作爲地址。
        type:指定socket類型。常用的socket類型有,SOCK_STREAM、SOCK_DGRAM、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET等等。    
        protocol:就是指定協議。常用的協議有,IPPROTO_TCP、IPPTOTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等,它們分別對應TCP傳輸協議、UDP傳輸協議、STCP傳輸協議、TIPC傳輸協議
        注意:並不是上面的type和protocol可以隨意組合的,如SOCK_STREAM不可以跟IPPROTO_UDP組合。當protocol爲0時,會自動選擇type類型對應的默認協議。


 

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

 


函數功能:bind()函數把一個地址族中的特定地址賦給socket,也可以說是綁定ip端口和socket。例如對應AF_INET、AF_INET6就是把一個ipv4或ipv6地址和端口號組合賦給socket。
函數參數:sockfd:即socket描述字,它是通過socket()函數創建了,唯一標識一個socket。bind()函數就是將給這個描述字綁定一個名字。addr:一個const struct sockaddr *指針,指向要綁定給sockfd的協議地址。這個地址結構根據地址創建socket時的地址協議族的不同而不同。addrlen:對應的是地址的長度。

通用函數類型:

struct sockaddr{
  sa_family_t  sa_family;
  char         sa_data[14];
}
如ipv4對應的是:

struct sockaddr_in {
    sa_family_t    sin_family; /* address family: AF_INET */
    in_port_t      sin_port;   /* port in network byte order 2字節*/
    struct in_addr sin_addr;   /* internet address 4字節*/
  unsigned char sin_zero[8];
};
/* Internet address. */
struct in_addr {
    uint32_t       s_addr;     /* address in network byte order */
};
ipv6對應的是: 

struct sockaddr_in6 { 
    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];   /* IPv6 address */ 
};
Unix域對應的是: 

#define UNIX_PATH_MAX    108
struct sockaddr_un { 
    sa_family_t sun_family;               /* AF_UNIX */ 
    char        sun_path[UNIX_PATH_MAX];  /* pathname */ 
};


 

int_addr_t indet_addr(const char *cp)

  功能:將字符串形式的IP地址轉化爲整數型的IP地址(網絡字節序)

  範例:int_addr.saddr=inet_addr("192.168.1.1");

  char *inet_ntoa(struct in_addr)

  功能:將整數形式的IP地址轉化爲字符串形式的IP地址


 

int listen(int sockfd, int backlog)
  listen函數的第一個參數即爲要監聽的socket描述字,第二個參數爲相應socket可以排隊的最大連接個數。socket()函數創建的socket默認是一個主動類型的,listen函數將socket變爲被動類型的,等待客戶的連接請求。 

 int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen)

  connect函數的第一個參數即爲客戶端的socket描述字,第二參數爲服務器的socket地址,第三個參數爲socket地址的長度。客戶端通過調用connect函數來建立與TCP服務器的連接。成功返回0,若連接失敗則返回-1。


 int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); 

 

  參數sockfd就是上面解釋中的監聽套接字,這個套接字用來監聽一個端口,當有一個客戶與服務器連接時,它使用這個一個端口號,而此時這個端口號正與這個套接字關聯。當然客戶不知道套接字這些細節,它只知道一個地址和一個端口號。

參數addr

  這是一個結果參數,它用來接受一個返回值,這返回值指定客戶端的地址,當然這個地址是通過某個地址結構來描述的,用戶應該知道這一個什麼樣的地址結構。如果對客戶的地址不感興趣,那麼可以把這個值設置爲NULL。

參數addrlen

如同大家所認爲的,它也是結果的參數,用來接受上述addr的結構的大小的,它指明addr結構所佔有的字節個數。同樣的,它也可以被設置爲NULL。 

如果accept成功返回,則服務器與客戶已經正確建立連接了,此時服務器通過accept返回的套接字來完成與客戶的通信。

注意:  accept默認會阻塞進程,直到有一個客戶連接建立後返回,它返回的是一個新可用的套接字,這個套接字是連接套接字。此時我們需要區分兩種套接字,監聽套接字: 監聽套接字正如accept的參數sockfd,它是監聽套接字,在調用listen函數之後,是服務器開始調用socket()函數生成的,稱爲監聽socket描述字(監聽套接字),連接套接字:一個套接字會從主動連接的套接字變身爲一個監聽套接字;而accept函數返回的是已連接socket描述字(一個連接套接字),它代表着一個網絡已經存在的點點連接。一個服務器通常通常僅僅只創建一個監聽socket描述字,它在該服務器的生命週期內一直存在。內核爲每個由服務器進程接受的客戶連接創建了一個已連接socket描述字,當服務器完成了對某個客戶的服務,相應的已連接socket描述字就被關閉。連接套接字socketfd_new 並沒有佔用新的端口與客戶端通信,依然使用的是與監聽套接字socketfd一樣的端口號(如果想讓多臺客戶端同時接入並工作,可使用多進程)


int recv( SOCKET s,char FAR *buf,int len, int flag)

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

   該函數的第一個參數指定接收端套接字描述符; 第二個參數指明一個緩衝區,該緩衝區用來存放recv函數接收到的數據;第三個參數指明buf的長度;第四個參數一般置0。


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

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

   客戶程序一般用send函數向服務器發送請求,而服務器則通常用send函數來向客戶程序發送應答。第一個參數指定發送端套接字描述符;第二個參數指明一個存放應用程序要發送數據的緩衝區;第三個參數指明實際要發送的數據的字節數;第四個參數一般置0。

 


int close(int sockfd)

            返回:若成功則返回0.失敗則返回-1;

       close一個TCP套接字的默認行爲是把該套接字標記成已關閉,然後立即返回到調用程序。該套接字描述符不能再由調用進程使用,也就是說它不能再作爲read和write的第一個參數。然而TCP將嘗試發送已排隊等待發送到對端的任何數據,發送完畢後發生的是正常的TCP連接終止序列。


Socket流程


 

 

服務端:創建套接字->配置端口信息->綁定套接字->開始監聽->相關操作<-

客戶端:建套接字->配置連接端口信息->綁定套接字->連接->相關操作-<

簡單例子(來源我博哥,在此感謝博哥)

client

#include<netinet/in.h>         // sockaddr_in 
#include<sys/types.h>          // socket 
#include<sys/socket.h>         // socket 
#include<stdio.h>              // printf 
#include<stdlib.h>             // exit 
#include<string.h>             // bzero 
    
   
#define SERVER_PORT 8000 
#define BUFFER_SIZE 1024 
#define FILE_NAME_MAX_SIZE 512 


struct sockaddr_in client_addr;//客戶端結構體類型(內含有端口號,IPv4地址,以及地址族的結構體)
struct sockaddr_in server_addr;
int client_socket_fd;//客戶端socket返回值,用於判斷
char file_serve_name[FILE_NAME_MAX_SIZE+1]; 
char file_client_local[FILE_NAME_MAX_SIZE+1];
char buffer[BUFFER_SIZE]; 
FILE *fp;



int Start_Client_Socket()// 聲明並初始化一個客戶端的socket地址結構 
{
  	bzero(&client_addr, sizeof(client_addr)); 
  	client_addr.sin_family = AF_INET; 
  	client_addr.sin_addr.s_addr = htons(INADDR_ANY); 
  	client_addr.sin_port = htons(0); 
  	return 1;
}

int Creat_Socket()// 創建socket,若成功,返回socket描述符 
{
  	client_socket_fd = socket(AF_INET, SOCK_STREAM, 0); 
  	if(client_socket_fd < 0) 
  	{ 
    		perror("創建socket失敗:"); 
    		exit(1); 
  	} 
  	return 1;
}
  
int Client_Server_Bind() // 綁定客戶端的socket和客戶端的socket地址結構
{
  	if(-1 == (bind(client_socket_fd, (struct sockaddr*)&client_addr, sizeof(client_addr)))) 
  	{ 
    		perror("Client Bind Failed:"); 
    		exit(1); 
  	} 
  	return 1;
}

int Start_Serve_Socket()// 聲明一個服務器端的socket地址結構,並用服務器那邊的IP地址及端口對其進行初始化,用於後面的連接 
{
  	char IP_serve_local[FILE_NAME_MAX_SIZE+1];
  	bzero(IP_serve_local, FILE_NAME_MAX_SIZE+1); 
  	printf("請輸入服務器的IP地址:\t"); 
  	scanf("%s",IP_serve_local); 
  	bzero(&server_addr, sizeof(server_addr)); 
  	server_addr.sin_family = AF_INET; 
  	if(inet_pton(AF_INET, IP_serve_local, &server_addr.sin_addr) == 0) 
  	{ 
    		perror("服務器IP錯誤:"); 
    		exit(1); 
  	} 
  	server_addr.sin_port = htons(SERVER_PORT); 
  	return 1;
}
 
int Client_Serve_Link()// 向服務器發起連接,連接成功後client_socket_fd代表了客戶端和服務器的一個socket連接 
{
  	socklen_t server_addr_length = sizeof(server_addr); 
  	if(connect(client_socket_fd, (struct sockaddr*)&server_addr, server_addr_length) < 0) 
  	{ 
    		perror("無法連接到服務器:"); 
    		exit(0); 
  	} 
   	return 1;
}

int File_LocalName_Trans()
{
  	bzero(file_serve_name, FILE_NAME_MAX_SIZE+1);
  	bzero(file_client_local, FILE_NAME_MAX_SIZE+1); 
  	printf("請輸入要獲取的文件在服務器的位置及名字:\t"); 
  	scanf("%s",file_serve_name); 
  	printf("請輸入要保存的文件在客戶端的位置及名字:\t"); 
  	scanf("%s",file_client_local); 
  	bzero(buffer, BUFFER_SIZE); 
  	strncpy(buffer, file_serve_name, strlen(file_serve_name)>BUFFER_SIZE?BUFFER_SIZE:strlen(file_serve_name)); 
  	return 1;
}

int Send_File_Server_Name()// 向服務器發送要獲取的位置及名稱
{
  	if(send(client_socket_fd, buffer, BUFFER_SIZE, 0) < 0) 
  	{ 
    		perror("發送文件名稱錯誤:"); 
    		exit(1); 
  	} 
	return 1;
}

int Write_Server_To_Client()
{
   // 打開文件,準備寫入 
  	fp = fopen(file_client_local, "w"); 
  	if(NULL == fp) 
  	{ 
    		printf("File:\t%s 無法寫入\n", file_serve_name); 
    		exit(1); 
  	} 
   
  // 從服務器接收數據到buffer中 
  // 每接收一段數據,便將其寫入文件中,循環直到文件接收完並寫完爲止 
  	bzero(buffer, BUFFER_SIZE); 
  	int length = 0; 
  	while((length = recv(client_socket_fd, buffer, BUFFER_SIZE, 0)) > 0) 
  	{ 
    		if(fwrite(buffer, sizeof(char), length, fp) < length) 
    		{ 
      			printf("File:\t%s 寫入失敗\n", file_serve_name); 
      			break; 
    		} 
    		bzero(buffer, BUFFER_SIZE); 
  	} 
  	printf("文件:\t%s 接收成功\n", file_serve_name);
	return 1; 
}
 
int Close_File_Socket()// 接收成功後,關閉文件,關閉socket
{
  
  	close(fp); 
  	close(client_socket_fd); 
	return 1;
}


int main() 
{ 

 Start_Client_Socket(); 
  	Creat_Socket();
 	Client_Server_Bind();
  	Start_Serve_Socket();
  	Client_Serve_Link();
  	File_LocalName_Trans();
  	Send_File_Server_Name();
  	Write_Server_To_Client();
  	Close_File_Socket();
 
  	return 0; 
}
 

sever

#include<netinet/in.h>         					// sockaddr_in 
#include<sys/types.h>          					// socket 
#include<sys/socket.h>         					// socket 
#include<stdio.h>              					// printf 
#include<stdlib.h>             					// exit 
#include<string.h>             					// bzero 
   
#define SERVER_PORT 8000 
#define LENGTH_OF_LISTEN_QUEUE 20 
#define BUFFER_SIZE 1024 
#define FILE_NAME_MAX_SIZE 512 

struct sockaddr_in server_addr; 
struct sockaddr_in client_addr;

socklen_t client_addr_length = sizeof(client_addr);       	// 定義客戶端的socket地址結構 

int server_socket_fd;
int new_server_socket_fd;
int opt = 1; 
int length = 0; 

char buffer[BUFFER_SIZE];
char file_name[FILE_NAME_MAX_SIZE+1];           		//從客戶端傳過來,要獲取的文件名字及地址

FILE *fp;

int Start_Server_Socket()  					// 聲明並初始化一個服務器端的socket地址結構 
{
  	bzero(&server_addr, sizeof(server_addr)); 
  	server_addr.sin_family = AF_INET; 
  	server_addr.sin_addr.s_addr = htons(INADDR_ANY); 
  	server_addr.sin_port = htons(SERVER_PORT); 
  	return 1;
}

int Creat_Socket()  						// 創建socket,若成功,返回socket描述符 
{
  	server_socket_fd = socket(PF_INET, SOCK_STREAM, 0); 
  	if(server_socket_fd < 0) 
  	{ 
    		perror("創建socket失敗:"); 
    		exit(1); 
  	} 
  	setsockopt(server_socket_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); 
	return 1;
}

int Client_Server_Bind() 					// 綁定客戶端的socket和客戶端的socket地址結構
{
  	if(-1 == (bind(server_socket_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)))) 
  	{ 
    		perror("Server Bind Failed:"); 
    		exit(1); 
  	} 
	return 1;
}

int Serve_Listen()						//不斷監聽從客戶端傳來的信息,每來一個就開啓新進程
{
  	if(-1 == (listen(server_socket_fd, LENGTH_OF_LISTEN_QUEUE))) 
  	{ 
    		perror("監聽失敗:"); 
    		exit(1); 
  	}
	return 1; 
   
}
int Serve_Accept_Link()						// 接受連接請求,返回一個新的socket(描述符),這個新socket用於同連接的客戶端通信
{								// accept函數會把連接到的客戶端信息寫到client_addr中 
    	new_server_socket_fd = accept(server_socket_fd, (struct sockaddr*)&client_addr, &client_addr_length);     
    	if(new_server_socket_fd < 0) 
    	{ 
      		perror("Server Accept Failed:"); 
    		exit(0);
    	} 
	return 1;
}

int Send_File_Name()  						//接收需要發送的文件位置及文件名
{
    	bzero(buffer, BUFFER_SIZE); 				
    	if(recv(new_server_socket_fd, buffer, BUFFER_SIZE, 0) < 0) //recv函數接收數據到緩衝區buffer中
    	{ 
      		perror("接受文件名失敗"); 
      		exit(0); 
    	} 
   								// 然後從buffer(緩衝區)拷貝到file_name中 
    	bzero(file_name, FILE_NAME_MAX_SIZE+1); 
    	strncpy(file_name, buffer, strlen(buffer)>FILE_NAME_MAX_SIZE?FILE_NAME_MAX_SIZE:strlen(buffer)); 
    	printf("要發送的文件名爲:%s\n", file_name); 
	return 1;
}

int Document_Send()
{
   								// 打開文件並讀取文件數據 
    	fp = fopen(file_name, "r"); 
    	if(NULL == fp) 
    	{ 
      		printf("File:%s Not Found\n", file_name); 
    	} 
    	else 
    	{ 
      		bzero(buffer, BUFFER_SIZE); 
								// 每讀取一段數據,便將其發送給客戶端,循環直到文件讀完爲止 
      		while((length = fread(buffer, sizeof(char), BUFFER_SIZE, fp)) > 0) 
      		{ 
        		if(send(new_server_socket_fd, buffer, length, 0) < 0) 
        		{ 
          			printf("Send File:%s Failed./n", file_name); 
          			exit(0); 
        		} 
        		bzero(buffer, BUFFER_SIZE); 
      		} 
      		
      		fclose(fp); 					// 關閉文件 
      		printf("文件:%s 發送成功\n", file_name); 
    	} 
	return 1;
   
}

int Link_Trans()
{
  	while(1) 
	{ 
		Serve_Accept_Link();
		Send_File_Name();
		Document_Send();	
		close(new_server_socket_fd); 			// 關閉與客戶端的連接 
	} 
	return 1;
}


int main(void) 
{ 
	Start_Server_Socket();					//服務器server初始化
	Creat_Socket();						//創建服務器socket
	Client_Server_Bind();  					//客戶端與服務器綁定
	Serve_Listen();   					//服務器進行監聽
	Link_Trans();						//鏈接並且進行文件的傳輸
   	close(server_socket_fd);   				//關閉監聽用的socket
  	return 0; 
}

函數的組合應用便可以實現網絡通信的功能

 

 

 

------------------------------------------------------- -------------正在更新中--------------------------------------------------------------------------------------------------------------------------------------------------最後修改時間:2019年4月10日16:11:50-------------------------------------------------------------------

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章