Linux网络编程(2)——套接字编程(2)UDP协议实现(回显服务器)

本文详细介绍了如何使用C++实现UDP服务器与客户端的通信过程,包括socket的创建、绑定、发送与接收数据的具体步骤,并提供了封装后的代码示例,便于理解和实际应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 sockaddr_in  这个结构比较关键。

UDP:服务器:

#include <stdio.h>
#include <sys/socket.h> //socket相关
#include <netinet/in.h>
#include <arpa/inet.h>//htons()
#include <cstringt> //sendto()  

//#include<pthread.h> //头文件
//#include <unistd.h>  //sleep头文件
//#include <vector>
//#include <windows.h>
#include <stdlib.h>

int main() {

	//1.先创建socket
	//int socket(int domain, int type, int protocol);
	//domain:地址域
	//type:套接字类型
	//protocol:通常 0 就好了

	//AF_INET:一个宏,表示使用IPv4协议
	//AF_INET6:IPv6协议
	//SOCK_DGRAM表示使用UDP协议
	int sock = socket(AF_INET, SOCK_DGRAM, 0);
	if (sock < 0) {
		//返回结果小于0,就失败了
		perror("socket");
		return 1;
	}

	//2.把当前 socket 绑定一个 IP + 端口号
	//int bind(int socket, const struct sockaddr *address, socklen_t address_len);
	//int socket:和哪个socket相连
	//const struct sockaddr *address:IP 地址
	//bind:一套准备工作
	sockaddr_in addr;  //如果文件后缀是.c   前面要加struct
	addr.sin_family = AF_INET;   //用UDP的协议家族

		//IP 也需要转成网络字节序,inet_addr 自动帮我们转了
	addr.sin_addr.s_addr = inet_addr("0.0.0.0");//IP 地址  
		//0.0.0.0:表示把电脑的所有的IP都包含在一起,因为电脑的网卡有很多,所以有很多IP,一般选第一个。
		//inet_addr():IP地址转换函数,把点分十进制,转换成整数
		//inet_ntoa():整数转回点分十进制
	addr.sin_port = htons(9090);//端口号

	//htons :  端口号要先转成网络字节序

	int ret = bind(sock,(sockaddr*)&addr,sizeof(addr));
	if (ret < 0) {
		perror("bind");
		return 1;
	}
	printf("服务器绑定成功!!\n");//前两步也就是手机开机,信号良好了。

	//3.处理服务器收到的请求
	while (true) {
		//服务器的工作流程:
			//1.初始化
			//2.(a,b,c)代表服务器的工作流程

		//a)读取客户端的请求
		//面向数据报的函数接口
		sockaddr_in peer; //发送端的IP   数据来自哪里
		socklen_t len = sizeof(peer);
		char buf[1024] = {0};
		ssize_t n = recvfrom(sock, buf, sizeof(buf)-1,0,(sockaddr*)&peer,&len);
		if (n<0) {
			perror("recvform");
			continue;
			//continue:考虑到容错,不要因为一次请求结束就退出
		}
		buf[n] = '\0';
		printf("[%s:%d] buf:%s\n", inet_ntoa(peer.sin_addrs.s_addr)/*IP*/, ntohs(peer.sin_port), buf);
			//ssize_t recvfrom(int sock, void *buf, size_t len, int flags, struct sockaddr *from, socklen_t *fromlen);
			/*	参数:
				sock:索引将要从其接收数据的套接字。
				buf:存放消息接收后的缓冲区。
				len:buf所指缓冲区的容量。
				flags:是以下一个或者多个标志的组合体,可通过or操作连在一起,
						MSG_DONTWAIT:操作不会被阻塞。
						MSG_ERRQUEUE:指示应该从套接字的错误队列上接收错误值,依据不同的协议,错误值以某种辅佐性消息的方式传递进来,使用者应该提供足够大的缓冲区。
						导致错误的原封包通过msg_iovec作为一般的数据来传递。导致错误的数据报原目标地址作为msg_name被提供   (0就好了,可以不用管)
				struct sockaddr *from:发送数据的IP,数据来自那里
				socklen_t *fromlen:IP长度,记得那么写就行了 
				
				返回值:
					成功执行时,返回接收到的字节数。另一端已关闭则返回0。失败返回-1,errno被设为以下的某个值
					EAGAIN:套接字已标记为非阻塞,而接收操作被阻塞或者接收超时
					EBADF:sock不是有效的描述词
					ECONNREFUSE:远程主机阻绝网络连接
					EFAULT:内存空间访问出错
					EINTR:操作被信号中断
					EINVAL:参数无效
					ENOMEM:内存不足
					ENOTCONN:与面向连接关联的套接字尚未被连接上
					ENOTSOCK:sock索引的不是套接字
				*/

		//b)根据请求进行响应
		//略过,因为写的是回显服务器

		//c)把相应写回客户端
		n = sendto(sock, buf, strlen(buf), 0, (sockaddr*)&peer, len);
		if (n<0) {
			perror("sendto");
			continue;
		}
		
	}

	close(sock);  //及时关闭服务器

	return 0;
}

Makefile文件:
	g++ zxczxc.cc  -o server

netstat:查看网络的状态命令
netstat -anp | grep  9090  (9090刚才的端口)

客户端:

#include <cstdio>
#include <sys/socket.h> //socket相关
#include <netinet/in.h>
#include <arpa/inet.h>//htons()
#include <cstringt> //sendto()  

//./client 127.0.0.1  //IP 改变 连接就变了,可配置
int main(int argc, char* argv[]) {
	//1.先创建socket
	
	int sock = socket(AF_INET, SOCK_DGRAM, 0);
	if (sock < 0) {
		perror("socket");
		return 1;
	
	//2.客户端一般不需要绑定 bind
		//bind 意味着和某个具体端口关联,如果没有bind,操作系统随机分配
		//服务器不 bind,会导致服务器每次启动端口改变,客户端没办法连接。
		//客户端也 bind 了,可能有问题,通常情况下一个端口号不能有两个进程bind,因为一个客户端bind了,后面的客户端肯能也bind。随意随机分配更科学。

	//3.准备服务器的sockaddr_in结构
		sockaddr_in sever_addr;
		sever_addr.sin_family = AF_INET;
		sever_addr.sin_addr.s_addr = inet_addr(argv[1]);
		sever_addr.sin_port = htons(9090);
		socklen_t len
	//4.直接发送数据即可、
		while (1) {
			char buf[1024] = { 0 };
			printf("请输入一段内容:");
			fflush(buf);
			scanf("%s", buf);
			sendto(sock, buf, 0, (sockaddr*)&sever_addr, sizeof(sever_addr));
			//从服务器接受一下结果
			char buf_output[1024] = { 0 };
			recvfrom(sock, buf_output, sizeof(buf_output), NULL, NULL);
			//两个NULL:表示对端的地址不关心
			printf("sever resp:%s\n", buf_output);
		}
		
		
	return 0;
}


封装之后的UDP:

SOCKET:

#include <cstdio>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <cstringt>
#include <sys/socket.h> //socket相关
#include <netinet/in.h>
#include <arpa/inet.h>//htons()
#include <cstringt> //sendto()  

class UdpSocket
{
public:
	UdpSocket() :fd_(-1) {
	
	}
	


	//打开一个Socket 
	//创建成功返回true 失败返回 false
	bool Socket() {
		fd_ = socket(AF_INET, SOCK_DGRAM, 0);
		if (fd_ < 0) {
			perror("socket");
		}
		return true;
	}

	bool Close() {
		if (fd_ != -1) {
			close(fd_);
		}
		return true;
	}

	bool Bind(const std::string& ip,uint16_t port) {
		//根据参数构造一个 socketaddr_in 结构
		//调用bind
		sockaddr_in addr;
		addr.sin_family = AF_INET;
		addr.sin_addr.s_addr = inet_addr(ip.c_str());
		addr.sin_port = htons(port);
		int ret = bind(fd_, (sockaddr*)&addr, sizeof(addr));
		if (ret < 0) {
			perror("bind");
			return false;
		}
		return true;
	}


	//除了成功失败,还需要返回
	//1.读到的数据
	//2.对方的IP 地址
	//3.对方的端口号
	bool RecvFrom(std::string* msg,std::string* ip,uint16_t * port=NULL) {
		char buf[1024 * 10] = { 0 };
		sockaddr_in peer;
		socklen_t len = sizeof(peer);
		ssize_t n=recvfrom(fd_ ,buf, sizeof(buf)-1, (sockaddr*)&peer, &len);
		if (n < 0) {
			perror("recvfrom");
			return false;
		}
		*msg = buf;
		if (ip != NULL) {
			*ip = inet_ntoa(peer.sin_addr);
		}
		if (port!=NULL) {
			*port = ntohs(peer.sin_port);
		}
		return true;
	}

	bool SendTo(const std::string& msg,const std::string& ip,uint16_t port) {
		sockaddr_in addr;
		addr.sin_family = AF_INET;
		addr.sin_addr.s_addr = inet_addr(ip.c_str());
		addr.sin_port = htons(port);
		ssize_t n = sendto(fd_, msg.c_str(), msg.size(), 
			0, (sockaddr*)&addr, sizeof(addr));
		if (n<0) {
			perror("sendto");
			return false;
		}
		return true;
	}
private:
	int fd_;  //表示 Socket
};

服务端:

#pragma once
#include "SOCKET.hpp"
#include <cassert>
#include <functional>
//通用的UDP 服务器类
//服务器的工作流程:
//1.初始化
//2.(a,b,c)代表服务器的工作流程

//a)读取客户端的请求
//b)根据请求进行响应
//c)把相应写回客户端

//a,c 固定套路,在哪都一样,b 和业务相关,对应代码提取成回调函数
//修改指针handler
//typedef void(*Handle)(const std::string& req, std::string* resp);
//C++11
typedef function<void(const std::string& , std::string*)> Handler;

class UdpServer
{
public:
	UdpServer(){
		assert(sock_.Socket());
	}
	~UdpServer() {
		sock_.Close();
	}

	//核心流程
	bool Strat(const std::string& ip, uint16_t port, Handle handler) {
		//1.创建socket(已完成)
		//2.绑定端口号
		boll ret = sock_.Bind(ip, port);
		if (!ret) {
			return false;
		}

		while (true) {
			//处理每个请求
			//1.读取请求
			std::string req;
			std::string peer_ip;
			std::string peer_port;
			uint16_t peer_port;
			sock_.RecvFrom(&req, &peer_ip, &peer_port);
			//2.根据请求响应
			std::string resp;
			handler(req, &resp);
			//3.请求返回到客户端
		}
	}
private:
	UdpSocket sock_;
};

客户端: 

#pragma  once

#include "SOCKET.hpp"

class UdpClient {
public:
	UdpClient(std::string& ip,uint16_t port)
		:server_ip_(ip), server_port_(port) {
		sock_.Socket();
	}
	~UdpClient() {
		sock_.Close();
	}

	bool RecvFrom(std::string* msg) {
		
		return sock_.RecvFrom(msg);
	}
	bool SendTo(std::string& msg) {

		return sock_.SendTo((msg),server_ip_,server_port_);
	}
private:
	UdpSocket sock_;
	std::string server_ip_;
	uint16_t server_port_;
};

封装之后的服务端主函数:

#include "封装UDP服务端.hpp"

//核心业务功能
void Echo(const std::string& req, std::string* resp) {
	*resp = req;
}

int main() {
	UdpServer server;
	server.Strat("0.0.0.0", 9090, Echo);
	
	return 0;
}

代码实践是最好学习记忆的方式的方式!!!

利用function 也可以写 lambda 表达式。这种方式很方便。要了解。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值