linux網絡編程——TCP淺析及socket編程實例

1、BS 和 CS

(1)CS架構:(client server,客戶端服務器架構)

(2)BS架構:(broswer server,瀏覽器服務器架構)

 

2、關於TCP理解的重點

(1)TCP協議工作在傳輸層,對上服務socket接口,對下調用IP層。

(2)TCP協議面向連接,通信前必須先3次握手建立連接關係後才能開始通信。

(3)TCP協議提供可靠傳輸,不怕丟包、亂序等。

 

3、TCP如何保證可靠傳輸

(1)TCP在傳輸有效信息前要求通信雙方必須先握手,建立連接才能通信

(2)TCP的接收方收到數據包後會ack給發送方,若發送方未收到ack會丟包重傳

(3)TCP的有效數據內容會附帶校驗,以防止內容在傳遞過程中損壞

(4)TCP會根據網絡帶寬來自動調節適應速率(滑動窗口技術)

(5)發送方會給各分割報文編號,接收方會校驗編號,一旦順序錯誤即會重傳。

 

4、TCP的三次握手

(1)建立連接需要三次握手

(2)建立連接的條件:服務器listen時客戶端主動發起connect

 

5、TCP的四次握手(揮手)

(1)關閉連接需要四次握手

(2)服務器或者客戶端都可以主動發起關閉

注:這些握手協議已經封裝在TCP協議內部,socket編程接口平時不用管。

 

6、基於TCP通信的服務模式

(1)具有公網IP地址的服務器(或者使用動態IP地址映射技術)

(2)服務器端socket、bind、listen後處於監聽狀態

(3)客戶端socket後,直接connect去發起連接

(4)服務器收到並同意客戶端接入後會建立TCP連接,然後雙方開始收發數據,收發時是雙向的,而且雙方均可發起。

(5)雙方均可發起關閉連接。

 

7、常見的使用了TCP協議的網絡應用

(1)http、ftp

(2)QQ服務器

(3)mail服務器

 

8、socket編程接口介紹

8.1、建立連接

(1)socket。

socket函數類似於open,用來打開一個網絡連接,如果成功則返回一個網絡文件描述符(int類型),之後我們操作這個網絡連接都通過這個網絡文件描述符。

(2)bind

(3)listen

(4)connect

8.2、發送和接收

(1)send 和 write

(2)recv 和 read

8.3、輔助性函數

(1)inet_aton、 inet_addr、 inet_ntoa

(2)inet_ntop、 inet_pton

8.4、表示IP地址的相關數據結構

(1)都定義在 netinet/in.h

(2)struct socket,

這個結構體是網絡編程接口中用來表示一個IP地址的,注意這個IP地址是不區分IPv4和IPv6的(或者說是兼容IPv4和IPv6的)

(3)typedef uint32_t in_addr_t; 網絡內部用來表示IP地址的類型

(4)struct in_addr

        {

              in_addr_t s_addr;

         };

(5)struct sockaddr_in

結構體詳細信息:

struct sockaddr_in

{

__SOCKADDR_COMMON (sin_);

in_port_t sin_port; /* Port number. */

struct in_addr sin_addr; /* Internet address. */

 

/* Pad to size of `struct sockaddr'. */

unsigned char sin_zero[sizeof (struct sockaddr) -

__SOCKADDR_COMMON_SIZE -

sizeof (in_port_t) -

sizeof (struct in_addr)];

};

(6)struct sockaddr

這個結構體是linux的網絡編程接口中用來表示IP地址的標準結構體,bind、connect等函數中都需要這個結構體,這個結構體是兼用IPv4和IPv6的。在實際編程中這個結構體會被一個 struct sockaddr_in 或者一個 struct sockaddr_in6所填充。

(7)表示IP地址的相關數據結構

類型小結:in_addr_t、 struct in_addr、 struct sockaddr_in、struct sockaddr

 

9、inet_addr、inet_ntop、 inet_pton函數使用實例:

  • inet_addr                  //IP:點分十進制轉二進制
  • inet_ntop                  //IP: 二進制轉十進制(字符串顯示192.168.xx.xx)
  • inet_pton                  //IP:點分十進制轉二進制 (兼容IPv6的新函數)

#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define IPADDR	"192.168.1.102"

//0x66	01	a8	c0
//102	1	168	192
//網絡字節序,其實就是大端模式(統一規定的)

int main(void)
{
	struct in_addr addr = {0};
	char buf[50] = {0};
	
	addr.s_addr = 0x6601a8c0;
	
	inet_ntop(AF_INET, &addr, buf, sizeof(buf));	//二進制轉十進制(字符串顯示192.168.xx.xx)
	
	printf("ip addr = %s.\n", buf);
	
	/* 
	//使用inet_pton來轉換
	int ret = 0;
	char buf[50] = {0};
	
	in_addr_t addr = 0;
	
	//addr = inet_addr(IPADDR);	//IP:點分十進制轉二進制
	ret = inet_pton( AF_INET, IPADDR, &addr); //IP:點分十進制轉二進制 (兼容IPv6的新函數)
	if (ret != 1)
	{
		printf("inet_pton error.\n");
		return -1;
	}
	
	printf("addr = 0x%x.\n", addr);		//0x6601a8c0 */
	
	return 0;
}

 

 

10、socket實踐編程

10.1、服務器端程序編寫

(1)socket

(2)bind

(3)listen

(4)accept

注意:

accept,返回值是一個fd, accept正確返回就表示我們已經和前來連接我的客戶端之間建立了一個TCP連接了,以後我們就要通過這個連接來和客戶端進行讀寫操作,讀寫操作就需要一個fd,這個fd就由accept來返回了。

socket返回的fd叫做監聽fd,是用來監聽客戶端的,不能用來和任何客戶端進行讀寫;

accept返回的fd叫做連接fd,用來和連接的客戶端程序進行讀寫。

 

10.2、客戶端程序編寫 

(1)socket

(2)connect

概念:端口號,實質就是一個數字編號,用來在我們一臺主機中(主機的操作系統中)唯一的標識一個能上網的進程。端口號和IP地址一起會被打包到當前進程發出或者接收到的每一個數據包中。每一個數據包將來在網絡上傳遞的時候,內部都包含了發送方和接收方的信息(就是IP地址和端口號),所以IP地址和端口號這兩個往往打包在一起不分家的。

 

10.3、

(1)客戶端發送 & 服務器接收

(2)服務器發送 & 客戶端接收

(3)客戶端和服務器原則上都可以任意的發和收,但是實際上雙方必須配合:

  • client發的時候server就收,
  • 而server發的時候client就收。

(4)必須瞭解到一點:client和server之間的通信是異步的。

(5)解決方案:依靠應用層協議來解決。說白了就是我們server和client事先做好一系列的通信約定

 

10.4、自定義應用層協議步驟

(1)自定義應用層協議第一步:規定發送和接收方法

  • 規定連接建立後由客戶端主動向服務器發送1個請求數據包,然後服務器收到數據包後回覆客戶端一個迴應數據包,這就是一個通信回合。
  • 整個連接的通信就是由N多個回合組成的。

(2)、自定義應用層協議第二步:定義數據包格式

 

10.5、socket編程實例

實例功能:
(1)編寫服務器、客戶端程序,實現服務器/客戶端之間的TCP通信
(2)自定義應用層協議,實現學生信息註冊、獲取等基本功能,                                                                                        如:客戶端向服務器提交註冊學生信息的請求,服務器收到請求後,註冊完成並返回註冊成功的信息。
(3)本程序只是走通模擬了這個流程,具體的服務器註冊等功能並未實現,可根據具體業務進行拓展。

 

服務器端程序:server.c

//server.c

#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>          /* See NOTES */
#include <arpa/inet.h>
#include <errno.h>
#include "pubdata.h"


#define SERPORT 6666	//通信端口號
#define SERADDR "192.168.1.108"		//IP地址  ubuntu中ifconfig看到的
#define BACKLOG 100

char sendbuf[100] = {0};
char recvbuf[100] = {0};


int main(void)
{
	
	int sockfd = -1, ret = -1, clifd = -1;
	socklen_t len = 0;
	struct sockaddr_in seraddr = {0};
	struct sockaddr_in cliaddr = {0};
	
	//第一步:先socket打開文件描述符
	sockfd = socket(AF_INET, SOCK_STREAM, 0);  
	if (-1 == sockfd)
	{
		perror("socket");
		return -1;
	}
	
	printf("socketfd = %d.\n", sockfd);
	
	//第二步:bind綁定sockfd和當前電腦的ip地址&端口號
	seraddr.sin_family = AF_INET;	//設置地址族爲IPv4
	seraddr.sin_port = htons(SERPORT);//htons: host to net short 設置地址的端口號信息
	seraddr.sin_addr.s_addr = inet_addr(SERADDR);	//設置IP地址
	ret = bind(sockfd, ( struct sockaddr *)&seraddr, sizeof(seraddr));	//綁定
	if (ret < 0)
	{
		perror("bind");
		return -1;
	}
	printf("bind success.\n");
	
	//第三步:listen監聽端口
	ret = listen(sockfd, BACKLOG);	
	if (ret < 0)
	{
		perror("listen");
		return -1;
	}
	
	//第四步:accept阻塞等待客戶端接入
	clifd = accept(sockfd, (struct sockaddr *)&cliaddr, &len);
	printf("連接已建立,client fd = %d.\n", clifd);
	
	
    //連接建立就可以通信了
	//服務器端接收
	 
	 while(1)
	 {
		 //定義一個用於描述自定義通信協議的結構體類型info,並定義結構體變量st
		 info st;	
		 
		//回合中第一步:服務器收
		ret = recv(clifd, &st, sizeof(info), 0);
		
		//回合中的第二步:服務器解析客戶端數據包,然後幹活
		if (st.cmd == CMD_REGISTER)
		{
			printf("用戶要註冊的學生信息\n");
			printf("學生姓名:%s, 學生年齡:%d.\n", st.name, st.age);
			//在這裏服務器要進行真正的註冊動作,一般是插入數據庫一條信息記錄
			
			//回合中的第三步:回覆客戶端
			st.stat = STAT_OK;
			ret = send(clifd, &st, sizeof(info), 0);	
		}
		
		if (st.cmd == CMD_CHECK)
		{
			//具體動作
			
		}
		
		if (st.cmd == CMD_REGISTER)
		{
			//具體動作
			
		}

	 }
	 	 
	
	return 0;
}

 

客戶端程序:client.c

//client.c

#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>          /* See NOTES */
#include <arpa/inet.h>
#include <errno.h>
#include <string.h>
#include "pubdata.h"


#define SERPORT 6666	            //服務器給客戶端開放的通信端口號
#define SERADDR "192.168.1.108"		//服務器給客戶端開放的IP地址
#define BACKLOG 100

char sendbuf[100] = {0};
char recvbuf[100] = {0};



int main(void)
{
	
	int sockfd = -1, ret = -1, clifd = -1;
	struct sockaddr_in seraddr = {0};
	
	//第一步:先socket打開文件描述符
	sockfd = socket(AF_INET, SOCK_STREAM, 0);
	//錯誤檢驗
	if (-1 == sockfd)
	{
		perror("socket");
		return -1;
	}
	
	printf("socketfd = %d.\n", sockfd);
	
	//第二步:connect連接服務器
	seraddr.sin_family = AF_INET;	//設置地址族爲IPv4
	seraddr.sin_port = htons(SERPORT);//設置地址的端口號信息
	seraddr.sin_addr.s_addr = inet_addr(SERADDR);	//設置IP地址
	ret = connect(sockfd, (const struct sockaddr *)&seraddr, sizeof(seraddr));//連接服務器
	//錯誤檢驗
	if (ret < 0)
	{
		perror("connect");
		return -1;
	}
	printf("成功建立連接.\n");
	
    //第三步:建立連接之後就可以開始通信了
	while(1)
	{	
        //定義一個用於描述自定義通信協議的結構體類型info,並定義結構體變量st1
		info st1;
		printf("請輸入學生姓名\n");
		scanf("%s", st1.name);
		printf("請輸入學生年齡\n");
		scanf("%d", &st1.age);
		st1.cmd = CMD_REGISTER;    //使用自定義註冊功能碼CMD_REGISTER
		
		//回合中第一步:客戶端給服務器發送消息
		ret = send(sockfd, &st1, sizeof(info), 0);
		printf("發送了一個學生信息.\n"); 
		
		//回合中的第二步:客戶端接收服務器的回覆
		memset(&st1, 0, sizeof(st1));
		ret = recv(sockfd, &st1, sizeof(st1), 0);
		
		//回合中第三步:客戶端解析服務器的回覆,再做下一步定奪
		if (st1.stat == STAT_OK)
		{
			printf("註冊學生信息成功.\n"); 
		}
		else
		{
			printf("註冊學生信息失敗.\n"); 
		}
			
	}
	
	
	return 0;
}

 

定義數據包格式:

用於描述自定義應用層協議的公用數據結構信息(包含在pubdata.h頭文件中):

pubdata.h

//pubdata.h

#ifndef __PUBDATA_H
#define __PUBDATA_H

#define CMD_REGISTER	1001	//註冊學生信息
#define CMD_CHECK	1002	//檢驗學生信息
#define CMD_GETINFO   	1003	//獲取學生信息

#define STAT_OK		30		//回覆ok
#define STAT_ERR 	31		//回覆出錯了

typedef struct commu
{
	char name[20];		    //學生姓名
	int age; 		    //學生年齡
	int cmd;		    //命令碼
	int stat;		    //狀態信息,用於回覆
}info;

#endif

 

上述server.c和client.c程序通過編譯鏈接後,生成兩個可執行程序,如:server和client,

首先運行服務器端server,

然後運行客戶端client,

正常情況下,雙方即成功建立TCP通信,

功能驗證:

客戶端向服務器端提交學生信息,

服務器收到客戶端請求後則會主動回覆信息,

如下:

客戶端:

服務器端:

 

出現上述結果則表明客戶端和服務器端之間通信成功,且定義的數據包格式亦能正常工作,即:

(1)規定連接建立後由客戶端主動向服務器發送1個請求數據包,然後服務器收到數據包後回覆客戶端一個迴應數據包,這就是一個通信回合。

(2)整個連接的通信就是由N多個回合組成的。

 

注:學習資料參考《朱老師物聯網大講堂》——linux網絡編程實踐

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