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