標籤(空格分隔): 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;
}