1. 程式人生 > >網路程式設計之伺服器與客戶端的建立

網路程式設計之伺服器與客戶端的建立

一什麼是 套接字:

網路上的兩個程式通過一個雙向的通訊連線實現資料的交換,這個連線的一端稱為一個Socket。Socket的英文原意是“插座”,通常稱之為套接字,來描述IP地址和埠,是一個通訊鏈的控制代碼,用來實現不同虛擬機器或者計算機之間的通訊。 
在Internet上的主機一般運行了多個服務軟體,同時提供幾種服務。每種服務都開啟一個Socket,並繫結到一個埠上,與不同客戶端的不同服務對應著不同的Socket,這樣實現了與多個伺服器進行不同服務的功能,因為Socket的不同,這些客戶端或服務不會混淆。 
應用層通過傳輸層進行資料通訊時,TCP和UDP會遇到同時為多個應用程式程序提供併發服務的問題。多個TCP連線或多個應用程式程序可能需要通過同一個TCP協議埠傳輸資料。為了區別不同的應用程式程序和連線,許多計算機作業系統為應用程式與TCP/IP協議互動提供了稱為套接字 (Socket)的介面,區分不同應用程式程序間的網路通訊和連線。

二 套接字socket型別:

1. 流式套接字(SOCK_STREAM) 
提供面向連線,可靠的資料傳輸服務,資料無差錯,無重複的傳送,且按傳送順序接受。 
2. 資料報式套接字(SOCK_DGRAM) 
提供了一個無連線服務。資料包以獨立包形式被髮送,不提供無錯保證,資料可能丟失或重複,並且接受順序混亂。網路檔案系統(NFS)使用資料報式套接字。 
3. 原始套接字(SOCK_RAW) 
該介面允許對較低層協議,如IP,ICMP直接訪問。常用於檢驗新的協議實現或者訪問現在服務中配置的新裝置。

三 基於TCP的socket程式設計

tcp中伺服器端的流程如下: 
1. 建立套接字(Socket)。 
2. 將套接字繫結到一個本地地址和埠上(bind)。 
3. 將套接字設為監聽模式,準備接受客戶請求(listen)。 
4. 等待客戶請求到來,當請求到來後,接受連線請求。返回一個新的對應於此次連線的套接字(accept)。 
5. 用於返回的套接字和客戶端進行通訊(send/recv)。 
6. 返回,等待另一客戶請求。 
7. 關閉套接字(close)。

四 相關函式說明:

1,建立套接字—socket() 
應用程式在使用套接字前,首先必須擁有一個套接字,系統呼叫socket()嚮應用程式提供建立套接字的手段,其呼叫格式如下:

#include <sys/socket.h>
int socket(int family, int type, int protocol);
該呼叫要接收三個引數: family、type、protocol。 
引數 family指定通訊發生的區域,UNIX系統支援的地址族有:AF_UNIX、AF_INET、AF_NS等,而DOS、WINDOWS中僅支援AF_INET,它是網際網區域。因此,地址族與協議族相同。 
引數type 描述要建立的套接字的型別。 
引數protocol說明該套接字使用的特定協議,如果呼叫者不希望特別指定使用的協議,則置為0,使用預設的連線模式。 
根據這三個引數建立一個套接字,並將相應的資源分配給它,同時返回一個整型套接字號。

2,繫結套接字—bind() 
bind函式把一個本地協議地址賦予一個套接字,對於網際網協議,協議地址是32位的IPv4地址或者是128位的IPv6地址與16位的TCP或UDP埠號的組合。
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *myaddr, socklen_t addrlen);
第一個引數sockfd是函式返回的描述符。 
第二個引數是一個指定特定於協議的地址結構的指標。 
第三個引數是該地址結構的長度。

3,監聽函式—listen() 
listen函式僅由TCP伺服器呼叫,它做兩件事。 
1. 當socket函式建立一個套接字時,它被假設為一個主動套接字,也就是說,它是一個將呼叫connect發起連線的客戶套接字。listern函式把一個未連線的套接字轉換為一個被動套接字,指示核心應接受指向套接字的連線請求。 
2. listen的第二個函式規定核心應該為相應套接字排隊的最大連線數。
#include <sys/socket.h>
int listen(int sockfd, int backlog);
listen函式通常應該在呼叫socket和bind這兩個函式之後,並在呼叫accept函式之前呼叫。
,
4,建立連線—accept()和connect() 
accept函式由TCP伺服器呼叫,用於從已完成連線佇列對頭返回下一個已完成連線。如果已完成連線佇列為空,那麼程序被投入睡眠。
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen);
引數cliaddr和addrlen用來返回已連線的對端程序的協議地址。如果accept成功,那麼其返回值是由核心自動生成的一個全新描述符,代表與所返回客戶的TCP連線。
connect函式是由TCP客戶端呼叫,用來建立與TCP伺服器的連線。
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *servaddr, socklen_t addrlen);
第二個和第三個引數就是連線伺服器的套接字地址結構的指標和該結構的大小,用來將客戶端的套接字與伺服器套接字相連線,所以套接字地址結構必須含有伺服器的IP地址和埠號。 
這兩個函式呼叫用於完成一個完整相關的建立,其中connect()用於建立連線。無連線的 套接字程序也可以呼叫connect(),但這時在程序之間沒有實際的報文交換,呼叫將從本地作業系統直接返回。這樣做的優點是程式設計師不必為每一資料指定目的地址,而且如果收到的一個數據報,其目的埠未與任何套接字建立“連線”,便能判斷該埠不可操作accept() 用於使伺服器等待來自某客戶程序的實際連線。

5, 關閉套接字—close() 

通常的UNIX close()函式用來關閉套接字,並終止TCP/UDP連線。
#include <unistd.h>
int close(int sockfd);

close一個TCP套接字的預設行為是把該套接字標記為關閉狀態,然後立即返回到呼叫程序。

五 socket的程式碼實現:

伺服器程式碼:

#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>

#define PORT 9999

    //初始化套接字,返回監聽套接字
int init_socket()
{
	int listen_socket = socket(AF_INET, SOCK_STREAM, 0);
	if(listen_socket == -1)
	{
		perror("socket ");
		return -1;
	}
	#if 0
	//地址結構
	 struct sockaddr_in
    {
       short int sin_family;  /* Internet地址族 */
       unsigned short int sin_port;  /* 埠號 */
       struct in_addr sin_addr;   /* IP地址 */
       unsigned char sin_zero[8];  /* 填0 */
     };
	#endif
	
	//將套接字繫結到一個本地地址和埠上(bind)
	struct sockaddr_in addr;
	memset(&addr, 0, sizeof(&addr));
	
	addr.sin_family = AF_INET;    //設定地址族
	addr.sin_port = htons(PORT);   //設定本地埠
	addr.sin_addr.s_addr = htonl(INADDR_ANY);   //使用本地任意IP地址
	
	//bind 把一個本地協議地址賦予給套接字
	int ret = bind (listen_socket, (struct sockaddr *)&addr, sizeof(addr));
	if(ret == -1)
	{
		perror("bind ");
		return -1;
	}
	
	//監聽本地套接字,準備接受客戶端請求
	ret = listen(listen_socket,5);
	if(ret == -1)
	{
		perror("listen ");
		return -1;
	}
	printf("等待客戶端連線......\n");
	
	return listen_socket;
}

     //處理客戶端連線,返回與連線上的客戶資訊通訊的套接字
int MYACCEPT(int listen_socket)
{
	/**等待客戶請求到來,當請求到來後,接受連線請求。返回一個新的對應於此次連線的套接字(accept)。**/
	//接受連線
	struct sockaddr_in client_addr;  // 用來儲存客戶端的IP和埠資訊
	int len = sizeof(client_addr);
	
	int client_socket = accept (listen_socket, (struct sockaddr *)&client_addr,&len);
	if(client_socket == -1)
	{
		perror("accept ");
	}
	/***
	inet_aton(const char *cp,struct in_addr *inp)
	表示將a.b.c.d形式的IP轉換為32位的IP,儲存在 inp指標裡面
	***/
	printf("成功連線一個客戶端 : %s\n",inet_ntoa(client_addr.sin_addr));
	
	return client_socket;
}
	
	//處理客戶端連線
int Handle_client(int client_socket)
{
	char buf[1024];
	int i;
	while(1)
	{
		//從客戶端讀取資料
		int ret = read (client_socket, buf, 1024);
		if(ret == -1)
		{
			perror("read ");
			return -1;
		}
		//客戶端退出
		if(ret == 0)
		{
			printf("客戶端退出 \n");
			break;
		}
		buf[ret] = '\0';
		for(i = 0; i < ret - 1; i++)
		{
			buf[i] = buf[i] + 'A' - 'a';//將小寫字母用大寫輸出
		}
		write(client_socket, buf, ret);
		printf("傳送資料:%s\n", buf);
	}
	close(client_socket);  //關閉套接字
}


int main()
{
	int listen_socket = init_socket();  //返回套接字
	
	while(1)
	{
			//獲取與客戶端連線的套接字
		int client_socket = MYACCEPT(listen_socket);
			//處理客戶端請求
		Handle_client(client_socket);
	}
	
	close(listen_socket);//關閉套接字
	
	return 0;
}
客戶端程式碼:
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>

#define PORT  9999

	//客戶端向伺服器請求服務(進行通訊)
int Ask(int c_socket)
{
	char buf[1024];
	
	while(1)
	{
		fgets(buf,1024,stdin);
		
		if(strncmp(buf, "end", 3) == 0)
		{
			break;
		}
		
		write(c_socket, buf, strlen(buf));
		
		int ret = read (c_socket, buf, 1023);
		if(ret == -1)
		{
			perror("read ");
			return -1;
		}
		
		printf(" %s\n",buf);
	}
}

int main()
{
	//建立與伺服器通訊的套接字
	int c_socket = socket(AF_INET, SOCK_STREAM, 0);
	if(c_socket == -1)
	{
		perror("socket ");
		return -1;
	}
	
	//連線伺服器(connect)
	
	#if 0
	//地址結構
	 struct sockaddr_in
    {
       short int sin_family;  /* Internet地址族 */
       unsigned short int sin_port;  /* 埠號 */
       struct in_addr sin_addr;   /* IP地址 */
       unsigned char sin_zero[8];  /* 填0 */
     };
	#endif
	
	//將套接字繫結到一個本地地址和埠上(bind)
	struct sockaddr_in addr;
	memset(&addr, 0, sizeof(&addr));
	
	addr.sin_family = AF_INET;    //設定地址族
	addr.sin_port = htons(PORT);   //設定本地埠
	//addr.sin_addr.s_addr = htonl(INADDR_ANY);   //使用本地任意IP地址
	
	//將a.b.c.d形式的IP轉換為32位的IP,儲存在 inp指標裡面。
	inet_aton("127.0.0.1",&(addr.sin_addr));
	
	//連線伺服器成功返回 0 ,失敗返回 -1
	//成功即可通過c_socket 與伺服器進行通訊
	int ret = connect (c_socket, (struct sockaddr*)&addr,sizeof(addr));
	if(ret == -1)
	{
		perror("connect ");
		return -1;
	}
	
	printf("成功連線伺服器 \n");
	
	//和伺服器進行通訊(請求服務)
	Ask(c_socket);
	
	//關閉套接字
	close(c_socket);
	
	
	return 0;
}