0x00 前言
文章中的文字可能存在语法错误以及标点错误,请谅解;
如果在文章中发现代码错误或其它问题请告知,感谢!
系统版本:Ubuntu 14.04_64
最后更新:2022-04-28
0x01 使用场景
对于TCP来说,一般使用send()
、recv()
函数进行数据读写。
对于UDP来说,一般使用sendto()
、recvfrom()
函数进行数据读写。
我们都知道,UDP是一个简单传输层协议,丢包不可重传。UDP提供的是无连接(connectionless)服务,UDP客户和服务器之间并不建立连接,所以双方之间每次发送数据都需要指定发送的目的地址以及端口信息1。sendto()
函数可以在参数中指定发送的目的地,可以让数据正确发送到对方。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 | 说明 | recvfrom | send |
---|---|---|---|
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;
}
以上。