这个正好是我的一个课堂上机小练习,为了实现这个功能,我们需要了解一下几个知识点
- udp发送和接受数据的过程
- select语句功能
- 如何开启一个线程
- *如何传输结构体struct
一.udp发送数据和接受数据流程
无论是客户端还是服务器端,刚开始都需要向系统申请套接字socket,然后通过socket来实现发送和接受消息,只不过服务器端需要把该套接字绑定到某个端口通过调用bind()函数(很多书上说bind()函数公开自己所要监听的端口),其实客户端也是可以执行bind()函数来指明发送数据的端口,不过一般不写,系统会自动分配一个端口给它。总的梳理一下
客户端:申请socket——通过该socket发送数据和接受数据
服务器:申请socket——bind()端口——调用recvfrom()阻塞
二.select语句功能
#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代表永久)
三.开启线程
#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数组转换为相应的数据。
void * memcpy ( void * destination, const void * source,size_t num );
函数说明:从source指向的地址开始拷贝num个字节到以destination开始的地址。其中destination与source指向的数据类型无关。
五.项目代码
说明:因为我是复用老师给的代码中几个函数Sendto和Recv函数,所以需要替换还有头文件需要自己添加一下
客户端
#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 ,¶)!=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);
}
服务器
#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));
}
}
}
}