【Linux】Linux 學習筆記2015(網絡編程)

標籤(空格分隔): Linux

Author:atao

第一章 網絡基礎

一、網絡基礎概述

1.傳輸層:端到端,保存的是端口號
端口號(16位)是標示進程的。
較低端的端口號(0~1023)特定端口號一般不能使用。
2.TCP協議的三次握手
1)連接時需要三次握手
2)關閉時需要四次握手
其中:
SYN位:表示連接請求序號
ACK位:表示確認序號
FIN位:表示關閉連接的請求序號
3.報文結構
1)首部:主要是協議的一些控制信息,通常包含源地址、目的地址、分組順序號,讓網絡知道如何選擇路徑,保證報文能到達目的地。
2)數據:數據就是實際需要傳遞的信息。
3)尾部:一般包含協議規定的校驗信息,用於接收端檢查報文的正確性。

二、網絡工具

1.截包命令:tcpdump -c 4 -X
參數:
-c //表示截取幾個包
-X //表示查看截取包的內容
-i //表示要截取哪一個端口或者網卡的包
2.檢測端口和網絡連接情況
命令: netstat -apn

三、網絡模型

1.OSI(開放系統互連)參考模型
應用層:定義用戶接口協議,如http、ftp、pop3
表示層:控制數據加密、解密、壓縮、解壓
會話層:控制多方通信
傳輸層:控制數據包,可以通過重傳機制確保傳輸正確
網絡層:將數據包分組,實現分組重排
數據鏈路層:控制數據幀
物理層:控制電氣比特流
2.TCP/IP參考模型
應用層:涵蓋了OSI模型的5-7層
傳輸層:負責在網絡設備間傳輸數據,TCP、UDP
網絡層:負責網絡尋址(IP地址)、數據封裝、路由選擇、分片、錯誤處理和診斷。IP協議、ICMP協議、路由協議處於該層
網絡接口層:提供網絡層於物理層之間所需要的接口。

四、TCP/IP協議族

1、TCP協議:傳輸控制協議,該協議主要用於在主機間建立一個虛擬連接,以實現高可靠性的數據包交換。
2、IP協議:因特網協議,是互聯網應用層承載的基礎,規定了數據傳輸時的基本單元和格式。還定義了數據包的遞交辦法和路由選擇。

五、網絡拓撲結構

1、星型拓撲結構
2、環型拓撲結構
3.總線型拓撲結構

六、IP地址

1、32位IP地址:網絡號+主機號
A類:0 +7位網絡ID +24主機ID 第一字節爲:1~126
B類:10 +14位網絡ID +16位主機ID 第一字節爲:128~191
C類:110 +21位網絡ID +8 位主機ID 第一字節爲:192~223
2、保留和限制使用的地址
-主機號爲0的地址爲網絡地址
-主機號全爲1的地址爲廣播地址
-127開頭的地址爲環回地址
3、私有地址
10.0.0.0 ~ 10.255.255.255
172.16.0.0 ~ 172.31.255.255
192.168.0.0 ~ 192.168.255.255

七、常用協議格式

1.RFC 894以太網幀格式
目的地址(6B) + 源地址(6B) + 類型(2B) + 數據(46~1500B) + CRC(4B)
類型:
0800 //IP數據報
0806 //ARP請求/應答(ARP:地址轉換協議)
8035 //RARP請求/應答
2.ARP數據報的格式
目的地址+源地址+幀類型+硬件類型+協議類型+硬件地址長度+協議地址長度+發送端以太網地址+發送端IP地址+目的以太網地址+目的IP地址

第二章 SOCKET編程

一、字節序

大端->低地址存放高字節,高地址存放低字節
小端->低地址存放低字節,高地址存放高字節

二、注意

1、TCP/IP規定網絡字節序採用大端字節序
2、本機中編程時一般存放的是本機字節序

所以在編程是發包前,IP和port要將本機轉換爲網絡字節序

#include <arpa/inet.h>或<linux/in.h>(一般用這個)
uint32_t htonl(port);   //32字節的,本機轉網絡
uint32_t ntohl(port);   //32字節的,網絡轉本機
uint16_t htons(port);   //16字節的,本機轉網絡
uint16_t ntohs(port);   //16字節的,網絡轉本機
//*************************************
in_addr_t inet_addr(ip);//將本機IP轉網絡IP
char * inet_ntoa(ip);//將網絡IP轉本機IP
3、設置一個IPV4的地址需要設sockaddr_in的三個參數
struct sockaddr_in serveraddr = {0};
serveraddr.sin_family = PF_INET;//表示是IPV4地址族
serveraddr.sin_port = 6996;//表示設置的端口號
serveraddr.sin_addr.s_addr = "192.168.1.110";//設置IP地址
serveraddr.sin_addr.s_addr = INADDR_ANY;//表示接收任意IP地址
/*
其中,sockaddr_in表示是IPV4地址結構,定義如下:
struct sockaddr_in
{
    short int           sin_family;//地址族
    unsigned short int  sin_port;//端口號
    struct in_addr      sin_addr;//32位IP地址
    unsigned char       sin_zero[8];//填充0以保持同樣大小
}
typedef struct in_addr
{
    u_long s_addr;
}in_addr;
*/
4、查看本機器的域名路徑:/etc/hosts

三、TCP編程流程

1)服務端
1.新建一個socket通信套接字

    int sockfd;
    sockfd = socket(PF_INET, SOCK_STREAM, 0);
    /*
        PF_INET/AF_INET 表示是IPV4網絡協議
        PF_INET6/AF_INET6 表示是IPV6網絡協議
        PF_IPX/AF_IPX IPX-Novell協議
        PF_NETLINK/AF_NETLINK 核心用戶接口裝置
        PF_X25/AF_X25 ITU-T X.25/ISO-8208 協議
        PF_AX25/AF_AX25 業餘無線AX.25協議
        PF_ATMPVC/AF_ATMPVC 存取原始ATM PVCs 
        PF_APPLETALK/AF_APPLETALK appletalk(DDP)協議
        PF_PACKET/AF_PACKET 初級封包接口
    */

    2.設置地址可重用

int val = 1;//1->啓動可重用     0->取消可重用
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val));
/*
    • SOL_SOCKET: 基本套接口 
    • IPPROTO_IP: IPv4套接口 
    • IPPROTO_IPV6: IPv6套接口 
    • IPPROTO_TCP: TCP套接口 
*/

    3.設置IP地址和PORT端口號

    struct sockaddr_in serveraddr = {0};
    serveraddr.sin_family = PF_INET;//表示是IPV4地址族
    serveraddr.sin_port = 6996;//表示設置的端口號
    serveraddr.sin_addr.s_addr = INADDR_ANY;//表示接收任意IP地址

    4.綁定IP地址和PORT端口號

    bind(sockfd, (struct sockaddr *)&serveraddr, serverlen);

    5.監聽隊列

    listen(sockfd, BACKLOG);//設置監聽數量

    6.阻塞、等待客戶端的數據連接請求

confd = accept(sockfd, (struct sockaddr *)&clientaddr, &clientlen);

    7.接收客戶端的消息

    read(confd, buf_r, sizeof(buf_r));
    或:
    recv(confd, buf_r, sizeof(buf_r), 0);

    8.迴應客戶端的消息

    write(confd, buf_w, strlen(buf_w));
    或:
    send(confd, buf_w, strlen(buf_w), 0);

    9.關閉通信套接字

    if(0 < sockfd)
    {
        close(sockfd);
    }

2)客戶端
1.新建一個socket通信套接字

    int sockfd;
    sockfd = socket(PF_INET, SOCK_STREAM, 0);

    2.設置服務器的IP和端口號

    servaddr.sin_family = PF_INET;
    servaddr.sin_port = htons(SERVER_PORT);
	servaddr.sin_addr.s_addr = inet_addr(SERVER_IP); 

    3.向服務器發送連接請求

    connect(sockfd, (struct sockaddr *)&servaddr, addrlen);

    4.向服務器發送消息

    write(sockfd, buf_w, strlen(buf_w));
	printf("sent to server msg: %s\n", buf_w);
    或:
    send(sockfd, buf_w, strlen(buf_w), 0);

    5.接收服務器的迴應消息

    read(sockfd, buf_r, sizeof(buf_r));
    或:
    recv(sockfd, buf_r, sizeof(buf_r), 0);
    阻塞設置如下:
    recv(sockfd, buf_r, sizeof(buf_r), MSG_DONTWAIT);

    9.關閉通信套接字

    if(0 < sockfd)
    {
        close(sockfd);
    }

四、UDP編程流程

1)服務端
1.新建一個socket通信套接字

    int sockfd;
    sockfd = socket(PF_INET, SOCK_DGRAM, 0);
2.設置地址可重用
int val = 1;//1->啓動可重用     0->取消可重用
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val));
3.設置IP地址和PORT端口號
    struct sockaddr_in serveraddr = {0};
    serveraddr.sin_family = PF_INET;//表示是IPV4地址族
    serveraddr.sin_port = 6996;//表示設置的端口號
    serveraddr.sin_addr.s_addr = INADDR_ANY;//表示接收任意IP地址
4.綁定IP地址和PORT端口號
    bind(sockfd, (struct sockaddr *)&serveraddr, serverlen);
5.阻塞,等待客戶,並接收消息
    recvfrom(sockfd, buf_r, sizeof(buf_r), 0, \
		(struct sockaddr *)&cliaddr, &cliaddrlen);
6.迴應客戶端的消息
	sendto(sockfd, buf_w, strlen(buf_w), 0, \
		(struct sockaddr *)&cliaddr, cliaddrlen);
7.關閉通信套接字
    if(0 < sockfd)
    {
        close(sockfd);
    }

2)客戶端
1.新建一個socket通信套接字

    int sockfd;
    sockfd = socket(PF_INET, SOCK_DGRAM, 0);
2.設置服務器的IP和端口號
    servaddr.sin_family = PF_INET;
    servaddr.sin_port = htons(SERVER_PORT);
	servaddr.sin_addr.s_addr = inet_addr(SERVER_IP); 
3.向服務器發送信息
	sendto(sockfd, buf_w, strlen(buf_w), 0, \
			(struct sockaddr *)&servaddr, addrlen);
4.循環接收服務端的迴應消息
	// 確認是服務器發來的消息
	while(1)
	{
		// 接收服務器的迴應
	recvfrom(sockfd, buf_r, sizeof(buf_r), 0, \
				(struct sockaddr *)&tmpaddr, &tmplen);
	printf("recieved from server msg: %s\n", buf_r);
	if((servaddr.sin_family == tmpaddr.sin_family) && \
		(servaddr.sin_port == tmpaddr.sin_port) && \
		(servaddr.sin_addr.s_addr == tmpaddr.sin_addr.s_addr))\
		{
			printf("Yes, it is my server\n");
			break;
		}
	}
5.關閉通信套接字
    if(0 < sockfd)
    {
        close(sockfd);
    }

第三章 廣播與多播

一、廣播與多播概述

1.TCP只支持單播
2.UDP可以實現廣播與多播還有單播
3.多播是介於單播和多播之間的

二、特點

1)關於流量的節省
1.多播可以節省流量
2.單播的消息在流通過程中,其實已經到了每臺主機,只是沒向上提交
2)關於侷限性
1.單播應用於局域網和廣域網
2.廣播只能應用於局域網
3.多播應用於局域網和廣域網

三、廣播的概述

1.子網廣播地址
•主機號全爲1的IP地址爲子網廣播地址。
•向子網廣播地址發送的數據報,子網內的所有主機都能收到。
•子網廣播數據報不會被路由器轉發。
2.受限廣播地址
•255.255.255.255地址爲受限廣播地址。
•路由器不會轉發該地址的IP數據報。
•BOOTP和DHCP服務器就是利用這個地址爲發出IP地址廣播申請的主機分配IP地址 。
3.鏈路層廣播地址
•MAC地址全1的地址,即FF:FF:FF:FF:FF:FF。
•帶有這樣目的MAC地址的幀經過任何該子網上的主機時,都會被其鏈路層接收。
•ARP就是利用這個地址發出廣播來確定具有指定IP地址對應主機的MAC地址。

四、廣播實現(只需要更改客戶端,即發送端)

1.發送前, 要設置廣播選項(在創建通信套接字之後添加下列代碼)
int val=1; 
setsockopt(sockfd,SOL_SOCKET,SO_BROADCAST,val,sizeof(int)); 
2.設置發送地址爲廣播地址
    #define BROADCAST_IP “192.168.20.255”
    serveraddr.sin_addr.s_addr = inet_addr(BROADCAST_IP);

五、多播的概述

1.主機要接收多播數據必須預先加入多播組
2.進程通過把UDP套接字(SOCK_DGRAM類型)綁定到一個多播組IP地址
3.如果同一臺機器上有多個進程加入該組,則網絡接口會把每個消息複製給所有這些進程。
4.多播地址
1)多播是通過D類地址進行的
2)D類地址的前4位是1110,後面28位是多播的組標識
3)多播地址範圍是224.0.0.0 ~ 239.255.255.255
– 224.0.0.1爲全主機組,支持多播的主機必須加入全主機組。
– 224.0.0.2爲全路由組,支持多播的由器必須加入全路由組。
4)多播組按照多播範圍被分爲四類
– 鏈路-本地多播地址:224.0.0.0 ~ 224.0.0.255
這些地址是給那些在網絡拓撲的最底層相連的機器的。
多播路由器不會轉發這些地址的多播消息。
– 全局多播地址:224.0.1.0 ~ 238.255.255.255
該地址範圍內的消息應該被所有多播路由器傳播。
– 管理範圍內的多播地址:239.0.0.0 ~ 239.255.255.255
這些地址用在專門組織內部,並且不應該被傳遞到組織範圍之外。

六、多播的實現(需要更改服務端,即接收端)

1.多播選項(用setsockopt函數設置第三個參數)
    IP_ADD_MEMBERSHIP   //加入一個多播組
    IP_DROP_MEMBERSHIP  //離開一個多播組
2.加入多播組(在綁定IP之後之後添加下列代碼)
//加入多播組struct ip_mreq mltaddr = {0};
    mtladdr.imr_multiaddr.s_addr = inet_addr("224.0.1.1");
    //可以選擇任何網口加入多播,但虛擬機網卡不支持多播
    mtladdr.imr_interface.s_addr = htonl(INADDR_ANY);
    // mtladdr.imr_interface.s_addr = inet_addr( argv[1] );
	// mtladdr.imr_interface.s_addr = inet_addr("192.168.1.86"); 
    setsockopt(udpfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, \
    &mltaddr, sizeof( mtladdr));
3.離開多播組
    setsockopt(udpfd, IPPROTO_IP, IP_DROP_MEMBERSHIP, \
    &mltaddr, sizeof( mtladdr));

七、通訊架構

1、多數TCP服務器都是併發服務器
2、多數UDP服務器都是串行的
3、TCP服務器模型
1)TCP串行服務器
    while(1)
    {
        //1.接受客戶連接
        connfd = accept();
        
        //2.處理客戶請求
        
        //3.處理完畢後關閉connfd
        close(connfd);
    }
2)TCP併發線程化服務器
    while(1)
    {
        //接收客戶連接
        connfd = accept();
        //創建client子線程處理客戶請求,子線程處理完close(connfd);
        pthread_create(..., (void *)cnnfd);
    }
3)TCP併發fork服務器
    //(在定義後)安裝子進程結束信號sigchild處理函數,回收殭屍進程
	signal(SIGCHLD, sig_handler);
	/*
	    SIGCHLD //表示在一個進程終止或者停止時,將信號返回給父進程
	    waitpid();   
	    //大於0,表示正常返回子進程的進程ID
	    //等於0,表示沒有收集到子進程
	    //小於0,表示錯誤返回
	*/
    void sigchld_handler(int sig)
    {
        while(waitpid(-1, NULL, WNOHANG) > 0)
        {
            printf("child %d over!\n", sig);
        }
    }
//主進程循環
    while(1)
    {
        connfd = accept();//接受客戶端連接
        child = fork();//創建子進程
        if(0 == child)  //是子進程
        {
            close(sockfd);//關閉
            //處理客戶端消息請求的函數
            close(connfd);//客戶請求處理完畢後關閉連接套接字
            exit(0);//子進程退出
        }
        else if(0 < child)  //是父進程
        {
            close(connfd);
        }
    }
4)UDP串行服務器
    uin_open_udp(sock, port);
    while(1)
    {
        recvfrom();//接收客戶端的消息請求
        //處理客戶端消息
        sendto();//返回處理結果
        sleep(1);
    }

八、windows下socket庫的使用

1.必須包含Winsock2.h頭文件
2.必須鏈接ws2_32.lib庫

設置方法:項目->屬性->鏈接器->輸入->附加依賴項->輸入ws2_32.lib;
3.不同之處
1)創建通信套接字時採用SOCKET類型,而不是int
2)創建socket出錯返回值是:INVALID_SOCKET
3)操作socket錯誤返回SOCKET_ERROR
4)關閉socket用closesocket(sockfd);
5)windows下用recv、send進行TCP的收發
6)windows沒有socklen_t類型,直接採用int
7)在使用任何一個socket函數前,必須要初始化

    //初始化SOCKET庫
	INIT_SOCK();
    SOCKET sockfd;
    sockfd = socket(PF_INET, SOCK_STREAM, 0);
    if(INVALID_SOCKET == sockfd)
    {
        perror("socket failed:");
        return -1;
    }
4.實例:
/*///////////////頭文件部分////////////*/
#ifndef		_HEADER_H_
#define		_HEADER_H_

#include <stdio.h>

#ifndef NDEBUG
#define PTrace printf
#else
#define PTrace
#endif

#ifdef _WIN32
#include <Winsock2.h>
#include <process.h>	//用於windowns下創建線程
#include <errno.h>

//聲明創建線程的函數
void thread_write(void *arg);

#if 0
typedef struct WSAData {
	WORD                    wVersion;
	WORD                    wHighVersion;
	char                    szDescription[WSADESCRIPTION_LEN + 1];
	char                    szSystemStatus[WSASYS_STATUS_LEN + 1];
	unsigned short          iMaxSockets;
	unsigned short          iMaxUdpDg;
	char FAR *              lpVendorInfo;
} WSADATA, FAR * LPWSADATA;
#define MAKEWORD(low,high) \
	((WORD)((BYTE)(low)) | (((WORD)(BYTE)(high)) << 8)))
#endif

#define INIT_SOCK() \
do{\
	int nRet; \
	WSADATA wsaData; \
	WORD wVersionRequested = MAKEWORD(1, 1); \
	nRet = WSAStartup(wVersionRequested, &wsaData); \
	if (0 != nRet)\
	{ \
	printf("Can not find a usable WinSock dll\n"); \
	return -1; \
	} \
	else if (wsaData.wVersion != wVersionRequested) \
	{\
	printf("Wrong version\n"); \
	return -1; \
	} \
} while (0)

#define RELEASE_SOCK() WSACleanup()
#define CLOSE_SOCK	closesocket

typedef int socklen_t;

#else		// LINUX
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define INIT_SOCK()
#define RELEASE_SOCK()
#define INVALID_SOCKET -1
#define SOCKET_ERROR -1
#define CLOSE_SOCK	close

typedef int SOCKET;

#endif		//end ifdef _WIN32

#endif		// _SOCK_WIN_H
/*///////////////主函數部分////////////*/
#include "header.h"

#define SERVER_PORT 6996
#define SERVER_IP "192.168.1.110"

//創建一個線程
void thread_write(void *arg)
{
	char buf_r[128] = { 0 };
	int cnt;

	//分離線程

	while (1)
	{
		while (0 == (cnt = recv(*(SOCKET*)(arg), buf_r, sizeof(buf_r), 0)));
		Sleep(1000);
		printf("recieved form server msg:%s\n", buf_r);
	}
}

int main(int argc, char *argv[])
{
	char buf_w[128] = {0};
	//char buf_r[128] = { '\0' };
	int cnt = 0;
	int errnum = -1;
	SOCKET fd = 0;
	struct sockaddr_in servaddr = { 0 };
	socklen_t servlen = sizeof(struct sockaddr_in);
	HANDLE hth1;//用於回收線程

	//初始化SOCKET庫
	INIT_SOCK();

	//創建客戶端套接字
	fd = socket(PF_INET, SOCK_STREAM, 0);
	if (INVALID_SOCKET == fd)
	{
		perror("socket failed:");
		return -1;
	}

	//設置服務器地址
	servaddr.sin_family = PF_INET;
	servaddr.sin_port = htons(SERVER_PORT);
	servaddr.sin_addr.s_addr = inet_addr(SERVER_IP);

	//向服務器請求連接
	cnt = connect(fd, (struct sockaddr *)&servaddr, servlen);
	if (SOCKET_ERROR == cnt)
	{
		perror("connect failed:");
		goto _out;
	}

	printf("已與服務器建立連接...\n");
	//創建一個線程
	hth1 = (HANDLE)_beginthread(thread_write, 0, (void *)&fd);

	errnum = errno;
	if (EINTR == errnum)
	{
		printf("已與服務器斷開連接...\n");
		CloseHandle(hth1);//釋放線程
		goto _out;
	}

	//向服務器發送信息
	while (1)
	{
		gets_s(buf_w, sizeof(buf_w));
		printf("sent to server msg:%s\n", buf_w);
		if (SOCKET_ERROR == send(fd, buf_w, strlen(buf_w), 0))
		{
			perror("send falied:");
			goto _out;
		}
	}

_out:
	if (INVALID_SOCKET != fd)
	{
		//關閉socket
		closesocket(fd);
	}
	while (1);
	return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章