首页 > 代码库 > 第二十五、二十六天:基于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));}
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 }
两方的程序代码差不多。就是发送方和接收方对调。这个程序是实现本机的两个网卡eth0和eth1进行通话。利用getip函数获得指定网卡的ip.
第二十五、二十六天:基于UDP的网路聊天程序