首页 > 代码库 > 第二十五、二十六天:基于UDP的网路聊天程序

第二十五、二十六天:基于UDP的网路聊天程序

    连续四天学习套接字的编程,可见套接字的重要性了。基于TCP和UDP分别写了两个程序。一是利用TCP实现一个服务器对多个客户端,客户端你发送信息,服务器就从事先准备好的五个字符串中随机回复一条。另一个是利用UDP实现两个人的对话,对话时可以是多个信息同时输入。

   先是第一个程序。要实现一对多,就要使用线程编程,服务器端在不断监听中,如果有连接请求的话,就用通过accept函数接受并创建一个线程来处理。线程的创建函数为int pthread_create(pthread_t * thread, pthread_attr_t * attr, void * (*start_routine)(void *), void * arg);这个函数是一个回调函数,会调用start_routine函数指针指向的函数进行处理,后面的arg为指向函数的参数。线程还有个重要的函数是 int pthread_join(pthread_t thread, void **retval);用来等待一个线程的结束。retval用来存放线程的返回值。下面是这个程序的源代码:   

 1 #include<stdio.h> 2 #include<stdlib.h> 3 #include<string.h> 4 #include<sys/socket.h> 5 #include<netinet/in.h> 6  7 void *func(void *arg){ 8  9     int ret = 0;10     int nfd = (int)arg;11     char revdata[1024] = {0};12     char *talk[5] = {0};13     *(talk + 0) = "hello world !";14     *(talk + 1) = "hello bunfly !";15     *(talk + 2) = "hello good  !";16     *(talk + 3) = "hello xixi !";17     *(talk + 4) = "hello lala !";18     int i = 0;19     while(1){20         ret = recv(nfd,revdata,1024,0);21         if(ret < 0){22             perror("recv");23             exit(EXIT_FAILURE);24         }25         ret = send(nfd,*(talk +i),strlen(*(talk+i)),0);26         if(ret < 0){27             perror("recv");28             exit(EXIT_FAILURE);29         }30         printf("ret is %d\n",ret);31         i = rand() % 4;32     }    33     close(nfd);34 }35 int main()36 {37     /*创建套接口*/38     int  fd = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);39     if(fd < 0){40         perror("socket");41         exit(EXIT_FAILURE);42     }43     /*服务端信息*/44     struct sockaddr_in srv;45     srv.sin_family = AF_INET;46     srv.sin_port=htons(9527);47     srv.sin_addr.s_addr = inet_addr("192.168.31.200");48     /*绑定*/49     int ret = bind(fd,(struct sockaddr *)&srv,sizeof(struct sockaddr ));50     if(ret < 0){51         perror("bind");52         exit(EXIT_FAILURE);53     }54     /*监听*/55     ret = listen(fd,10);56     if(ret < 0){57         perror("bind");58         exit(EXIT_FAILURE);59     }60     /*接受*/61     struct sockaddr_in snd;62     int snd_len = 0;63     pthread_t pid;64     while(1){65         int nfd = accept(fd,(struct sockaddr *)&snd,&snd_len);66         if(nfd < 0){67             perror("accpet");68             exit(EXIT_FAILURE);69         }70         ret = pthread_create(&pid,NULL,func,(void *)nfd);71         if(ret < 0){72             perror("pthread_create");73             return 1;74         }75     }76     close(fd);77 }
服务端
 1 #include<stdio.h> 2 #include<stdlib.h> 3 #include<string.h> 4 #include<sys/socket.h> 5 #include<netinet/in.h> 6 #include<fcntl.h> 7  8 int main() 9 {10     /*创建套接口*/11     int  fd = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);12     if(fd < 0){13         perror("socket");14         exit(EXIT_FAILURE);15     }16     /*服务端信息*/17     struct sockaddr_in srv;18     srv.sin_family = AF_INET;19     srv.sin_port=htons(9527);20     srv.sin_addr.s_addr = inet_addr("192.168.31.122");21     /*链接*/22     int ret = connect(fd,(struct sockaddr *)&srv,sizeof(struct sockaddr ));23     if(ret < 0){24         perror("connect");25         exit(EXIT_FAILURE);26     }27     /*聊天*/28     char data[1024] = {0};29     char revdata[1024] = {0};30     int i = 0;31     while(1){32         ret = read(0,data,1024);33         if(ret < 0){34             perror("read");35             exit(EXIT_FAILURE);36         }37         ret = send(fd,data,1024,0);38         if(ret < 0){39             perror("send");40             exit(EXIT_FAILURE);41         }42         ret = recv(fd,revdata,1024,0);43         if(ret < 0){44             perror("send");45             exit(EXIT_FAILURE);46         }47         printf("service say: %s\n",revdata);48     }49     close(fd);50     51 }
客户端

   上面的代码如果在实际的操作中有着明显的漏洞。监听程序最大允许接受10个连接请求,如果这十个一直连接不断开的话,后续的连接请求就无法得到处理。正确的方式应该是着几个请求在完成一个连接,执行一个操作后又要重新连接。

   TCP可靠的连接方式造成了连接过错比较复杂。消耗资源,所以大部分程序就是使用UDP的方式,比如迅雷,QQ。

   使用UDP聊天,这就意味着双方都要进行绑定操作。前面写的程序当发送信息后,程序就处于等待接收的状态。一直等到有信息来了之后才能够继续发送信息。这种状态称为阻塞状态。这种方式明显是不可取的。这时就要利用int select(int maxfdp,fd_set *readfds,fd_set *writefds,fd_set *errorfds,struct timeval *timeout)函数实现I/O多路复用。具体的实现过程我在代码中有注释。

#include<unistd.h>#include<stdlib.h>#include<stdio.h>#include<string.h>#include<netinet/in.h>#include<sys/socket.h>#include<sys/ioctl.h>#include<net/if.h>unsigned long getip(char *net_name);//获得指定网卡的ip。int main(void){    int fd  = 0;    fd = socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);//创建套接字,使用ipv4协议,数据报格式,UDP协议    if(fd < 0){        perror("socket");        exit(EXIT_FAILURE);    }    struct sockaddr_in gg ;    gg.sin_family = AF_INET;    gg.sin_port = htons(9527);    gg.sin_addr.s_addr = getip("eth0");//获取eth0网卡的ip地址        int ret = bind(fd,(struct sockaddr *)&gg,sizeof(struct sockaddr));//向系统绑定gg的信息    if(ret < 0){        perror("bind");        exit(EXIT_FAILURE);    }    fd_set rfds;    char data[1024] = {0};    char revdata[1024] = {0};    struct sockaddr_in mm ;    mm.sin_family = AF_INET;    mm.sin_port = htons(9527);    mm.sin_addr.s_addr = getip("eth1");//获得eth1网卡的ip地址    int mm_len;    while(1){        memset(revdata,0,1024);        memset(data,0,1024);//初始化数据,        FD_ZERO(&rfds);        FD_SET(0,&rfds);        FD_SET(fd,&rfds);        ret = select(fd+1,&rfds,NULL,NULL,NULL);//多路复用IO,使得fd,0处于非阻塞状态        if(FD_ISSET(fd,&rfds)){//监听到fd有数据读入,执行数据接收            ret = recvfrom(fd,revdata,1024,0,(struct sockaddr *)&mm,&mm_len);            if(ret < 0){                perror("recv");                exit(EXIT_FAILURE);            }            printf("mm say: %s",revdata);        }        if(FD_ISSET(0,&rfds)){//监听到键盘有数据读入,执行数据的读取和发送            ret = read(0,data,1024);            if(ret < 0){                perror("read");                exit(EXIT_FAILURE);            }            ret = sendto(fd,data,ret,0,(struct sockaddr *)&mm,sizeof(struct sockaddr));            if(ret < 0){                perror("recv");                exit(EXIT_FAILURE);            }        }        }        close(fd);}unsigned long getip(char *net_name){     int sock;    struct sockaddr_in sin;    struct ifreq ifr;    sock = socket(AF_INET,SOCK_DGRAM,0);    if(sock < 0){        perror("socket");        return -1;    }    strcpy(ifr.ifr_name,net_name);    int ret = ioctl(sock,SIOCGIFADDR,&ifr);    if(ret < 0){        perror("ioctl");        return -1;    }    memcpy(&sin,&ifr.ifr_addr,sizeof(sin));    return inet_addr(inet_ntoa(sin.sin_addr));}
gg方
 1 #include<unistd.h> 2 #include<stdlib.h> 3 #include<stdio.h> 4 #include<string.h> 5 #include<netinet/in.h> 6 #include<sys/socket.h> 7 #include<sys/ioctl.h> 8 #include<net/if.h> 9 unsigned long getip(char *net_name);10 11 int main(void)12 {13     int fd  = 0;14     fd = socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);15     if(fd < 0){16         perror("socket");17         exit(EXIT_FAILURE);18     }19     struct sockaddr_in gg ;20     gg.sin_family = AF_INET;21     gg.sin_port = htons(9527);22     gg.sin_addr.s_addr = getip("eth0");23     24     fd_set rfds;25     char data[1024] = {0};26     char revdata[1024] = {0};27     struct sockaddr_in mm ;28     mm.sin_family = AF_INET;29     mm.sin_port = htons(9527);30     mm.sin_addr.s_addr = getip("eth1");31     int ret = bind(fd,(struct sockaddr *)&mm,sizeof(struct sockaddr));32     if(ret < 0){33         perror("bind");34         exit(EXIT_FAILURE);35     }36     int gg_len;37     while(1){38         memset(revdata,0,1024);39         memset(data,0,1024);40         FD_ZERO(&rfds);41         FD_SET(0,&rfds);42         FD_SET(fd,&rfds);43         ret = select(fd+1,&rfds,NULL,NULL,NULL);44         if(FD_ISSET(fd,&rfds)){45             ret = recvfrom(fd,revdata,1024,0,(struct sockaddr *)&gg,&gg_len);46             if(ret < 0){47                 perror("recv");48                 exit(EXIT_FAILURE);49             }50             printf("gg say: %s",revdata);51         }52         if(FD_ISSET(0,&rfds)){53             ret = read(0,data,1024);54             if(ret < 0){55                 perror("read");56                 exit(EXIT_FAILURE);57             }58             ret = sendto(fd,data,ret,0,(struct sockaddr *)&gg,sizeof(struct sockaddr));59             if(ret < 0){60                 perror("recv");61                 exit(EXIT_FAILURE);62             }63         }    64     }    65     close(fd);66 }67 unsigned long getip(char *net_name){68     int sock;69     struct sockaddr_in sin;70     struct ifreq ifr;71     sock = socket(AF_INET,SOCK_DGRAM,0);72     if(sock < 0){73         perror("socket");74         return -1;75     }76     strcpy(ifr.ifr_name,net_name);77     int ret = ioctl(sock,SIOCGIFADDR,&ifr);78     if(ret < 0){79         perror("ioctl");80         return -1;81     }82     memcpy(&sin,&ifr.ifr_addr,sizeof(sin));83     return inet_addr(inet_ntoa(sin.sin_addr));84 }
mm方

   两方的程序代码差不多。就是发送方和接收方对调。这个程序是实现本机的两个网卡eth0和eth1进行通话。利用getip函数获得指定网卡的ip.

   

 

 

 

 

   

第二十五、二十六天:基于UDP的网路聊天程序