Linux下使用udp协议实现群聊室

Posted by Chejdj Blog on December 11, 2017

这个正好是我的一个课堂上机小练习,为了实现这个功能,我们需要了解一下几个知识点。

  • udp发送和接受数据的过程
  • select语句功能
  • 如何开启一个线程
  • 如何传输结构体struct

一.udp发送数据和接受数据流程

这里写图片描述 无论是客户端还是服务器端,刚开始都需要向系统申请套接字socket,然后通过socket来实现发送和接受消息,只不过服务器端需要把该套接字绑定到某个端口通过调用bind()函数(很多书上说bind()函数公开自己所要监听的端口),其实客户端也是可以执行bind()函数来指明发送数据的端口,不过一般不写,系统会自动分配一个端口给它。总的梳理一下

客户端:申请socket——通过该socket发送数据和接受数据

服务器:申请socket——bind()端口——调用recvfrom()阻塞

二.select语句功能

1
2
3
	#include<sys/select.h>
 	#include<sys/time.h>
 	int select(int maxfdp1,fd_set *readset,fd_set *writeset,fd_set *exceptset,const struct timeval *timeout)

先说一下select的功能:这属于IO复用模型,进程会受阻与select调用,select同时监听多个套接字,只要有一个套接字响应了,就不会阻塞,可以执行你想要的处理过程,简单一点就是一个监听多个socket功能。看一下它的参数

maxfdp1:指定带监听的描述符个数,它的值是待测试的最大描述符加1,换句话说调用socket函数返回int参数就是socket文件描述符

read_set,read_set,exceptset:就是我们要监听的socket文件描述符的集合,一个读,一个写,一个异常

timeout:就是指定监听时长(null代表永久)

三.开启线程

1
2
3
4
5
	#include<pthread.h>
	int pthread_create(pthread_t *restrict tidp,
                   const pthread_attr_t *restrict attr,
                   void *(*start_rtn)(void), 
                   void *restrict arg);)

第一个参数为指向线程标识符的指针。

第二个参数用来设置线程属性。

第三个参数是线程运行函数的起始地址。

最后一个参数是运行函数的参数。如果函数不需要参数变成NULL。

四.传输结构体

因为socket只能传输字符串,所以我们必须把struct转成char数组才能传输,接收端也只能char数组接受再转成结构体 使用memcpy将文件、结构体、数字等,可以转换为char数组,之后进行传输,接收方在使用memcpy将char数组转换为相应的数据。

1
	void * memcpy ( void * destination, const void * source,size_t num );

函数说明:从source指向的地址开始拷贝num个字节到以destination开始的地址。其中destination与source指向的数据类型无关。

五.项目代码

说明:因为我是复用老师给的代码中几个函数Sendto和Recv函数,所以需要替换还有头文件需要自己添加一下。

客户端
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
	#include"unp.h"
	#include<string.h>
	struct fun_para{
 	int port;
	};
	typedef struct{
  	char username[10];
  	char msg_buf[1024];
  	int type;
  	struct sockaddr_in address;
	}MSG;
	static void func(void * para){
	struct fun_para* kk=(struct fun_para *)para;
	int port=kk->port;
	int s=socket(AF_INET,SOCK_DGRAM,0);
   	struct sockaddr_in serv;
   	bzero(&serv,sizeof(serv));
   	serv.sin_family=AF_INET;
   	serv.sin_addr.s_addr=htonl(INADDR_ANY);
   	serv.sin_port=htons(port);
   	int sin_len=sizeof(serv);
   	bind(s,(struct sockaddr *)&serv,sizeof(serv));
   	if(s==-1)printf("create socket erro:");
    for(;;){
    		char recvBuf[1200]={0};
            MSG *client_msg=(MSG*)malloc(sizeof(MSG));
         	int kk=recvfrom(s,recvBuf,1200,0,(SA*)&serv,&sin_len);
         	memcpy(client_msg,recvBuf,sizeof(MSG));
         	printf("username: %smessage:%s\n",client_msg->username,client_msg->msg_buf);
        	}
	}

 int
 main(int argc, char **argv)
 {
	int sockfd;
	struct sockaddr_in	servaddr;
	pthread_t tid;
	char sendline[1024];
	char username[10];
	if (argc != 3)
		err_quit("usage: tcpcli <IPaddress> <Port>");
	sockfd = Socket(AF_INET, SOCK_STREAM, 0);
	bzero(&servaddr, sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	servaddr.sin_port = htons(9877);
	Inet_pton(AF_INET, argv[1], &servaddr.sin_addr);
	sockfd=Socket(AF_INET,SOCK_DGRAM,0);
    bind(sockfd,(struct sockaddr *)&servaddr,sizeof(servaddr));
    struct fun_para para;
    para.port=atoi(argv[2]);
	struct sockaddr_in serv;
    bzero(&serv,sizeof(serv));
    serv.sin_family=AF_INET;
    serv.sin_addr.s_addr=htonl(INADDR_ANY);
    serv.sin_port=htons(atoi(argv[2]));
    printf("client port: %d\n",atoi(argv[2]));
    printf("input your name\n");
   	int flag=0;
   	//注册消息
   	// Sendto(sockfd,&msg,sizeof(struct MSG),0,(SA*)&servaddr,sizeof(servaddr));
    if(pthread_create(&tid,NULL,&func ,&para)!=0){
         printf("thread_create Failed\n");
      } 
    while(Fgets(sendline,1024,stdin)!=NULL){
            MSG *send=(MSG*)malloc(sizeof(MSG));
       if(flag==0){
          strcpy(send->username,sendline);
          strcpy(send->msg_buf,sendline);
          strcpy(username,sendline);
          send->type=0;
          flag++;
          }else{
               	strcpy(send->msg_buf,sendline);
               	strcpy(send->username,username);
               	send->type=1;
               }
       send->address=serv;
       char information[1200]={0};
       memcpy(information,send,sizeof(MSG));
       Sendto(sockfd,information,sizeof(information),0,(SA*)&servaddr,sizeof(servaddr));
         }
	//str_cli(stdin, sockfd);		/* do it all */
	exit(0);
}
服务端代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
#include "unp.h"
 typedef struct {
  char username[10];
  char msg_buf[1024];
  int type;
  struct sockaddr_in address;
}MSG;
// 建立一个链表存储客户端的功能
typedef struct client{
    struct sockaddr_in ct_addr;
    struct client* next;
}CNODE,*pCNODE;
void list_insert(pCNODE *phead,pCNODE p){
     p->next=*phead;
     *phead=p;
}
void msg_brocast(int send_sockfd,char* msg,pCNODE phead){
    while(phead){
        printf("Port: %s %d message:%s ",inet_ntoa((phead->ct_addr).sin_addr),htons((phead->ct_addr).sin_port),msg);
        Sendto(send_sockfd,msg,1200,0,(SA*)&(phead->ct_addr),sizeof(SA));
        phead=phead->next;
}
}
int
main(int argc, char **argv)
{
     int sockfd,send_socket;
     int maxfdp1;
     int select_int;
     struct sockaddr_in addr,cli;
      pCNODE my_list=NULL;
     sockfd=Socket(AF_INET,SOCK_DGRAM,0);
     send_socket=Socket(AF_INET,SOCK_DGRAM,0);
     addr.sin_family=AF_INET;
     addr.sin_port=htons(9877);//绑定端口号9877
     addr.sin_addr.s_addr=htonl(INADDR_ANY);
     Bind(sockfd,(SA*)&addr,sizeof(addr));
     fd_set rset;
     FD_ZERO(&rset);
      while(1){
      FD_SET(sockfd,&rset);
      maxfdp1=sockfd+1;
      select_int = select(maxfdp1,&rset,NULL,NULL,NULL);
      if(select_int<0){
              printf("erro\n");
           return;
       }else if(select_int==0){

               printf("timeout\n");
           }else{
              if(FD_ISSET(sockfd,&rset)){
                 pCNODE pNew =(pCNODE)calloc(1,sizeof(CNODE));
                 printf("receve data\n");
                 char recvBuf[1200]={0};
                  MSG *client_msg=(MSG*)malloc(sizeof(MSG));
                 int lent=sizeof(cli);
                 printf("bigsize is %d\n",sizeof(MSG));
                 int kk=recvfrom(sockfd,recvBuf,1200,0,(SA*)&(cli),&lent);  
                  printf("1address: %s  %d \n",inet_ntoa(cli.sin_addr),htons(cli.sin_port));     
        	 memcpy(client_msg,recvBuf,sizeof(MSG));
                 pNew->ct_addr=client_msg->address;
                  if(client_msg->type==0){
                        printf("添加新成员\n");
                        list_insert(&my_list,pNew);
                  }else if(client_msg->type==1){
                      msg_brocast(send_socket,recvBuf,my_list);       
                   }
                // printf("IP地址为:%s 端口号为:%d",inet_ntoa(cli.sin_addr),htons(cli.sin_port));
                 }
}
}
}