网络(八)并发的UDP服务端 以进程完成功能
多进程处理 UDP客户端
// todo UDP发送端
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define LOGIN_SUCCESS 1
#define LOGIN_FAIL 0
//?发送数据
//?@param fd 套接字描述符
//?@param addr 目标地址
//?@param addrlen 地址长度
void send_data(int fd, struct sockaddr_in * addr , socklen_t addrlen);
void login(int fd, struct sockaddr_in * addr ,struct sockaddr_in * new_addr , socklen_t new_addrlen);
//命令行参数 ip port
int main(int argc, char *argv[] ){
// if(argc!= 3){
// printf("Usage: %s ip port\n", argv[0]);
// exit(EXIT_FAILURE);
// }
//!通过socket函数创建套接字
//!@param domain 协议族,AF_INET表示IPv4协议族
//!@param type 套接字类型,SOCK_DGRAM表示UDP套接字
//!@param protocol 协议,一般为0 让系统⾃动识别
//!@return 成功返回套接字描述符,失败返回-1
int fd = socket(AF_INET, SOCK_DGRAM, 0);
if(fd == -1){
//创建套接字失败
perror("socket err");
exit(EXIT_FAILURE);
}
//准备接收消息的地址
/* struct sockaddr_in {
short int sin_family; // 地址族 AF_INET
unsigned short int sin_port; // 端口号
struct in_addr sin_addr;// IP地址
unsigned char sin_zero[8]; // 填充字节 为了对齐sockaddr
};
*/
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family=AF_INET;
addr.sin_port=htons(8083);//htons函数将主机字节序转换为网络字节序
//addr.sin_addr.s_addr=inet_addr("192.168.74.1");//inet_addr()将点分十进制IP地址转换为网络字节序IP地址
//inet_aton()将点分十进制IP地址转换为网络字节序IP地址
//@param ip 字符串形式的IP地址
//@param in_addr 结构体变量,用于存储IP地址
int ret=inet_aton("172.17.140.183", &addr.sin_addr); // 成功返回⾮0,失败返回0
if(ret == 0){
perror("inet_aton err");
exit(EXIT_FAILURE);
}
printf("ip == %d\n",addr.sin_addr.s_addr);
//inet_ntoa()将网络字节序IP地址转换为点分十进制IP地址
//char *ip=inet_ntoa(addr.sin_addr); // 成功返回⾮0,失败返回0
//printf("ip == %s\n",ip);
//获取 和服务端的新建的子进程通信
struct sockaddr_in new_addr;
login(fd, &addr, &new_addr, sizeof(new_addr));
//与新的子进程通信
send_data(fd, &new_addr, sizeof(new_addr));
return 0;
}
//!发送数据
//!@param fd 套接字描述符
//!@param addr 目标地址
//!@param addrlen 地址长度
void send_data(int fd, struct sockaddr_in * addr , socklen_t addrlen){
while (1){
int n = 0;//返回发送的字节数
char buf[1024] = {
0};
printf("请输入要发送的消息:");
fgets(buf, 1024, stdin);
//!发送数据
//!@param fd 套接字描述符
//!@param buf 发送缓冲区
//!@param len 发送缓冲区长度
//!@param flags 发送标志 0 表示默认操作
//!@param addr 目标地址
//!@param addrlen 地址长度
//!@return 成功返回发送的字节数,失败返回-1
n= sendto(fd, buf, strlen(buf), 0, (struct sockaddr *)addr, addrlen);
if(n == -1){
perror("sendto err");
exit(EXIT_FAILURE);
}
if(strncmp(buf, "exit",4) == 0){
break;
}
}
}
void login(int fd, struct sockaddr_in * addr ,struct sockaddr_in * new_addr , socklen_t addrlen){
char login_status=LITTLE_ENDIAN;
while (1){
int n = 0;//返回发送的字节数
char buf[1024] = {
0};
printf("请输入要发送的消息:");
fgets(buf, 1024, stdin);
n= sendto(fd, buf, strlen(buf), 0, (struct sockaddr *)addr, addrlen);
if(n == -1){
perror("sendto err");
exit(EXIT_FAILURE);
}
//接收消息服务器的响应
n= recvfrom(fd, &login_status, sizeof(login_status), 0, (struct sockaddr *)new_addr, &addrlen);
if(n == -1){
perror("recvfrom err");
exit(EXIT_FAILURE);
}
if(login_status == LOGIN_SUCCESS){
printf("登录成功\n");
printf("新的子进程的地址为:%s:%d\n",inet_ntoa(new_addr->sin_addr),ntohs(new_addr->sin_port));
break;
}else if(login_status == LOGIN_FAIL){
printf("登录失败\n");
continue;
}
if(strncmp(buf, "exit",4) == 0){
break;
}
}
}
多进程 处理UDP服务端
父进程负责创建套接字,绑定到指定端口,等待客户端的连接请求,创建子进程处理客户端的请求。
子进程中返回一个新建的套接字,后续与客户端通信将使用新的套接字,在子进程中完成。
// todo UDP服务器端程序
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
#define LOGIN_SUCCESS 1
#define LOGIN_FAIL 0
void todo(int signum){
printf("收到信号%d\n",signum);
waitpid(-1, NULL, WNOHANG);
}
//接收数据
void recv_data(int sockfd);
//初始化套接字
int init_socket(char *ip, char* port);
int TheLogin(char *ip, char * port);
int main(int argc, char *argv[]){
//处理僵死的子进程
signal(SIGCHLD, todo);
//验证
int new_sockfd = TheLogin("172.17.140.183", "8083");
//接收数据
recv_data(new_sockfd);
//关闭套接字
close(new_sockfd);
return 0;
}
//接收数据
void recv_data(int sockfd) {
struct sockaddr_in client_addr;//客户端的地址
int client_addr_len = sizeof(client_addr);
while(1) {
char recv_buf[1024]={
0};
//接收数据
//*@param sockfd 套接字描述符
//*@param buf 接收缓冲区
//*@param len 接收缓冲区长度
//*@param flags 接收标志
//*@param src_addr 发送方地址
//*@param addrlen 发送方地址长度
//*@return 成功返回接收到的字节数,失败返回-1
int ret = recvfrom(sockfd, recv_buf, sizeof(recv_buf), 0, (struct sockaddr *) &client_addr, &client_addr_len);
if (ret == -1) {
perror("recvfrom err");
exit(EXIT_FAILURE);
}
//打印接收到的信息
char *ip_str = inet_ntoa(client_addr.sin_addr);//将网络字节序IP地址转换为点分十进制IP地址
int port = ntohs(client_addr.sin_port); //将网络字节序端口号转换为主机字节序端口号
printf("接收到来自%s:%d的数据:%s\n", ip_str, port, recv_buf);
if(strncmp(recv_buf, "exit", 4) == 0){
//退出程序
break;
}
}
close(sockfd);
exit(EXIT_SUCCESS);
}
int init_socket(char *ip,char *port){
//!通过socket函数创建套接字
//!@param domain 协议族,AF_INET表示IPv4协议族
//!@param type 套接字类型,SOCK_DGRAM表示UDP套接字
//!@param protocol 协议,一般为0 让系统⾃动识别
//!@return 成功返回套接字描述符,失败返回-1
int fd = socket(AF_INET, SOCK_DGRAM, 0);
if(fd == -1){ //创建套接字失败
perror("socket err");
exit(EXIT_FAILURE);
}
//准备服务器地址
/* struct sockaddr_in {
short int sin_family; // 地址族 AF_INET
unsigned short int sin_port; // 端口号
struct in_addr sin_addr;// IP地址
unsigned char sin_zero[8]; // 填充字节 为了对齐sockaddr
};
*/
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family=AF_INET;
addr.sin_port=htons(atoi(port));//htons函数将主机字节序转换为网络字节序
//inet_aton()将点分十进制IP地址转换为网络字节序IP地址
//*@param ip 字符串形式的IP地址
//*@param in_addr 结构体变量,用于存储IP地址
int ret=inet_aton(ip, &addr.sin_addr); // 成功返回⾮0,失败返回0
if(ret == 0){
perror("inet_aton err");
exit(EXIT_FAILURE);
}
//!绑定套接字到服务器地址
//!@param sockfd 套接字描述符
//!@param addr 服务器地址
//!@param addrlen 服务器地址长度
//!@return 成功返回0,失败返回-1
int ret2 = bind(fd, (struct sockaddr*)&addr, sizeof(addr));
if(ret2 == -1){
perror("bind err");
exit(EXIT_FAILURE);
}
return fd;
}
int TheLogin(char *ip, char *port){
unsigned char login_status;
int new_sockfd;
//初始化套接字
int sockfd = init_socket(ip, port);
struct sockaddr_in client_addr;//客户端的地址
int client_addr_len = sizeof(client_addr);
while(1) {
char recv_buf[1024]={0};
int ret = recvfrom(sockfd, recv_buf, sizeof(recv_buf), 0, (struct sockaddr *) &client_addr, &client_addr_len);
if (ret == -1) {
perror("recvfrom err");
exit(EXIT_FAILURE);
}
//打印接收到的信息
char *ip_str = inet_ntoa(client_addr.sin_addr);//将网络字节序IP地址转换为点分十进制IP地址
int port = ntohs(client_addr.sin_port); //将网络字节序端口号转换为主机字节序端口号
printf("接收到来自%s:%d的数据:%s\n", ip_str, port, recv_buf);
//登录验证
//判断是否为登录请求
login_status = ( strncmp(recv_buf, "login",5)==0 ? LOGIN_SUCCESS: LOGIN_FAIL ) ;
if(login_status == LOGIN_SUCCESS){
//使用子进程完成后续的交互
int pid = fork();
if(pid == -1){
perror("fork err");
exit(EXIT_FAILURE);
}else if(pid == 0){
printf("开始了新的进程\n");
//子进程将关闭父进程的套接字
close(sockfd);
//子进程使用新的套接字完成后续的交互
//使用0号,系统将自动分配一个可用端口号
new_sockfd = init_socket(ip, "0");
printf("新的套接字文件描述符:%d\n", new_sockfd);
//子进程通过新的端口 发送成功消息
sendto(new_sockfd, &login_status, sizeof(login_status), 0, (struct sockaddr *) &client_addr, client_addr_len);
break;
}
} else{
//回传失败消息
sendto(sockfd, &login_status, sizeof(login_status), 0, (struct sockaddr *) &client_addr, client_addr_len);
}
}
//返回新的套接字
return new_sockfd;
}