【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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值