zoukankan      html  css  js  c++  java
  • Linux 高级Socket编程

    设置套接字函数:

    #include<sys/socket.h>
    
    int setsockopt(int sockfd, int level, int optname, const void* optval, socklen_t* optlen);
    
    //sockfd要设置的目的套接字
    //level套接字的控制层次
    //optname optval optlen是三个相关的参数,通过不同的搭配可以设置不同的功能

    应用:

    1.数据收发时限设置

    struct timeva timeout;
    timeout.tv_sec=5;
    timeout.tv_usec=0;
    
    //接受时限
    setsockopt(serversocket, SQL_SOCKET,SO_RCVTIMEO, (char*)&timeout,sizeof(timeout));
    
    //发送时限
    setsockopt(serversocket, SQL_SOCKET,SO_SNDTIMEO, (char*)&timeout,sizeof(timeout));

    2.修改收发缓冲区

    //接收缓冲区
    int opt=1024*1024;
    setsockopt(serversocket, SQL_SOCKET, SO_RCVBUF, (const char*)&opt,sizeof(opt));
    
    
    //发送缓冲区
    setsockopt(serversocket, SQL_SOCKET, SO_SNDBUF, (const char*)&opt,sizeof(opt));

    3.广播设置

    int bBroadcast=1;
    setsockopt(seversocket, SQL_SOCKET, SO_BROADCAST,(cosnt char*)&bBroadcast,sizeof(bBroadcast));

    4.直接数据复制

      为了提升系统性能,在发送或接受数据时,可以主动设置数据不经历由缓冲区到套接字缓存区的拷贝。

    int opt=0;
    
    setsockopt(serversocket, SQL_SOCKET,SO_SNDBUF,(char*)&opt,sizeof(opt));
    
    setsockopt(serversocket, SQL_SOCKET,SO_RCVBUF,(char*)&opt,sizeof(opt));

    Select技术:

    #include <stdio.h>
    #include <sys/socket.h>
    #include <unistd.h>
    #include <stdlib.h>
    #include <sys/types.h>
    #include <errno.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include <string.h>
    
    #define SERVER_PORT 5555
    #define QUEUE_LENGTH 5
    #define BUF_SIZE 200
    
    int main(int argc, char **argv)
    {
        int server_socket,new_socket;
        struct sockaddr_in server_addr,client_addr;
        socklen_t sin_size;
        int client_socket[QUEUE_LENGTH];
        int conn_num;
        int yes=1;
        char buf[BUF_SIZE];
        int ret;
        int i;
        //创建套接字
        if((server_socket=socket(AF_INET,SOCK_STREAM,0))<0){
            perror("Socket");
            return 0;
        }
        //设置为可重复使用
        if(setsockopt(server_socket,SOL_SOCKET,SO_REUSEADDR,&yes,sizeof(int))==-1){
            perror("setsockopt");
            return 0;
        }
        //设置服务器地址信息设置
        server_addr.sin_family=AF_INET;                    //TCP
        server_addr.sin_port=htons(SERVER_PORT);
        server_addr.sin_addr.s_addr=INADDR_ANY;            //本地IP地址
        
        memset(server_addr.sin_zero,'\0',sizeof(server_addr.sin_zero));
        
        //绑定套接字与地址信息
        if(bind(server_socket,(struct sockaddr*)&server_addr,sizeof(server_addr))==-1){
            perror("setsockopt");
            return 0;
        }
        
        //侦听
        if(listen(server_socket,5)==-1){
            perror("setsockopt");
            return 0;
        }
        
        printf("listen port : %d\n",SERVER_PORT);
        
        fd_set clientfdset;
        int maxsock;
        struct timeval tv;
        
        conn_num=0;
        sin_size=sizeof(client_addr);
        maxsock=server_socket;
        
        while(1){
            //初始化,清空并添加服务器套接字到集合
            FD_ZERO(&clientfdset);
            FD_SET(server_socket,&clientfdset);
            
            //设置超时时间
            tv.tv_sec=15;
            tv.tv_usec=0;
            
            //添加连接的客户端到集合
            for(i=0;i<QUEUE_LENGTH;i++){
                if(client_socket[i]!=0)
                    FD_SET(client_socket[i],&clientfdset);
            }
            //select模式
            ret=select(maxsock+1,&clientfdset,NULL,NULL,&tv);
            if(ret<0){
                perror("select");
                break;
            }
            else if(ret==0){
                printf("waiting timeout\n");
                continue;
            }
            
            //检查集合内是否已经存在
            for(i=0;i<conn_num;i++){
                if(FD_ISSET(client_socket[i],&clientfdset)){
                    ret=recv(client_socket[i],buf,sizeof(buf),0);
                    
                    if(ret<=0){
                        printf("client[%d] close\n",i);
                        close(client_socket[i]);
                        FD_CLR(client_socket[i],&clientfdset);
                        client_socket[i]=0;
                    }
                    else{
                        printf("client[%d] msg: %s\n",i,buf);
                        send(client_socket[i],buf,sizeof(buf),0);
                    }
                }
            }
            
            if(FD_ISSET(server_socket,&clientfdset)){
                new_socket=accept(server_socket,(struct sockaddr*)&client_addr,&sin_size);
                if(new_socket<=0){
                    perror("accept");
                    continue;
                }
                if(conn_num<QUEUE_LENGTH){
                    client_socket[conn_num++]=new_socket;
                    printf("new client[%d] %s: %d\n",conn_num,inet_ntoa(client_addr.sin_addr),ntohs(client_addr.sin_port));
                    if(new_socket>maxsock)
                        maxsock=new_socket;
                }
                else{
                    send(new_socket,"sorry overload!",sizeof("sorry overload!"),0);
                    close(new_socket);
                    break;
                }
            }
        }
        
        for(i=0;i<QUEUE_LENGTH;i++){
            if(client_socket[i]!=0)
                close(client_socket[i]);
        }
        
    }

     原始套接字技术:

      原始套接字是一种套接字底层技术,它工作在网络层。利用原始套接字可以完成如下功能。

    •   设置网卡为混杂模式,嗅探当前网路流经本网卡的所有数据包。
    •   构造各种数据包(IP,ICMP,TCP,UDP等),并进行发送。
    •   进行新协议的验证。 

      原始套接字可用于木马中的通信模块,伪造IP地址,拒绝服务攻击,数据包嗅探。

    原始套接字的创建:

    int rawsock=socket(AF_INET, SOCK_RAW, htons(ETH_P_IP));
    //可以获取IP层的所有数据报文
    
    htons参数的可选值及其意义
    协议码 协议名
    IPPROTO_ICMP ICMP协议
    ETH_P_IP IP协议
    IPPROTO_TCP TCP协议
    IPPROTO_UDP UDP协议
    IPPROTO_IPV6 IPv6协议
    IPPROTO_EGP EGP协议
     

    数据发送:

      在原始套接字中,执行数据发送前要条用setsocketopt函数进行套接字的首部设定:

    int opt;
    setsockopt(sockfd,IPPROTO_IP, IP_HDRINCL, &opt, sizeof(opt));

    例子:

    //利用原始套接字实现一个简单的采集网络数据包,并进行反向解析IP,MAC地址
    #include <stdio.h> #include <sys/socket.h> #include <unistd.h> #include <sys/types.h> #include <linux/if_ether.h> #include <linux/in.h> #define BUFFER_MAX 2048 int main(int argc, char **argv) { int rawsock; char buffer[BUFFER_MAX]; char *ethhead; char *iphead; char *phead; //创建原始套接字 if((rawsock=socket(PF_PACKET,SOCK_RAW,htons(ETH_P_IP)))<0){ printf("error:create raw socket!\n"); exit(0); } long framecount =0; while(1){ int readnum = recvfrom(rawsock,buffer,2048,0,NULL,NULL); if(readnum<42){ printf("error:header is incomplete!\n"); exit(0); } ethhead=(char*)buffer; phead=ethhead; int ethernetmask=0XFF; framecount++; printf("---------------AnalysisiPacket[%d]---------------\n",framecount); printf("MAC:"); int i=6; for(;i<=11;i++) printf("%.2X:",phead[i]&ethernetmask); printf("------->"); for(i=0;i<=5;i++) printf("%.2X:",phead[i]&ethernetmask); printf("\n"); iphead=ethhead+14; phead=iphead+12; printf("IP:"); for(i=0;i<=3;i++){ printf("%d",phead[i]&ethernetmask); if(i!=3) printf("."); } printf("------->"); for(i=4;i<=7;i++){ printf("%d",phead[i]&ethernetmask); if(i!=7) printf("."); } printf("\n"); int prototype=(iphead+9)[0]; phead=iphead+20; printf("Protocol:"); switch(prototype){ case IPPROTO_ICMP: printf("ICMP\n"); break; case IPPROTO_IGMP: printf("IGMP\n"); break; case IPPROTO_IPIP: printf("IP"); break; case IPPROTO_TCP: printf("TCP|source port: %u |",(phead[0]<<8)&0XFF00|phead[1]&0XFF); printf("destport: %u\n",(phead[2]<<8)&0XFF00|phead[3]&0XFF); break; case IPPROTO_UDP: printf("UDP|source port: %u |",(phead[0]<<8)&0XFF00|phead[1]&0XFF); printf("destport: %u\n",(phead[2]<<8)&0XFF00|phead[3]&0XFF); break; case IPPROTO_RAW: printf("RAW\n"); break; default: printf("Unkown\n"); } printf("-----------------end--------------------"); } return 0; }

    广播技术:

      ARP(Address Resolution Protocol)和NTP(Network Time Protocol)都属于广播通信。

        ARP是局域网中的地址解析协议,利用这个协议,可以找出IP地址到MAC地址的映射关系。当主机A准备与主机B通信时,如果只知道主机B的IP地址,则主机A向整个全网发送一个ARP请求,询问IP地址为XXXX的主机,如果主机B收到就会产生回应。

      NTP是网络时间协议。在支持广播的局域网中设置NTP协议,可以使NTP服务器每隔一个固定的时间间隔,就向全网发送时间信息,客户端在收到时间信息后进行更新处理。

    原理解析:

      要进行广播通信,首先要理解广播地址。在IP地址中,如果最后一个数字是255,则一定是一个广播地址。

    • 网络广播地址:网络广播地址在没有进行子网划分的网络内广播,由于当强的网络均涉及子网划分,故此种地址很少存在
    • 受限广播地址:以255.255.255.255组成的广播地址,在当前路由器均不转发此类广播
    • 子网广播地址:子网广播地址是一种常用的广播方式,它是指在一个具体的子网内进行广播,比如192.168是网络ID,那么192.168.1.255就是子网192.168.1的广播
    • 全部子网广播地址:是指所有子网络的广播,以上一个为例,全部子网广播地址是192.168.255.255 

    广播要采用UDP的方式,具体流程如下:

    1. 创建UDP套接字
    2. 设置套接字属性为SO_BROADCAST,设置为广播地址
    3. 设置广播地址为INADDR_BROADCAST,同时也要指定发送端口
    4. 进行数据收发操作

    例子:

    //bserver.c
    #include <sys/types.h>
    #include <stdio.h>
    #include <sys/socket.h>
    #include <stdlib.h>
    #include <string.h>
    #include <netdb.h>
    #include <errno.h>
    
    #define BUFFSIZE 200
    #define PORT 5050
    
    int main(int argc, char **argv)
    {
        int serversocket;
        struct sockaddr_in serveraddress,clientaddress;
        
        int so_broadcast=1;
    
        
        if((serversocket=socket(AF_INET,SOCK_DGRAM,0))<0){
            perror("socket");
            return 0;
        }
        
        if(setsockopt(serversocket,SOL_SOCKET,SO_BROADCAST,&so_broadcast,sizeof(so_broadcast))<0){
            perror("setsockopt");
            return 0;
        }
        
        serveraddress.sin_family=AF_INET;
        serveraddress.sin_port=htons(INADDR_ANY);
        serveraddress.sin_addr.s_addr=htonl(INADDR_BROADCAST);
        
        if(bind(serversocket,(struct sockaddr*)&serveraddress,sizeof(struct sockaddr))<0){
            perror("bind");
            return 0;
        }
        
        clientaddress.sin_family=AF_INET;
        clientaddress.sin_port=htons(PORT);
        clientaddress.sin_addr.s_addr=htonl(INADDR_BROADCAST);
        
        while(1){
            char buf[BUFFSIZE];
            printf("please input your word:");
            scanf("%s",buf);
            if(sendto(serversocket,buf,strlen(buf),0,(struct sockaddr*)&clientaddress,sizeof(clientaddress))<0){
                perror("sendto");
                return 0;
            }
            else
                printf("send msg: %s\n",buf);
        }
        
        return 0;
    }
    //bclient.c
    #include <sys/types.h>
    #include <stdio.h>
    #include <sys/socket.h>
    #include <stdlib.h>
    #include <string.h>
    #include <netdb.h>
    #include <errno.h>
    
    int main(int argc, char **argv)
    {
        int clientsocket;
        struct sockaddr_in serveraddress,clientaddress;
        
        clientsocket=socket(AF_INET,SOCK_DGRAM,0);
        
        serveraddress.sin_family=AF_INET;
        serveraddress.sin_port=htons(5050);
        serveraddress.sin_addr.s_addr=htonl(INADDR_ANY);
        
        int opt=1;
        if(setsockopt(clientsocket,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt))<0){
            perror("setsockopt");
            return 0;
        }
        
        if(bind(clientsocket,(struct sockaddr*)&serveraddress,sizeof(struct sockaddr))!=0){
            perror("bind");
            return 0;
        }
        
        char buf[200];
        
        while(1){
            memset(buf,0,200);
            int size=0;
            size=recvfrom(clientsocket,buf,200,0,(struct sockaddr*)&serveraddress,sizeof(serveraddress));
            buf[size]='\0';
            printf("IP:%s msg:%s\n",inet_ntoa(clientaddress.sin_addr),buf);
            
            if(strcmp(buf,"quit")==0){
                printf("system quit!\n");
                close(clientsocket);
                return 0;
            }
        }
        
        return 0;
    }

    组播技术:

      组播可以实现小范围内的互联,在发送者和每一个接受者之间时间点对多点的网络连接,是广播通信的一种变种。

      根据IP地址的规定,D类地址为组播地址,其网络号为固定的1110,第4到31位定义了某一特殊的组播地址,范围为244.0.0.0~239.255.255.255。其中244.0.0.0~244.0.0.255的地址,它们大多是为了特殊的目的保留的,不建议使用。

    套接字的基本属性:组播参数对应5个参数,通过setsockopt设置

    //加入组播
    int setsockopt(client_socket,IPPROTO_IP,IP_ADD_MEMBERSHIP,&multiaddress,sizeof(multiaddress))
    
    //退出组播
    int setsockopt(client_socket,IPPROTO_IP,IP_DROP_MEMBERSHIP,&multiaddress,sizeof(multiaddress))
    
    //这里有一个重要的参数multiaddress,结构:
    struct ip_mreq{
        struct in_addr imr_multiaddr;        //组播地址
        struct in_addr imr_interface;          //IPv4地址
    }

    主要流程:

    1. 服务器端设置一个多播地址,创建一个多播组。
    2. 客户端指定多播地址,加入多播。
    3. 程序结束后,退出多播。

    例子:

    //memberServer.c
    
    #include <stdio.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include <netdb.h>
    #include <unistd.h>
    #include <stdlib.h>
    #include <string.h>
    
    int main(int argc, char **argv)
    {
        int server_socket;
        struct sockaddr_in address;
        
        //创建UDP
        server_socket=socket(AF_INET,SOCK_DGRAM,0);
        if(server_socket<0){
            perror("socket");
            return 0;
        }
        
        //初始化多播地址
        memset(&address,0,sizeof(address));
        address.sin_family=AF_INET;
        address.sin_port=htons(5555);
        address.sin_addr.s_addr=inet_addr("224.0.1.100");
        
        //发送信息
        while(1){
            char buf[200];
            printf("input your word:");
            scanf("%s",buf);
            if(sendto(server_socket,buf,sizeof(buf),0,(struct sockaddr*)&address,sizeof(address))<0){
                perror("sendto");
                return 0;
            }
        }
        
        return 0;
    }
    //memberClient.c
    
    #include <stdio.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include <netdb.h>
    #include <unistd.h>
    #include <stdlib.h>
    #include <string.h>
    
    int main(int argc, char **argv)
    {
        struct ip_mreq mreq;
        int serveraddress_len;
        int client_socket;
        struct sockaddr_in serveraddress;
        
        //初始化地址
        memset(&serveraddress,0,sizeof(serveraddress));
        serveraddress.sin_family=AF_INET;
        serveraddress.sin_port=htons(5555);
        serveraddress.sin_addr.s_addr=htonl(INADDR_ANY);
        
        if((client_socket=socket(AF_INET,SOCK_DGRAM,0))<0){
            perror("client");
            return 0;
        }
        
        //绑定SOCKET
        if(bind(client_socket,(struct sockaddr*)&serveraddress,sizeof(serveraddress))<0){
            printf("bind");
            return 0;
        }
        
        int opt=1;
        if(setsockopt(client_socket,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt))<0){
            printf("setsockopt1");
            return 0;
        }
        
        //加入多播
        mreq.imr_multiaddr.s_addr=inet_addr("244.0.1.100");
        mreq.imr_interface.s_addr=htonl(INADDR_ANY);
        
        if(setsockopt(client_socket,IPPROTO_IP,IP_ADD_MEMBERSHIP,&mreq,sizeof(mreq))<0){
            perror("setsockopt2");
            return 0;
        }
        
        while(1){
            char buf[200];
            serveraddress_len=sizeof(serveraddress);
            if(recvfrom(client_socket,buf,200,0,(struct sockaddr*)&serveraddress,(socklen_t *)serveraddress_len)<0){
                perror("recvfrom");
            }
            printf("msg from server: %s\n",buf);
            
            if(strcmp(buf,"quit")==0){
                if(setsockopt(client_socket,IPPROTO_IP,IP_DROP_MEMBERSHIP,&mreq,sizeof(mreq))<0){
                    perror("setsokopt3");
                }
                close(client_socket);
                return 0;
            }
        }
        
        return 0;
    }
  • 相关阅读:
    儿子和女儿——解释器和编译器的区别与联系
    求eclipse中的java build path 详解
    求eclipse中的java build path 详解
    System.Activator类
    htmlagilitypack解析html
    KindleEditor insertfile初始化多个
    按住ALT键复制
    隐藏行错误排查
    列类型: 202错误
    C#中的&运算
  • 原文地址:https://www.cnblogs.com/coder2012/p/2995889.html
Copyright © 2011-2022 走看看