歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux管理 >> Linux網絡 >> linux網絡編程實踐

linux網絡編程實踐

日期:2017/3/3 13:40:42   编辑:Linux網絡

《朱老師物聯網大講堂》學習筆記

學習地址:www.zhulaoshi.org

(1).

linux網絡編程框架,

網絡是分層的,OSI是7層的,這種分層是理論的,

實際應用只有4層,TCP/IP,

處理問題時,一定要知道你自己在哪一層,我們目前關注的是應用層,

因為網絡是目前最復雜的通信體系,

曾經有很多類似tcp/ip的協議,

CS,client server,客戶端服務器架構,

比如qq客戶端與qq服務器進行通信,

BS,broswer server,浏覽器服務器架構,

可以把浏覽器理解為一個通用的客戶端,主流的,

(2).

TCP協議傳輸特性,

工作在傳輸層,

對上為socket接口提供服務,對下調用ip層,

面向連接,通信前要先進行3次握手建立連接關系,

tcp好比順豐,傳輸可靠,

tcp如何保證可靠傳輸,

接收方ack給發送方,若發送方未收到ack會重傳,

校驗碼,確保數據未損壞,

滑動窗口技術,來調節網絡適配速率,

給報文編號,若接收方收到的編號錯誤,就會重傳,

(3).

建立連接的條件;服務器listen時客戶端主動發起connect,

TCP的三次握手,雙方之間進行3次單向通信,

SYN,客戶端connect發送SYN,進入SYN-SEND狀態,

SYN+ACK,服務器收到SYN後,服務器端發送SYN-ACK,進入SYN-RCVD狀態,

ACK,客戶端進入establishd,發送ACK,服務器收到ACK後,服務器進入establisted,

上面這幾步驟,沒涉及錯誤的情況,

關閉連接需要4次揮手,

FIN,客戶端FIN,

ACK,服務器ACK,

FIN,服務器FIN,

ACK,客戶端ACK,

上面是客戶端主動關閉,也可以服務器主動關閉,

上面這些握手,揮手都封裝在TCP協議內部,和socket沒關系,

基於TCP通信的服務模式,

具有公網ip地址的服務器,公網ip地址有限,不是每個人都能有一個,那麼可以使用動態公網ip地址映射技術,

服務器端socket,blind,listen後處於監聽狀態,

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

然後雙方就可以建立tcp連接收發數據然後關閉連接,

使用tcp協議的應用,

http,ftp,qq服務器,mail服務器,

(4)

socket編程接口,

建立連接,socket,bind,listen/connect,

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

domain,網絡域,ipv4或者其它類型,

type,SOCK_STREAM指的是tcp連接,SOCK_DGRAM指的是udp連接,

protocol,給0使用默認協議,

返回值int,返回一個套接字,有點像文件描述符,

int bind( int socket, const struct sockaddr *address, socklen_t address_len);

把本地ip地址和我們的socket綁定起來,

socket是上一步得到的,address不區分ipv4和ipv6,address_len表示結構體的長度,

int listen(int socket, int backlog);

socket第一,二步的那個,

backlog指定同時監聽幾個,服務器會有個監聽隊列,

int connect(int socket, const struct sockaddr *address, socklen_t address_len);

address是要連接的服務器的ip地址,socket是之前打開的,

發送和接收,

ssize_t write( int fildes, const void *buf, size_t nbyte);

fildes其實就是socket,

ssize_t send( int socket, const void *buffer, size_t length, int flags );

flag,正常通信用不到,設為0,此時和write差不多,

ssize_t recv(....);

ssize_t read(....);

ip地址十進制形式,點分二進制形式,下面的函數就是負責兩種形式的轉換,

inet_aton,inet_ntoa,inet_addr,

inet_ntop,inet_pton,

n代表網絡net那端使用的二進制形式,p代表字符串也就是255.255.255.255這種形式,

上面3個不支持ipv6,功能差不多,

ip地址相關數據結構都在netinet/in.h,

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

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

struct in_addr

{

in_addr_t s_addr;

};

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

};

(5)

in_addr_t inet_addr( const char *cp );

把點分十進制格式轉換為二進制格式的ip地址,這個函數同時會轉換為大端模式,

cp = 192.168.1.102,得到0x6601a8c0,數值正確,順序不同,也就是大小端!

那怎麼辦呢?

於是統一規定了一個網絡字節序,其實大端模式,

大端可以這樣記憶,易於機器讀出,因為數據肯定是從低地址開始讀(比如0地址),而數字等是從高位開始辨識(千位肯定比各位先讀出來),

而小端可以這樣記憶,想當然得以為,高位數據放高地址,

int inet_pton(int AF, const char *src, void *dst);

const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);

(6).

端口號,區分進程,實質是一個數字編號,會包含在每一個發送的數據包中,

bind就是把當前的ip地址和端口號和socket綁定在一起,

int sockfd = 0;

struct sockaddr_in seraddr

sockfd = socket();

htonl,

htons,

h = host,n = net,

l代表4個字節,s代表兩個字節

寫一部分調試一部分,

(7),(8),(9)

要說的東西主要體現在代碼裡面,

以下代碼為朱老師純手工打造,

client.c

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

#define SERADDR		"192.168.1.141"		// 服務器開放給我們的IP地址和端口號
#define SERPORT		9003

char sendbuf[100];
char recvbuf[100];

#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;

int main(void)
{
	// 第1步:先socket打開文件描述符
	int sockfd = -1, ret = -1;
	struct sockaddr_in seraddr = {0};
	struct sockaddr_in cliaddr = {0};
	
	// 第1步:socket
	sockfd = socket(AF_INET, SOCK_STREAM, 0);
	if (-1 == sockfd)
	{
		perror("socket");
		return -1;
	}
	printf("socketfd = %d.\n", sockfd);
	
	// 第2步: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("listen");
		return -1;
	}
	printf("成功建立連接\n");

/*
	while (1)
	{
		// 回合中第1步:客戶端給服務器發送信息
		printf("請輸入要發送的內容\n");
		scanf("%s", sendbuf);
		//printf("剛才輸入的是:%s\n", sendbuf);
		ret = send(sockfd, sendbuf, strlen(sendbuf), 0);
		printf("發送了%d個字符\n", ret);
		
		// 回合中第2步:客戶端接收服務器的回復
		memset(recvbuf, 0, sizeof(recvbuf));
		ret = recv(sockfd, recvbuf, sizeof(recvbuf), 0);
		//printf("成功接收了%d個字節\n", ret);
		printf("client發送過來的內容是:%s\n", recvbuf);

		// 回合中第3步:客戶端解析服務器的回復,再做下一步定奪
		
	}
*/

	while (1)
	{
		// 回合中第1步:客戶端給服務器發送信息
		info st1;
		printf("請輸入學生姓名\n");
		scanf("%s", st1.name);
		printf("請輸入學生年齡");
		scanf("%d", &st1.age);
		st1.cmd = CMD_REGISTER;
		//printf("剛才輸入的是:%s\n", sendbuf);
		ret = send(sockfd, &st1, sizeof(info), 0);
		printf("發送了1個學生信息\n");
		
		// 回合中第2步:客戶端接收服務器的回復
		memset(&st1, 0, sizeof(st1));
		ret = recv(sockfd, &st1, sizeof(st1), 0);
		
		// 回合中第3步:客戶端解析服務器的回復,再做下一步定奪
		if (st1.stat == STAT_OK)
		{
			printf("注冊學生信息成功\n");
		}
		else
		{
			printf("注冊學生信息失敗\n");
		}

	}

	return 0;
}
server.c

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

#define SERPORT		9003
#define SERADDR		"192.168.1.141"		// ifconfig看到的
#define BACKLOG		100

char recvbuf[100];

#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;

int main(void)
{
	// 第1步:先socket打開文件描述符
	int sockfd = -1, ret = -1, clifd = -1;
	socklen_t len = 0;
	struct sockaddr_in seraddr = {0};
	struct sockaddr_in cliaddr = {0};
	
	char ipbuf[30] = {0};
	
	
	sockfd = socket(AF_INET, SOCK_STREAM, 0);
	if (-1 == sockfd)
	{
		perror("socket");
		return -1;
	}
	printf("socketfd = %d.\n", sockfd);
	
	// 第2步:bind綁定sockefd和當前電腦的ip地址&端口號
	seraddr.sin_family = AF_INET;		// 設置地址族為IPv4
	seraddr.sin_port = htons(SERPORT);	// 設置地址的端口號信息
	seraddr.sin_addr.s_addr = inet_addr(SERADDR);	// 設置IP地址
	ret = bind(sockfd, (const 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;
		// 回合中第1步:服務器收
		ret = recv(clifd, &st, sizeof(info), 0);

		// 回合中第2步:服務器解析客戶端數據包,然後干活,
		if (st.cmd == CMD_REGISTER)
		{
			printf("用戶要注冊學生信息\n");
			printf("學生姓名:%s,學生年齡:%d\n", st.name, st.age);
			// 在這裡服務器要進行真正的注冊動作,一般是插入數據庫一條信息
			
			// 回合中第3步:回復客戶端
			st.stat = STAT_OK;
			ret = send(clifd, &st, sizeof(info), 0);
		}
		
		if (st.cmd == CMD_CHECK)
		{
			
		}
		
		if (st.cmd == CMD_GETINFO)
		{
			
		}

	}

	return 0;
}
我們要自己去設計安排應用層,數據收發的規則,

http,ftp這些應用層協議,就是這樣來的,不過它們設計的很完善。

Copyright © Linux教程網 All Rights Reserved