天天看點

Linux下網絡程式設計-UDP協定探測線上好友

1. UDP協定介紹

UDP協定 相對TCP協定來講屬于不可靠協定,UDP協定是廣播方式發送資料,沒有伺服器和用戶端的概念。

在Linux下使用socket建立UDP的套接字時,屬性要選擇資料報類型

SOCK_DGRAM

sockfd=socket(AF_INET,SOCK_DGRAM,0);           

2. UDP協定發送和接收資料的函數

2.1 recvfrom函數

UDP使用recvfrom()函數接收資料,他類似于标準的read(),但是在recvfrom()函數中要指明資料的目的位址。

#include <sys/types.h>  
#include <sys/socket.h>  
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr * from, size_t *addrlen);           

傳回值

成功傳回接收到資料的長度,負數失敗 

前三個參數等同于函數read()的前三個參數,flags參數是傳輸控制标志。最後兩個參數類似于accept的最後兩個參數(接收用戶端的IP位址)。

2.2 sendto函數

UDP使用sendto()函數發送資料,他類似于标準的write(),但是在sendto()函數中要指明目的位址。

#include <sys/types.h>  
#include <sys/socket.h>  
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr * to, int addrlen);           

成功傳回發送資料的長度,失敗傳回-1

前三個參數等同于函數read()的前三個參數,flags參數是傳輸控制标志。

參數to指明資料将發往的協定位址,他的大小由addrlen參數來指定。

2.3 設定套接字屬性

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
int getsockopt(int sockfd, int level, int optname,void *optval, socklen_t *optlen);
int setsockopt(int sockfd, int level, int optname,const void *optval, socklen_t optlen);           

setsockopt()函數用于任意類型、任意狀态套接口的設定選項值。盡管在不同協定層上存在選項,但本函數僅定義了最高的“套接口”層次上的選項。選項影響套接口的操作,諸如加急資料是否在普通資料流中接收,廣播資料是否可以從套接口發送等等。

參數

sockfd:辨別一個套接口的描述字。

level:選項定義的層次;目前僅支援SOL_SOCKET和IPPROTO_TCP層次。

optname:需設定的選項。

optval:指針,指向存放選項值的緩沖區。

optlen:optval緩沖區的長度。

UDP協定發送資料時,設定具有廣播特性: 預設情況下socket不支援廣播特性

char bBroadcast=1;
setsockopt(s,SOL_SOCKET,SO_BROADCAST,(const char*)&bBroadcast,sizeof(char));           

3. 案例: UDP協定資料收發

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <dirent.h>
#include <stdlib.h>
#include <pthread.h>
#include <semaphore.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <pthread.h>
#include <sys/select.h>
#include <sys/time.h>
#include <sys/epoll.h>
#include <poll.h>


#define SEND_MSG "1314520"   //發送的資料包
#define PORT 8888           //固定的端口号

int sockfd;
int main(int argc,char **argv)
{   
    if(argc!=2)
    {
        printf("./app <廣播位址>  目前程式固定的端口号是8888\n");
        return 0;
    }

    /*1. 建立socket套接字*/
    sockfd=socket(AF_INET,SOCK_DGRAM,0);

    //設定端口号的複用功能
    int on = 1;
    setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));

    /*2. 綁定端口号與IP位址*/
    struct sockaddr_in addr;
    addr.sin_family=AF_INET;
    addr.sin_port=htons(PORT); // 端口号0~65535
    addr.sin_addr.s_addr=INADDR_ANY;    //inet_addr("0.0.0.0"); //IP位址
    if(bind(sockfd,(const struct sockaddr *)&addr,sizeof(struct sockaddr))!=0)
    {
        printf("UDP伺服器:端口号綁定失敗.\n");
        return 0;
    }

    /*3. 接收資料*/
    unsigned char buff[1024+1];
    int cnt;
    struct sockaddr_in client_addr;
    socklen_t addrlen=sizeof(struct sockaddr_in);

    struct pollfd fds;
    fds.fd=sockfd;
    fds.events=POLLIN;
    while(1)
    {
        cnt=poll(&fds,1,1000);
        if(cnt>0)
        {
            cnt=recvfrom(sockfd,buff,1024,0,(struct sockaddr *)&client_addr,&addrlen);
            buff[cnt]='\0';
            
            //判斷是不是探測包資料
            if(strcmp(buff,SEND_MSG)==0)
            {
                printf("線上好友:%s,%d-->%s:%d\n",buff,cnt,inet_ntoa(client_addr.sin_addr),ntohs(client_addr.sin_port));
                cnt=sendto(sockfd,SEND_MSG,strlen(SEND_MSG),0,(const struct sockaddr *)&client_addr,sizeof(struct sockaddr));
                printf("回應探測包:%d位元組.\n",cnt);

                //這裡可以繼續寫代碼,将存在的好友儲存在連結清單,并記錄線上好友數量
            }
        }
        else
        {
            ssize_t cnt;
            struct sockaddr_in addr;
            addr.sin_family=AF_INET;
            addr.sin_port=htons(PORT); // 端口号0~65535
            addr.sin_addr.s_addr=inet_addr(argv[1]); //IP位址

            cnt=sendto(sockfd,SEND_MSG,strlen(SEND_MSG),0,(const struct sockaddr *)&addr,sizeof(struct sockaddr));
            printf("探測包發送:%d位元組.\n",cnt);
        }
    }
    return 0;
}           

4. 案例: 使用UDP協定探測線上好友

前面幾篇文章介紹了Linux下TCP協定設計的群聊天室的一個程式,如果想要知道同一個網絡下有多少好友線上,就可以使用UDP協定進行廣播探測。 大家的端口号是固定的,也就是隻要在這個網絡範圍内,大家都跑這個同一個聊天室程式,就可以互相探測,得到對方IP位址之後,再完成TCP協定建立,完成點對點聊天通信。

#include <stdio.h>    
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <arpa/inet.h>
#include <stdlib.h>
#include <pthread.h>
#include <sys/select.h>
#include <sys/time.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>
#include <libgen.h>
#include <sys/stat.h>
#include <time.h>
#include <errno.h>

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;//靜态初始化互斥鎖
#define CLIENT_COUNT 100 //伺服器可容納用戶端數量
#define SERVER_PORT 8080 //伺服器端口号

/*----------------------------連結清單相關函數----------------------------*/
/*---------------------存儲用戶端資訊和線程ID結構體-------------------*/
//存儲用戶端資訊和線程ID結構體
typedef struct node
{
    int nfds;            //用戶端套接字
    pthread_t phid;        //線程ID
    char name[50];    //使用者名
    struct node *next;
}node, *ptr_node;

//鍊隊列
typedef struct
{
    ptr_node head, tail;
}qlink;

/**********************
    連結清單初始化
**********************/
void qlink_Init(qlink *s)
{
    s->head = s->tail = (ptr_node)malloc(sizeof(node));
    if(s->tail == NULL)
    {
        printf("建立節點失敗!\n");
        exit(0);
    }
    s->head->next = NULL;
}
/****************************************
 添加節點
 參數:
        s --- 連結清單
        c --- 用戶端套接字
      phid --- 線程ID
*****************************************/
void qlink_Add(qlink *s, int c, pthread_t phid)
{
    pthread_mutex_lock(&mutex);  //互斥鎖上鎖(帶阻塞)
    ptr_node p;
    p = (ptr_node)malloc(sizeof(node));
    if (p == NULL)
    {
        printf("建立節點失敗[2]!\n");
        exit(0);
    }
    p->nfds = c;
    p->phid = phid;
    p->next = s->head->next;
    s->head->next = p;
    pthread_mutex_unlock(&mutex);//互斥鎖解鎖
}
/*--------------------------------END---------------------------------*/
/****************************全局變量定義******************************/
qlink s;            //連結清單
pthread_t phid;    //線程ID
int sockfd_UDP;            //UDP套接字
int Find_user = 0;        //找到使用者标志位
int f_dp;        //TCP用戶端套接字
int server_flag = 0;        //當成伺服器
int client_flag = 0;        //當成用戶端
/*---------------------------發送資料結構體---------------------------*/
typedef struct UDP_Test
{
    char name[50];//使用者名
    char buff[100];//發送資料,探測資料:UDP_TEST
    int flag;//1表示探測好友,2好友回複,3正常資料
}UDP_Test;
struct UDP_Test recv_msg;            //接收資訊結構體
struct UDP_Test send_msg;            //發送資訊結構體
/*--------------------------------END---------------------------------*/
/**********************************************************************/


/**********************************************************************/


/*****************************
    信号捕獲函數 
*****************************/
void signal_capture(int sig)
{
    if(sig==SIGSEGV)//段錯誤
    {
        time_t sec=time(NULL); //擷取系統秒機關時間    
        char buff[50];
        struct tm *time;
        time=localtime(&sec); //秒機關時間轉換時間結構體
        strftime(buff,sizeof(buff),"%Y/%m/%d %k:%M:%S\n",time);
        printf("段錯誤時間:%s\n",buff);
    }
    else
    {
        #if 0
        ptr_node p = s.head->next;
        while(p != NULL)
        {
            printf("已清理資源!p->nfds = %d\n", p->nfds);
            close(p->nfds);
            pthread_cancel(p->phid);//殺死線程
            p = p->next;
        }
        free(s.head);
        #endif
    }
    printf("伺服器資源清理完成\n");
    exit(0);
}
/*--------------------------------END---------------------------------*/
/****************************
    線程工作函數 
****************************/
void *start_routine(void *arg)
{
    #if 0
    int f_dp = *(int *)arg;
    memset(&send_msg, 0, sizeof(send_msg));
    char buff[100];
    while (1)
    {
        scanf("%s", buff);    
        strncpy(send_msg.name, "777" ,sizeof(send_msg.name));//使用者名 
        strncpy(send_msg.buff, buff, sizeof(buff));//探測消息内容
        
        printf("send_msg.buff = %s\n", send_msg.buff);
        write(f_dp, &send_msg, sizeof(send_msg));
        usleep(100);
    }
    #endif
    char buff[50];
    int cho = *(int *)arg;
    if(cho == 1)            //寫資料線程
    {
        memset(&send_msg, 0, sizeof(send_msg));
        while (1)
        {
            scanf("%s", buff);    
            //strncpy(send_msg.name, buff ,sizeof(send_msg.name));//使用者名
            strncpy(send_msg.buff, buff, sizeof(buff));//探測消息内容
            
            printf("send_msg.buff = %s\n", send_msg.buff);
            write(f_dp, &send_msg, sizeof(send_msg));
            usleep(100);
        }
        pthread_exit(NULL);
    }
    else                    //讀資料線程
    {
        int res, cnt;
        fd_set readfds;//讀事件
        struct timeval timeout;
        struct UDP_Test read_msg;
        while (2)
        {
            FD_ZERO(&readfds);
            FD_SET(f_dp, &readfds);
            timeout.tv_sec = 0;
            timeout.tv_usec = 0;
            res = select(f_dp + 1, &readfds, NULL, NULL, &timeout);
            if(res > 0)
            {
                cnt = read(f_dp, &read_msg, sizeof(read_msg));
                if(cnt > 0)
                {
                    printf("接收到資料: name:%s \t data:%s\n", read_msg.name, read_msg.buff);            //接收到了對方的使用者名
                    //break;
                }
                else
                {
                    printf("好友下線!\n");
                    break;
                }
            }
            else if(res < 0)
            {
                printf("錯誤!\n");
            }
            usleep(100);
        }
        pthread_exit(NULL);
    }
    
}
/*--------------------------------END---------------------------------*/
/***********************
 用于接收探測傳回資訊
***********************/
void *Receive_probe_retur(void *arg)
{
    memset(&recv_msg, 0, sizeof(recv_msg));
    fd_set readfds;//讀事件
    struct timeval timeout;
    int res, cnt;
    struct sockaddr_in src_addr;            //儲存其它使用者的ip位址和端口号
    socklen_t addrlen = sizeof(struct sockaddr_in);
    while(1)    //等待接收消息
    {
        FD_ZERO(&readfds);
        timeout.tv_sec=0;
        timeout.tv_usec=0;
        FD_SET(sockfd_UDP,&readfds);
        res = select(sockfd_UDP+1, &readfds, NULL, NULL, &timeout);
        if(res > 0)
        {
            cnt = recvfrom(sockfd_UDP, &recv_msg, sizeof(struct UDP_Test), 0, (struct sockaddr*)&src_addr, &addrlen);
            if(cnt > 0)
            {
                if(recv_msg.flag == 2)        //接收到傳回資訊
                {
                    printf("接收到%s使用者的确認資訊\n", recv_msg.name);
                    client_flag = 3;        //該使用者當作用戶端
                    Find_user = 1;            //标志搜尋到使用者,主線程不用再發探測資訊了
                    usleep(200);
                    break;
                }            
            }
        }
        else if(res < 0)
        {
            printf("錯誤!\n");
        }
    }
    pthread_exit(NULL);        //殺死線程
}

int main(int argc,char *argv[])
{
    
    if(argc!=2)
    {
        printf("./a.out <使用者名>\n");
        return 0;
    }
    
    signal(SIGPIPE,SIG_IGN);//忽略SIGPIPE信号
    signal(SIGINT, signal_capture);            //捕獲CTRL+c
    signal(SIGSEGV, signal_capture);            //捕獲段錯誤

    /* 初始化連結清單 */
    qlink_Init(&s);
    
    /*--------------------------通過UDP擷取線上使用者資訊--------------------------*/
    /*1.建立套接字*/
    sockfd_UDP = socket(AF_INET,SOCK_DGRAM,0);
    if(sockfd_UDP == -1)
    {
        printf("建立套接字失敗\n");
        return 0;
    }
    /*綁定端口号*/
    struct sockaddr_in addr;
    addr.sin_family=AF_INET;//IPV4
    addr.sin_port=htons(SERVER_PORT);//端口号
    addr.sin_addr.s_addr=INADDR_ANY;//本地所有IP(0.0.0.0)
    if(bind(sockfd_UDP,(struct sockaddr*)&addr,sizeof(struct sockaddr_in)))
    {
        printf("綁定端口号失敗\n");
        close(sockfd_UDP);
        return 0;
    }
    ssize_t cnt;
    /*擷取廣播位址*/
    //ifconfig -a |grep broadcast|awk '{print $6}'|tr -d broadcast: -- ubuntu下擷取本地廣播位址
    //ifconfig -a |grep Bcast|awk '{print $3}'|tr -d Bcast:  --red hat下擷取本地廣播位址
    FILE *fp = popen("ifconfig -a |grep broadcast|awk '{print $6}'|tr -d broadcast:", "r");
    char ip_addr[20];
    cnt = fread(ip_addr, 1, sizeof(ip_addr) - 1, fp);
    ip_addr[cnt]='\0';
    printf("廣播位址:%s\n",ip_addr);
    pclose(fp);
    addr.sin_addr.s_addr=inet_addr(ip_addr);//廣播IP位址
    //設定該套接字為廣播類型
    const int opt = 1;
    int nb = 0;
    nb = setsockopt(sockfd_UDP, SOL_SOCKET, SO_BROADCAST, (char *)&opt, sizeof(opt));
    if(nb == -1)
    {
        printf("設定廣播類型錯誤.\n");
        close(sockfd_UDP);
        return 0;
    }    

    /*-----------------------------接收探測資訊-----------------------------*/
    fd_set readfds;//讀事件
    struct timeval timeout;
    int res;
    struct sockaddr_in src_addr;            //儲存其它使用者的ip位址和端口号
    socklen_t addrlen = sizeof(struct sockaddr_in);
    
    int count = 0;
    memset(&recv_msg, 0, sizeof(recv_msg));
    memset(&send_msg, 0, sizeof(send_msg));
    /* 循環等待 5秒 探測資訊 */
    while(1)
    {
        FD_ZERO(&readfds);
        timeout.tv_sec=0;
        timeout.tv_usec=0;
        FD_SET(sockfd_UDP,&readfds);
        res = select(sockfd_UDP+1, &readfds, NULL, NULL, &timeout);
        if(res>0)
        {
            cnt = recvfrom(sockfd_UDP, &recv_msg, sizeof(struct UDP_Test), 0, (struct sockaddr*)&src_addr, &addrlen);
            if(cnt > 0)
            {
                if(recv_msg.flag == 1)        //接收到其它使用者發出的探測資訊,得到對方的IP位址
                {
                    printf("user addr:%s \t user port:%d\n", inet_ntoa(src_addr.sin_addr), ntohs(src_addr.sin_port));
                    printf("接收到資料: name:%s \t data:%s\n", recv_msg.name, recv_msg.buff);            //接收到了對方的使用者名
                    
                    /* 當接收到探測資訊後,傳回确認收到資訊給該使用者 */
                    strncpy(send_msg.name, argv[1], sizeof(send_msg.name));//使用者名
                    send_msg.flag = 2;//确認收到探測資訊标志
                    cnt = sendto(sockfd_UDP, &send_msg, sizeof(struct UDP_Test), 0, (const struct sockaddr *)&addr, sizeof(struct sockaddr_in));
                    printf("确認消息發送成功\n");
                    /*--------------------------------------------------------------*/
                    
                    printf("搜尋到線上使用者 %s\n", recv_msg.name);
                    client_flag = 3;        //接收到探測資訊,該使用者當作用戶端
                    //qlink_Add(&s, , pthread_t phid);
                    close(sockfd_UDP);
                    break;
                }            
            }
        }
        else if(res < 0)
        {
            printf("錯誤!\n");
        }
        usleep(100);
        count++;
        if(count >= 50000)        //若是5秒後還沒有接收到探測資訊,則跳出循環
        {
            count = 0;
            server_flag = 3;    //設定成伺服器
            break;
        }
    }
    /*----------------------------------END--------------------------------*/
/****************************************************用戶端***********************************************************/    
    if(client_flag == 3)        //标志在此次是用戶端
    {
        /*1.建立套接字*/
        int sockfd_c = socket(AF_INET, SOCK_STREAM, 0);
        if(sockfd_c == -1)
        {
            printf("建立網絡套接字失敗\n");
            return 0;
        }
        /*2.連接配接伺服器*/
        struct sockaddr_in t_addr;
        char buff[20];
        strcpy(buff, inet_ntoa(src_addr.sin_addr));
        int n = strlen(buff);
        buff[n] = '\0';
        printf("buff:%s\n", buff);
        t_addr.sin_family = AF_INET;//IPV4
        t_addr.sin_port = htons(8089);//端口号
        t_addr.sin_addr.s_addr = inet_addr(buff);//伺服器IP
    
        while(1)
        {
            printf("準備連接配接伺服器:connect addr:%s \t user port:%d\n", inet_ntoa(t_addr.sin_addr), ntohs(t_addr.sin_port));
            
            printf("sockfd_c:%d\n",sockfd_c);
            if(connect(sockfd_c, (const struct sockaddr *)&t_addr, sizeof(struct sockaddr_in)))
            {
                printf("連接配接失敗:%s,%d\n",strerror(errno),errno);
                sleep(2);
            }
            else
            {
                printf("伺服器連接配接成功\n");
                break;
            }
        }
        
        
        /*-----------------------将自己的使用者資訊發送過去----------------------*/
        memset(&send_msg, 0, sizeof(send_msg));
        strncpy(send_msg.name,argv[1],sizeof(send_msg.name));//使用者名
        write(sockfd_c, &send_msg, sizeof(send_msg));        //将使用者資訊發送過去
        /*----------------------------------END--------------------------------*/
        while(1);
        
    }
/****************************************************伺服器***********************************************************/
    else if(server_flag = 3)        //标志在此次是伺服器
    {
        memset(&send_msg, 0, sizeof(send_msg));
        pthread_t phid_1;
        pthread_create(&phid_1, NULL, Receive_probe_retur, NULL);        //建立線程 用于接收探測傳回資訊
        /************************************主線程循環發送探測消息******************************************/
        while(1)
        {
            strncpy(send_msg.name, argv[1], sizeof(send_msg.name));//使用者名
            //strncpy(send_msg.buff, "UDP_TEST", sizeof(send_msg.buff));//探測消息内容
            send_msg.flag = 1;//探測消息标志
            cnt = sendto(sockfd_UDP, &send_msg, sizeof(struct UDP_Test), 0, (const struct sockaddr *)&addr, sizeof(struct sockaddr_in));
            printf("探測消息發送成功\n");
            if(Find_user == 1)        //标志在子線程内找到了線上使用者
            {
                break;
            }
            sleep(1);
        }
        /*****************************開始建立TCP伺服器********************************/
        /* 建立套接字 */
        int sockfd_TCP = socket(AF_INET, SOCK_STREAM, 0);
        if(sockfd_TCP == -1)
        {
            printf("網絡套接字建立失敗\n");
            return 0;
        }
        /*允許綁定已使用的端口号*/
        int on = 1;/*  */
        setsockopt(sockfd_TCP, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
        
        /* 綁定端口号 */
        struct sockaddr_in s_addr;
        s_addr.sin_family = AF_INET;        //設定為IPV4
        s_addr.sin_port = htons(8089);//端口号
        s_addr.sin_addr.s_addr = INADDR_ANY;     //IP位址,該參數是讓系統随機配置設定
        if(bind(sockfd_TCP, (const struct sockaddr *)&s_addr, sizeof(s_addr)))
        {
            printf("綁定端口号失敗!\n");
            return 0;
        }
        //設定監聽數量
        listen(sockfd_TCP, 100);
        /* 等待用戶端連接配接 */
        int i;
        pthread_t phid[2];
        int *p;
        printf("已跳出循環\n");
        while(1)
        {
            struct sockaddr_in c_addr;    //用戶端屬性資訊
            socklen_t c_addr_len = sizeof(struct sockaddr_in);
            f_dp = accept(sockfd_TCP, (struct sockaddr *)&c_addr, &c_addr_len);
            printf("%d用戶端連接配接成功,ip=%s:%d\n",f_dp,inet_ntoa(c_addr.sin_addr),ntohs(c_addr.sin_port));
            read(f_dp, &recv_msg, sizeof(recv_msg));        //第一次讀取使用者資訊
            printf("user name = %s\n", recv_msg.name);
            for (i = 1; i <= 2; i++)
            {
                p = malloc(sizeof(int));
                *p = i;
                pthread_create(&phid[i - 1], NULL, start_routine, p);            //建立線程    -- i == 1是讀, i ==2寫
                pthread_detach(phid[i - 1]);//設定為分離屬性
            }        
        } 
    }
    /*----------------------------------END--------------------------------*/
    #if 0
    pthread_t phid;
    int *p = malloc(sizeof(int));
    *p = sockfd_c;
    pthread_create(&phid, NULL, start_routine, p);        //建立線程
    pthread_detach(phid);                    //設定分離屬性
    /*-------------------------------主線程讀------------------------------*/
    struct UDP_Test re_msg;    //存儲讀到的資料
    fd_set readfds_2;//讀事件
    while(1)
    {
        FD_ZERO(&readfds_2);//初始化讀事件
        FD_SET(sockfd_c,&readfds_2);//添加要監測的描述符到讀事件集合中
        timeout.tv_sec=0;
        timeout.tv_usec=0;
        //printf("使用者 \n");
        res = select(sockfd_c + 1, &readfds_2, NULL, NULL, &timeout);
        if(res > 0)    //當有資料的時候進行上鎖
        {
            memset(&re_msg, 0, sizeof(re_msg));
            pthread_mutex_lock(&mutex);  //互斥鎖上鎖(帶阻塞)
            int n = read(sockfd_c, &re_msg, sizeof(re_msg));        //讀取資料
            if(n <= 0)
            {
                printf("好友下線!\n");
                break;
            }
            else
            {
                printf("使用者 %s\t 消息 %s\n", re_msg.name, re_msg.buff);
            }
            pthread_mutex_unlock(&mutex);//互斥鎖解鎖
        }
        else if(res < 0)
        {
            printf("select函數出錯!\n");
            break;
        }
        usleep(100);
    }
    #endif
    /*----------------------------------END--------------------------------*/
    return 0;
}           
Linux下網絡程式設計-UDP協定探測線上好友

繼續閱讀