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;
}

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM