并发多进程服务器网络聊天模型

这两天在想,到底是否学会了,怎么验证你学会了,我一直在纠结,但是昨天早上醒来,我觉得我可能迄今为止做错了很多事,比葫芦画瓢?谁都会,靠着自己脑中残留的记忆来照着写,真的是硬实力吗?我不觉得,我认为只有自己写出来了,才算你牛逼。

这两天虽然只写了不多的代码,但我觉得让我受益匪浅。把遇到的问题即时记录下来则会很有帮助。
1.warning的警告要看在哪里警告,警告的提示跟函数参数的出入在哪,我的问题在于编译时提示我recvfrom的参数有问题,

仔细一看便知,原函数的参数用的(struct sockaddr *)的指针,而我用的是sockaddr_in,因此需要强制转换。 先看一下client.c的代码 :
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
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#define LOGIN_FAILURE 0
#define LOGIN_SUCCESS 1
void login_send(int sockfd,struct sockaddr_in *peer_addr,struct sockaddr_in *dest_addr,socklen_t addrlen)
{
char buffer[64]={0};
int send_number;
char login_flag;
while(1)
{
memset(buffer,0,sizeof(buffer));
printf("login: ");
fgets(buffer,64,stdin);
buffer[strlen(buffer)-1]='\0';
send_number=sendto(sockfd,buffer,strlen(buffer),0,(struct sockaddr *)peer_addr,addrlen);
if(send_number==-1)
{
perror("[ERROR] sendto() failed!");
exit(EXIT_FAILURE);
}
recvfrom(sockfd,&login_flag,sizeof(login_flag),0,(struct sockaddr *)dest_addr,&addrlen);
if(login_flag==LOGIN_SUCCESS)
break;
else
printf("password is not true,try again...\n");


}





}
void send_data(int sockfd,struct sockaddr_in *dest_addr,socklen_t addrlen)
{
char buffer[64]={0};
int send_number;
while(1)
{
memset(buffer,0,sizeof(buffer));
printf("> ");
fgets(buffer,sizeof(buffer),stdin);
buffer[strlen(buffer)-1]='\0';
send_number=sendto(sockfd,buffer,strlen(buffer),0,(struct sockaddr *)dest_addr,addrlen);
if(send_number==-1)
{
perror("[ERROR] sendto() failed!");
exit(EXIT_FAILURE);

}
if(strcmp(buffer,"quit")==0)
{
printf("quitted......\n");
exit(EXIT_SUCCESS);

}



}


}
int main(int argc, const char *argv[])
{
if(argc!=3)
{
fprintf(stderr,"Parameters wrong!(should contain:ip port)");
return -1;
}
int sockfd;
sockfd=socket(AF_INET,SOCK_DGRAM,0);//创建socket套接字
if(sockfd==-1)
{
perror("[ERROR] socket() failed!");
return -1;
}
//sockaddr_in信息填充
struct sockaddr_in peer_addr;
struct sockaddr_in dest_addr;
memset(&peer_addr,0,sizeof(peer_addr));
peer_addr.sin_family=AF_INET;
peer_addr.sin_addr.s_addr=inet_addr(argv[1]);
peer_addr.sin_port=htons(atoi(argv[2]));
socklen_t addrlen=sizeof(peer_addr);
//登录密钥验证

login_send(sockfd,&peer_addr,&dest_addr,addrlen);
printf("login success!\n");
//数据交互
send_data(sockfd,&dest_addr,addrlen);
close(sockfd);
return 0;
}
  1. 1
    2
    3
    4
    5
    6
    struct sockaddr_in peer_addr;
    struct sockaddr_in dest_addr;
    memset(&peer_addr,0,sizeof(peer_addr));
    peer_addr.sin_family=AF_INET;
    peer_addr.sin_addr.s_addr=inet_addr(argv[1]);
    peer_addr.sin_port=htons(atoi(argv[2]));

我写代码的时候只想到了memset初始化,不知道怎的将memset放到赋值后面了,难怪我说为什么一直显示无效的参数…因为没有参数。
client客户端的代码还是比较简单的,下面来看服务端的代码。

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
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#define LOGIN_FAILURE 0
#define LOGIN_SUCCESS 1
void printf_client_info(struct sockaddr_in *addr,char *buf)
{
printf("===============================\n");
printf("user IP : %s\n",inet_ntoa(addr->sin_addr));
printf("user port : %d\n", ntohs(addr->sin_port));
printf("user data : %s\n",buf);
}
int init_socket(const char *ip,const char *port)
{
int sockfd;
struct sockaddr_in my_addr;
memset(&my_addr,0,sizeof(my_addr));
socklen_t addrlen=sizeof(my_addr);
my_addr.sin_family=AF_INET;
my_addr.sin_port=htons(atoi(port));
my_addr.sin_addr.s_addr=inet_addr(ip);
sockfd=socket(AF_INET,SOCK_DGRAM,0);
if(sockfd==-1)
{
perror("[ERROR] socket failed!");
return -1;
}
if(bind(sockfd,(struct sockaddr *)&my_addr,addrlen)<0)
{
perror("[ERROR] failed to bind ");
return -1;
}
return sockfd;
}
int login_manage(const char *ip,const char *port)
{
int sockfd;
int sockfd_new;
char login_check;
char login_message[64]={0};
struct sockaddr_in client_addr;
socklen_t addrlen=sizeof(client_addr);
sockfd=init_socket(ip,port);
while(1)
{
login_check=LOGIN_FAILURE;
memset(login_message,0,sizeof(login_message));
recvfrom(sockfd,login_message,sizeof(login_message),0,(struct sockaddr *)&client_addr,&addrlen);
printf("[DEBUG:] recved login_message\n");
if(strcmp(login_message,"root")==0)
{
login_check=LOGIN_SUCCESS;
memset(login_message,0,sizeof(login_message));
pid_t fpid=fork();
if(fpid==-1)
{
perror("[ERROR] fork() failed!");
return -1;
}else if(fpid==0)
{
close(sockfd);
sockfd_new=init_socket(ip,"0");
sendto(sockfd,&login_check,sizeof(login_check),0,(struct sockaddr *)&client_addr,addrlen);
break;
}


}else{

sendto(sockfd,&login_check,sizeof(login_check),0,(struct sockaddr *)&client_addr,addrlen);
}

}
return sockfd_new;
}
void recv_data(int sockfd_new)
{
int n = 0;
char buf[1024] = {0};

struct sockaddr_in client_addr;
socklen_t len = sizeof(client_addr);
printf("[DEBUG]: enter recv_data:\n");
while(1)
{
memset(buf,0,sizeof(buf));
n = recvfrom(sockfd_new,buf,sizeof(buf),0,(struct sockaddr *)&client_addr,&len);
if(n < 0)
{
perror("Fail to sendto");
exit(EXIT_FAILURE);
}
printf("[DEBUG] now is up on printf_client_info()...\n");

printf_client_info(&client_addr,buf);

if(strncmp(buf,"quit",4) == 0)
break;
}

close(sockfd_new);
exit(EXIT_SUCCESS); //结束当前子进程
return ;

}
int main(int argc,const char *argv[])
{
int sockfd;
unsigned char login_flag;
if(argc < 3)
{
fprintf(stderr,"Usage : %s ip port!\n",argv[0]);
exit(EXIT_FAILURE);
}
sockfd = login_manage(argv[1],argv[2]);
printf("child process recv_data:\n");
//2.接收数据

recv_data(sockfd);



return 0;
}

老实说服务端的代码让我改了很长时间。
3.bind的问题,bind的作用是将当前程序和指定端口绑定,如果不绑定端口客户端没办法知道固定的端口。
4.就是登录标志login_check,我初始化其为LOGIN_FAILRE,放的位置不太对,应该是:每次重新循环的时候,都要赋初始值为LOGIN_FAILRE。
5.就是本次特别需要注意的地方, 父进程的作用就是验证登录密钥,如果正确,则fork子进程进行通信,如果不正确继续在父进程循环
在上面代码中,在login_manage()函数中,死循环只在本进程中运行,当判断条件真的时候,则fork一个子进程,然后在子进程中,给客户端发登陆成功消息然后跳出循环,因为fork也继承了死循环,这是很重要的一点。子进程中,需要重新创建一个socket套接字,关闭父进程的套接字(因为没什么用)。bind的参数写0分配随机端口,因为子进程发送信息后,客户端可以通过recvfrom保存ip和端口信息,所以可以找到与子进程通信的方法。
success_png

Contents
  1. 1. 这两天在想,到底是否学会了,怎么验证你学会了,我一直在纠结,但是昨天早上醒来,我觉得我可能迄今为止做错了很多事,比葫芦画瓢?谁都会,靠着自己脑中残留的记忆来照着写,真的是硬实力吗?我不觉得,我认为只有自己写出来了,才算你牛逼。