【高并发服务器】send、sendto以及recv、recvfrom区别小结

0x00 前言

文章中的文字可能存在语法错误以及标点错误,请谅解;

如果在文章中发现代码错误或其它问题请告知,感谢!

系统版本:Ubuntu 14.04_64

最后更新:2022-04-28

0x01 使用场景

对于TCP来说,一般使用send()recv()函数进行数据读写。
对于UDP来说,一般使用sendto()recvfrom()函数进行数据读写。

我们都知道,UDP是一个简单传输层协议,丢包不可重传。UDP提供的是无连接(connectionless)服务,UDP客户和服务器之间并不建立连接,所以双方之间每次发送数据都需要指定发送的目的地址以及端口信息1sendto()函数可以在参数中指定发送的目的地,可以让数据正确发送到对方。recvfrom()函数可以等待某个对方数据的到达并记录对方的地址及端口信息。

TCP则是传输控制协议,提供的是面向连接的可靠字节流,可以丢包重传。当使用TCP方式进行通信时,TCP客户首先会跟服务器建立一个连接(connection),即三次握手(three-way handshake)的过程。通过该连接,客户与服务器之间便可以进行数据交换2。所以,一旦连接建立之后,便意味着形成了一条通路,双方就可以不用在各自的发送过程中再指定目的地地址和端口信息或者在接收数据的过程中另外记录对方的地址和端口信息,直接进行数据读写即可。在TCP连接中,可以使用send()函数以及recv()函数进行数据读写,也可以使用sendto()函数以及recvfrom()函数进行读写,只不过将地址相关参数设置为NULL即可。

0x02 函数介绍

1 recvfrom和sendto

recvfrom:

#include <sys/types.h>
#include <sys/socket.h>

ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);

sockfd:指定接收端套接字描述符;
buf:待接收数据的缓冲区;
len:待接收数据的缓冲区大小;
flags:一般设置为0,或者一个或多个为常值的逻辑或3

flags说明recvfromsend
MSG_DONTROUTE绕过路由表查找·
MSG_DONTWAIT仅本操作非阻塞··
MSG_OOB发送或接收带外数据··
MSG_PEEK窥看外来消息·
MSG_WAITAIL等待所有数据·

src_addr:用来指定欲接收的网络地址
addrlen:用来指定欲接收的网络地址的长度

返回值:成功则返回实际传送出去的字符数, 失败返回-1, 错误原因存于errno 中.

错误代码4
1、EBADF 参数s 非法的socket 处理代码.
2、EFAULT 参数中有一指针指向无法存取的内存空间.
3、WNOTSOCK canshu s 为一文件描述词, 非socket.
4、EINTR 被信号所中断.
5、EAGAIN 此动作会令进程阻断, 但参数s 的soket 为补课阻断的.
6、ENOBUFS 系统的缓冲内存不足.
7、EINVAL 传给系统调用的参数不正确.

sendto:

#include <sys/types.h>
#include <sys/socket.h>

ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);

sockfd:指定发送端套接字描述符;
buf:待发送数据的缓冲区;
len:要发送的数据的字节数;
flags:一般设置为0,或者一个或多个为常值的逻辑或,具体见上

src_addr:用来指定欲发送的网络地址
addrlen:用来指定欲发送的网络地址的长度

返回值:成功则返回实际传送出去的字符数, 失败返回-1, 错误原因存于errno 中。

错误代码5
1、EBADF 参数s 非法的socket 处理代码。
2、EFAULT 参数中有一指针指向无法存取的内存空间。
3、WNOTSOCK canshu s 为一文件描述词, 非socket。
4、EINTR 被信号所中断。
5、EAGAIN 此动作会令进程阻断, 但参数s 的soket 为补课阻断的。
6、ENOBUFS 系统的缓冲内存不足。
7、EINVAL 传给系统调用的参数不正确。

2 recv和send

recv:

 #include <sys/types.h>
 #include <sys/socket.h>
 
 ssize_t recv(int sockfd, void *buf, size_t len, int flags);

sockfd:指定接收端套接字描述符;
buf:待接收数据的缓冲区;
len:待接收数据的缓冲区大小;
flags:一般设置为0,或者一个或多个为常值的逻辑或,具体见上。

返回值:成功则返回接收到的字符数, 失败返回-1,错误原因存于errno 中。

错误代码6
EBADF 参数s 非合法的socket 处理代码。
EFAULT 参数中有一指针指向无法存取的内存空间。
ENOTSOCK 参数s 为一文件描述词, 非socket。
EINTR 被信号所中断。
EAGAIN 此动作会令进程阻断, 但参数s 的socket 为不可阻断。
ENOBUFS 系统的缓冲内存不足。
ENOMEM 核心内存不足。
EINVAL 传给系统调用的参数不正确。

send:

#include <sys/types.h>
#include <sys/socket.h>

ssize_t send(int sockfd, const void *buf, size_t len, int flags);

sockfd:指定发送端套接字描述符;
buf:待发送数据的缓冲区;
len:要发送的数据的字节数;
flags:一般设置为0,或者一个或多个为常值的逻辑或,具体见上。

返回值:成功则返回实际传送出去的字符数, 失败返回-1. 错误原因存于errno。

错误代码7
EBADF 参数s 非合法的socket 处理代码。
EFAULT 参数中有一指针指向无法存取的内存空间。
ENOTSOCK 参数s 为一文件描述词, 非socket。
EINTR 被信号所中断。
EAGAIN 此操作会令进程阻断, 但参数s 的socket 为不可阻断。
ENOBUFS 系统的缓冲内存不足。
ENOMEM 核心内存不足。
EINVAL 传给系统调用的参数不正确。

0x03 代码举例

举几个例子可以更好的体会send、sendto以及recv、recvfrom。

1 UDP下的recvfrom和sendto

UDP server:

#include<stdio.h>
#include<sys/socket.h>
#include<unistd.h>
#include<stdlib.h>
#include<arpa/inet.h>
#include<string.h>

int main()
{
	//创建套接字
	int fd = socket(AF_INET, SOCK_DGRAM, 0);
	//绑定
	struct sockaddr_in myaddr;
	myaddr.sin_family = AF_INET;
	myaddr.sin_port = htons(8000);
	myaddr.sin_addr.s_addr = inet_addr("127.0.0.1");//inet_addr只能用于IPV4,也可以用inet_pton IPV4 IPV6都可以
	int ret = bind(fd, (struct sockaddr*)&myaddr, sizeof(myaddr));
	if(ret < 0)
	{
		perror("");
		return 0;
	}
	//读写
	char buf[1500] = "";
	struct sockaddr_in cliaddr;
	socklen_t len = sizeof(cliaddr);
	while(1)
	{
		memset(buf, 0, sizeof(buf));
		int n = recvfrom(fd, buf, sizeof(buf), 0, (struct sockaddr*)&cliaddr, &len);
		if(n < 0)
		{
			perror("");
			break;
		}
		else
		{
			printf("%s\n", buf);
			sendto(fd, buf, n, 0, (struct sockaddr*)&cliaddr, len);
		}
	}
	//关闭
	close(fd);
	return 0;
}

UDP client:

#include<stdio.h>
#include<sys/socket.h>
#include<unistd.h>
#include<stdlib.h>
#include<arpa/inet.h>
#include<string.h>

int main()
{
	//创建套接字
	int fd = socket(AF_INET, SOCK_DGRAM, 0);

	//读写
	char buf[1500] = "";
	
	struct sockaddr_in dstaddr;
	dstaddr.sin_family = AF_INET;
	dstaddr.sin_port = htons(8000);
	dstaddr.sin_addr.s_addr = inet_addr("127.0.0.1");//inet_addr只能用于IPV4,也可以用inet_pton IPV4 IPV6都可以

	int n = 0;
	while(1)
	{
		read(STDIN_FILENO, buf, sizeof(buf));
		n = sendto(fd, buf, strlen(buf), 0, (struct sockaddr*)&dstaddr, sizeof(dstaddr));
		memset(buf, 0, sizeof(buf));
		
		n = recvfrom(fd, buf, sizeof(buf), 0, NULL, NULL);//保不保存对方服务器IP无所谓,因为自己知道已经知道对方服务器IP
		if(n < 0)
		{
			perror("");
		}
		else
		{
			printf("%s\n", buf);
		}
	}
	//关闭
	close(fd);
	return 0;
}

2 TCP下的recv和send

TCP server:

#include<stdio.h>
#include<stdlib.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<unistd.h>

int main()
{
	//创建套接字
	int lfd = socket(AF_INET, SOCK_STREAM, 0);
	//绑定
	struct sockaddr_in addr;
	addr.sin_family = AF_INET;
	addr.sin_port = htons(8000);
	addr.sin_addr.s_addr = INADDR_ANY; //绑定通配地址
	//inet_pton(AF_INET, "192.168.21.37", addr.sin_addr.s_addr);
	
	int ret = bind(lfd, (struct socketaddr*)&addr, sizeof(addr));
	if(ret < 0)
	{
		perror("");
		exit(0);
	}
	//监听
	listen(lfd,128);
	//提取(一个)
	struct sockaddr_in cliaddr;
	socklen_t len = sizeof(cliaddr);
	int cfd = accept(lfd, (struct sockaddr*)&cliaddr, &len);
	char ip[16] = {0};
	printf("new client ip = %s port = %d\n", inet_ntop(AF_INET, &cliaddr.sin_addr.s_addr, ip, 16), ntohs(cliaddr.sin_port));
	//读写
	char buf[1024] = {0};
	while(1)
	{
		//发送至客户端
		bzero(buf, sizeof(buf));
		int n = read(STDIN_FILENO, buf, sizeof(buf));
		send(cfd, buf, n, 0);
		
		//从客户端读取
		n = recv(cfd, buf, sizeof(buf), 0);
		if(0 == n)
		{
			printf("client closed\n");
			break;
		}
		printf("%s\n", buf);
	}
	//关闭
	close(lfd);
	close(cfd);
	
	return 0;
}

TCP client:

#include<stdio.h>
#include<stdlib.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<unistd.h>

int main(int argc, char* argv[])
{	
	//创建套接字
	int sock_fd;
	sock_fd = socket(AF_INET, SOCK_STREAM, 0);
	//连接服务器
	struct sockaddr_in addr;
	addr.sin_family = AF_INET;
	addr.sin_port = htons(8000);
	inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr.s_addr);
	connect(sock_fd,(struct sockaddr*)&addr, sizeof(addr));
	//读写数据
	char buf[1024] = {0};
	while(1)
	{
		int n = read(STDIN_FILENO, buf, sizeof(buf));
		send(sock_fd, buf, n, 0);//发送数据至服务器
		
		n = recv(sock_fd, buf, sizeof(buf), 0);
		send(STDOUT_FILENO, buf, n, 0);
	}
	//关闭
	close(sock_fd);
	return 0;
}

以上。


  1. 《UNIX网络编程 卷1:套接字联网API》(第3版) ,p186 ↩︎

  2. 《UNIX网络编程 卷1:套接字联网API》(第3版) ,p30 ↩︎

  3. 《UNIX网络编程 卷1:套接字联网API》(第3版) ,p305 ↩︎

  4. http://c.biancheng.net/cpp/html/368.html ↩︎

  5. http://c.biancheng.net/cpp/html/372.html ↩︎

  6. http://c.biancheng.net/cpp/html/367.html ↩︎

  7. http://c.biancheng.net/cpp/html/370.html ↩︎

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值