概要
一旦,我们建立好了tcp连接之后,我们就可以把得到的fd当作文件描述符来使用。
一般情况下,
(1) send、recv 函数在TCP协议下使用,当你对于数据报socket调用了connect()函数时,你也可以利用send()和recv()进行数据传输,但该socket仍然是数据报socket,并且利用传输层的UDP服务。但在发送或接收数据报时,内核会自动为之加上目地和源地址信息。
(a) 在Unix系统下,如果send在等待协议传送数据时网络断开的话,调用send的进程会接收到一个SIGPIPE信号,进程对该信号的默认处理是进程终止。
(b) 在Unix系统下,如果recv函数在等待协议接收数据时网络断开了,那么调用recv的进程会接收到一个SIGPIPE信号,进程对该信号的默认处理是进程终止。
(2) sendto、recvfrom 函数在UDP协议下使用,但是如果在 TCP 中 connect 函数调用后也可以用,不过用的很少。
1 关于TCP/UDP套接口的发送缓冲区理解
(1) 下图展示了应用进程写数据到TCP套接口的过程
每一个TCP套接口有一个发送缓冲区,我们可以用SO_SNDBUF套接口选项来改变这个缓冲区的大小。当应用程序调用write时,内核从应用程序进程的缓冲区中拷贝所有数据到套接口的发送缓冲区。如果套接口的发送缓冲区容不下应用程序的所有数据(或是应用程序的缓冲区大于套接口发送缓冲区,或是套接口发送缓冲区还有其他数据),应用进程将被挂起(睡眠)。这里假设套接口是阻塞的,它是通常的缺省设置(还有非阻塞的套接口)。内核将不从write系统调用返回,直到应用程序缓冲区中的所有数据都拷贝到套接口发送缓冲区。因此从写一个TCP套接口的write调用成功返回仅仅表示我们可以重新使用应用进程的缓冲区。它并不告诉我们对端的TCP或应用程序已接收到数据。
TCP取套接口发送缓冲区的数据并把它发送给对端TCP,其过程基于TCP数据传送的所有规则。对端TCP必需确认收到数据,只有收到对端的ACK,本端TCP才能删除套接口发送缓冲区中已确认的数据。TCP必需保留数据拷贝直到对端确认为止。
TCP以MSS大小的或更小的块把数据传递给IP,同时给每个数据块安上一个TCP头部以构成TCP分节,其中的MSS是由对端通告的,当对端未通告时就用536这个值(IPv4的最小重组缓冲区字节数576减去IPv4头部字节20和TCP头部字节数20)。IP给每个TCP分节安上IP头部以构成IP数据报,查找其宿IP地址的路由表项以确定外出接口,然后把数据报传递给相应的数据链路。IP可能在把数据报传递给数据链路之前将其分片,不过我们已经谈到MSS选项的目的之一就是试图避免分片,而较新的实现又使用了路径MTU发现功能。每个数据链路都有一个输出队列,如果该队列已满,那么新到的分组将被丢弃,并沿协议栈向上返回一个错误,从链路层到IP层,再从IP层到TCP层。TCP将注意到这个错误,并在以后某个时刻重传相应分片。应用进程并不知道这种暂时情况。
(2) 下图展示了应用进程写数据到UDP套接口的过程
这一次我们展示的套接口发送缓冲区用虚线框,因为它并不存在。UDP套接口有发送缓冲区大小(我们可以用SO_SNDBUF套接口选项修改),不过它仅仅是写到套接口的UDP数据报的大小上限。如果应用进程写一个大于套接口发送缓冲区大小的数据包,内核将返回一个EMSGSIZE错误。既然UDP是不可靠的,它不必保存应用程序的数据拷贝,因此无需一个真正的发送缓冲区。(应用进程的数据在沿协议向下传递时,以某种形式拷贝到内核的缓冲区,然而数据链路层在送出这些数据后将丢弃该拷贝)
UDP简单地给用户数据报安上它的8个字节的头部以构成UDP数据报,然后传递给IP。IPv4或IPv6给UDP数据报安上相应的IP头部以构成IP数据报,执行路由操作确定外出接口,然后直接把数据包加入数据链路层输出队列(如果适合于MTU),或者分片后再把每个片加入数据链路层的输出队列。如果某个UDP应用进程发送大数据报,那么它比TCP应用进程更有可能分片,因为TCP会把应用数据划分成MSS大小的块,而UDP却没有对等的手段。
从写UDP套接口的write调用成功地返回表示用户写入的数据报或其所有片段已被加入数据链路层的输出队列。如果该队列没有足够的空间存放该数据报或它的某个片段,内核通常将给应用程序返回一个ENOBUFS错误。
2 read 和 write 函数
网络程序里最基本的函数就是 read 和 write 函数了。
2.1 写函数 write
ssize_t write(int fd, const void*buf,size_t nbytes);
write 函数将 buf 中的 nbytes 字节内容写入文件描述符 fd 。成功时返回写的字节数。失败时返回-1, 并设置 errno 变量。在网络程序中,当我们向套接字文件描述符写时有两可能。
(1) write的返回值大于0, 表示写了部分或者是全部的数据。这样我们用一个while循环来不停的写入,但是循环过程中的buf参数和nbyte参数得由我们来更新。也就是说,网络写函数是不负责将全部数据写完之后在返回的。
(2) 返回的值小于0,此时出现了错误。我们要根据错误类型来处理。如果错误为EINTR表示在写的时候出现了中断错误。如果为EPIPE表示网络连接出现了问题(对方已经关闭了连接)。
为了处理以上的情况,我们自己编写一个写函数来处理这几种情况.
int my_write(int fd, void *buffer, int length)
{
int bytes_left = 0;
int written_bytes = 0;
char *ptr = NULL;
ptr = buffer;
bytes_left = length;
while (bytes_lef > 0 )
{
written_bytes = write(fd, ptr, bytes_left);
if(written_bytes <= 0)
{
if(errno == EINTR)
{
written_bytes = 0;
}
else
{
return -1;
}
}
bytes_left -= written_bytes;
ptr += written_bytes;
}
return 0;
}
2.2 读函数 read
ssize_t read(int fd, void *buf, size_t nbyte);
read 函数是负责从 fd 中读取内容。
(1) 当读成功时, read返回实际所读的字节数, 如果返回的值是0 表示已经读到文件的结束了。
(2) 小于0表示出现了错误。如果错误为 EINTR 说明读是由中断引起 的, 如果是 ECONNREST 表示网络连接出了问题。和上面一样, 我们也写一个自己的读函数。
int my_read(int fd, void *buffer, int length)
{
int bytes_left = 0;
int bytes_read = 0;
char *ptr = NULL;
bytes_left = length;
while(bytes_left > 0)
{
bytes_read = read(fd, ptr, bytes_read);
if(bytes_read < 0)
{
if(errno == EINTR)
{
bytes_read=0;
}
else
{
return -1;
}
}
else if(bytes_read == 0)
{
break;
}
bytes_left -= bytes_read;
ptr += bytes_read;
}
return (length - bytes_left);
}
2.3 数据的传递
有了上面的两个函数, 我们就可以向客户端或者是服务端传递数据了。比如我们要传递一个结构, 可以使用如下方式
struct my_struct my_struct_client;
write(fd, (void *)&my_struct_client, sizeof(struct my_struct);
char buffer[sizeof(struct my_struct)];
struct *my_struct_server = NULL;
read(fd, (void *)buffer ,sizeof(struct my_struct));
my_struct_server = (struct my_struct *)buffer;
在网络上传递数据时我们一般都是把数据转化为char类型的数据传递.接收的时候也是一样的注意的是我们没有必要在网络上传递指针(因为传递指针是没有任何意义的,我们必须传递指针所指向的内容)。
2.4 read 和 write 实例分析
使用 read 和 write 进行 tcp socket 的读和写功能。
(1) tcp 连接断开
(a) 在Unix系统下,如果write/send在等待协议传送数据时网络断开的话,调用send的进程会接收到一个SIGPIPE信号,进程对该信号的默认处理是进程终止。
(b) 在Unix系统下,如果read/recv函数在等待协议接收数据时网络断开了,那么调用recv的进程会接收到一个SIGPIPE信号,进程对该信号的默认处理是进程终止。
2.4.1 unix socket 实例
(1) client.cpp
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/un.h>
#include <errno.h>
#include <stdarg.h>
#include <unistd.h>
#include <pthread.h>
#include <malloc.h>
#include <arpa/inet.h>
//unix socket文件路径
#define SERVER_UNIX_SOCKET_FILE "/tmp/server_unix_socket"
//客户端socket发送缓冲区设置
#define SOCKET_BUFF (1024)
/*
* 功能: 客户端发送数据给服务端接口
* 参数1:服务端监听的文件路径
* 参数2:要发送的数据
* 参数3:要发送的数据长度
* 参数4: 接收的数据存放到数组
* 参数5: 接收数组大小
* 返回值:>=0表示成功, <0表示失败
*/
int socketSendData(const char* server_file,
const char* send_data,
const int send_data_size,
char* recv_data,
const int recv_data_size)
{
// 参数空值检测
if(NULL == server_file || NULL == send_data || NULL == recv_data)
{
printf("socketSendData server_file or send_data or recv_data is null!\n");
return -1;
}
// 参数大小检测
if(0 >= send_data_size || SOCKET_BUFF <= send_data_size ||
0 >= recv_data_size)
{
printf("socketSendData send_data_size(%d) or recv_data_size(%d) error!\n",
send_data_size, recv_data_size);
return -1;
}
// 创建客户端socket
int client_socket = socket(AF_UNIX, SOCK_STREAM, 0);
if(0 > client_socket)
{
printf("socketSendData create socket err(%d)!\n", errno);
return -1;
}
// 构造服务端地址
struct sockaddr_un server_addr;
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sun_family = AF_UNIX;
strncpy(server_addr.sun_path, server_file, sizeof(server_addr.sun_path) - 1);
if(connect(client_socket, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0)
{
printf("socketSendData tcp connect err(%d)!\n", errno);
close(client_socket);
return -1;
}
// 发送数据包
int len = write(client_socket, send_data, send_data_size);
if(len != send_data_size)
{
printf("socketSendData socket send err(%d)!\n", errno);
close(client_socket);
return -1;
}
// 接收返回数据
len = read(client_socket, recv_data, recv_data_size - 1);
if(len <= 0)
{
printf("socketSendData socket recv err(%d)!\n", errno);
close(client_socket);
return -1;
}
printf("socketSendData recv data(%s)!\n", recv_data);
close(client_socket);
return 0;
}
int main()
{
char send_data[SOCKET_BUFF] = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
char recv_data[SOCKET_BUFF] = {0};
socketSendData(SERVER_UNIX_SOCKET_FILE,
send_data,
strlen(send_data),
recv_data,
SOCKET_BUFF);
return 0;
}
(2) server.cpp
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/un.h>
#include <errno.h>
#include <stdarg.h>
#include <unistd.h>
#include <pthread.h>
#include <malloc.h>
#include <signal.h>
#include <arpa/inet.h>
//unix socket文件路径
#define SERVER_UNIX_SOCKET_FILE "/tmp/server_unix_socket"
//unix socket文件路径长度
#define UNIX_SOCK_FILE_LEN (64)
//客户端socket发送缓冲区设置
#define SOCKET_BUFF (1024)
//设备信息
#define JSON_DATA_1 "{" \
"\"method\": \"HOST_GET_Ret_Eui64\"," \
"\"longAddress\":[" \
"{\"eui64\": \"AAA\", \"type\": 1, \"deviceEndpoint\":1}," \
"{\"eui64\": \"BBB\", \"type\": 2, \"deviceEndpoint\":2}]" \
"}"
/*
* 功能: socket服务器
*/
void tcpServer()
{
int i = 0;
int j = 0;
int ret = 0;
int listen_socket = -1;
int client_socket = -1;
struct sockaddr_un server_addr;
struct sockaddr_un client_addr;
char u8RecvBuf[SOCKET_BUFF] = {0};
unsigned int client_addr_len = sizeof(client_addr);
printf("tcpServer start\n");
// 创建套接字
listen_socket = socket(AF_UNIX, SOCK_STREAM, 0);
if(listen_socket < 0)
{
printf("tcpServer socket error!\n");
exit(1);
}
// 绑定
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sun_family = AF_UNIX;
strncpy(server_addr.sun_path, SERVER_UNIX_SOCKET_FILE, sizeof(server_addr.sun_path)-1);
unlink(SERVER_UNIX_SOCKET_FILE);
if((bind(listen_socket, (struct sockaddr *)&server_addr, sizeof(server_addr))) < 0)
{
printf("tcpServer bind error(%d)!\n", errno);
close(listen_socket);
exit(1);
}
// 开始监听
if(listen(listen_socket, 1) < 0)
{
printf("tcpServer listen error !");
close(listen_socket);
unlink(SERVER_UNIX_SOCKET_FILE);
exit(1);
}
// 忽略SIGPIPE信号,防止发送数据时客户端关闭导致服务端退出
signal(SIGPIPE, SIG_IGN);
// 循环接收数据
while (1)
{
memset(&client_addr, 0, sizeof(client_addr));
client_socket = accept(listen_socket, (struct sockaddr*)&client_addr, &client_addr_len);
if(client_socket < 0)
{
printf("tcpServer accept error!");
continue;
}
printf("tcpServer Accept a connection: client(%d), sun_family(%d), sun_path(%s)!\n",
client_socket, client_addr.sun_family, client_addr.sun_path);
ret = read(client_socket, u8RecvBuf, sizeof(u8RecvBuf));
if(ret < 0)
{
if(errno == EAGAIN || errno == EINTR)
{
// 超时或者信号中断属于正常情况
printf("tcpServer recv timeout or intrrupt signal continue recv!\n");
// 关闭客户端
close(client_socket);
continue;
}
// socket接收出错
printf("tcpServer socket recv err(%d)\n", errno);
// 关闭客户端
close(client_socket);
continue;
}
else if (ret == 0)
{
// 对方关闭socket连接
printf("tcpServer ret(%d) and client(%d) close!\n", ret, client_socket);
// 关闭客户端
close(client_socket);
}
else
{
if (ret < SOCKET_BUFF)
{
memset(&u8RecvBuf[ret], '\0', 1);
}
printf("tcpServer client(%d) recv(%s)!\n", client_socket, u8RecvBuf);
// 发送数据到客户端,这里开始处理接收的数据
// 返回客户端数据
write(client_socket, JSON_DATA_1, strlen(JSON_DATA_1) + 1);
// 关闭客户端
close(client_socket);
}
}
// 关闭服务端
close(listen_socket);
unlink(SERVER_UNIX_SOCKET_FILE);
return;
}
int main()
{
tcpServer();
return 0;
}
编译
[root@localhost thread]# g++ -o client ./client.cpp
[root@localhost thread]#
[root@localhost thread]#
[root@localhost thread]# g++ -o server ./server.cpp
[root@localhost thread]#
先运行 server
[root@localhost thread]# ./server
tcpServer start
再另外一个终端运行 client
[root@localhost thread]# ./client
socketSendData recv data({"method": "HOST_GET_Ret_Eui64","longAddress":[{"eui64": "AAA", "type": 1, "deviceEndpoint":1},{"eui64": "BBB", "type": 2, "deviceEndpoint":2}]})!
服务端运行结果
[root@localhost thread]# ./server
tcpServer start
tcpServer Accept a connection: client(4), sun_family(1), sum_path(S)!
tcpServer client(4) recv(aaaaaaaaaaaaaaaaaaaaaaaaaaaaa)!
2.4.2 ip network socket 实例
(1) client.cpp
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/un.h>
#include <errno.h>
#include <stdarg.h>
#include <unistd.h>
#include <pthread.h>
#include <malloc.h>
#include <arpa/inet.h>
//unix socket文件路径
#define SERVER_UNIX_SOCKET_FILE "/tmp/server_unix_socket"
//客户端socket发送缓冲区设置
#define SOCKET_BUFF (1024)
/*
* 功能: 客户端发送数据给服务端接口
* 参数1:服务端监听的文件路径
* 参数2:要发送的数据
* 参数3:要发送的数据长度
* 参数4: 接收的数据存放到数组
* 参数5: 接收数组大小
* 返回值:>=0表示成功, <0表示失败
*/
int socketSendData(const char* server_file,
const char* send_data,
const int send_data_size,
char* recv_data,
const int recv_data_size)
{
// 参数空值检测
if(NULL == server_file || NULL == send_data || NULL == recv_data)
{
printf("socketSendData server_file or send_data or recv_data is null!\n");
return -1;
}
// 参数大小检测
if(0 >= send_data_size || SOCKET_BUFF <= send_data_size ||
0 >= recv_data_size)
{
printf("socketSendData send_data_size(%d) or recv_data_size(%d) error!\n",
send_data_size, recv_data_size);
return -1;
}
// 创建客户端socket
int client_socket = socket(AF_INET, SOCK_STREAM, 0);
if(0 > client_socket)
{
printf("socketSendData create socket err(%d)!\n", errno);
return -1;
}
// 构造服务端地址
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(30);
server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
if(connect(client_socket, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0)
{
printf("socketSendData tcp connect err(%d)!\n", errno);
close(client_socket);
return -1;
}
// 发送数据包
int len = write(client_socket, send_data, send_data_size);
if(len != send_data_size)
{
printf("socketSendData socket send err(%d)!\n", errno);
close(client_socket);
return -1;
}
// 接收返回数据
len = read(client_socket, recv_data, recv_data_size - 1);
if(len <= 0)
{
printf("socketSendData socket recv err(%d)!\n", errno);
close(client_socket);
return -1;
}
printf("socketSendData recv data(%s)!\n", recv_data);
close(client_socket);
return 0;
}
int main()
{
char send_data[SOCKET_BUFF] = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
char recv_data[SOCKET_BUFF] = {0};
socketSendData(SERVER_UNIX_SOCKET_FILE,
send_data,
strlen(send_data),
recv_data,
SOCKET_BUFF);
return 0;
}
(2) server.cpp
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/un.h>
#include <errno.h>
#include <stdarg.h>
#include <unistd.h>
#include <pthread.h>
#include <malloc.h>
#include <signal.h>
#include <arpa/inet.h>
//客户端socket发送缓冲区设置
#define SOCKET_BUFF (1024)
//设备信息
#define JSON_DATA_1 "{" \
"\"method\": \"HOST_GET_Ret_Eui64\"," \
"\"longAddress\":[" \
"{\"eui64\": \"AAA\", \"type\": 1, \"deviceEndpoint\":1}," \
"{\"eui64\": \"BBB\", \"type\": 2, \"deviceEndpoint\":2}]" \
"}"
/*
* 功能: socket服务器
*/
void tcpServer()
{
int i = 0;
int j = 0;
int ret = 0;
int listen_socket = -1;
int client_socket = -1;
struct sockaddr_in server_addr;
struct sockaddr_in client_addr;
char u8RecvBuf[SOCKET_BUFF] = {0};
unsigned int client_addr_len = sizeof(client_addr);
printf("tcpServer start\n");
// 创建套接字
listen_socket = socket(AF_INET, SOCK_STREAM, 0);
if(listen_socket < 0)
{
printf("tcpServer socket error!\n");
exit(1);
}
// 绑定
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(30);
server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
if((bind(listen_socket, (struct sockaddr *)&server_addr, sizeof(server_addr))) < 0)
{
printf("tcpServer bind error(%d)!\n", errno);
close(listen_socket);
exit(1);
}
// 开始监听
if(listen(listen_socket, 1) < 0)
{
printf("tcpServer listen error !");
close(listen_socket);
exit(1);
}
// 忽略SIGPIPE信号,防止发送数据时客户端关闭导致服务端退出
signal(SIGPIPE, SIG_IGN);
// 循环接收数据
while (1)
{
memset(&client_addr, 0, sizeof(client_addr));
client_socket = accept(listen_socket, (struct sockaddr*)&client_addr, &client_addr_len);
if(client_socket < 0)
{
printf("tcpServer accept error!");
continue;
}
printf("tcpServer Accept a connection: client(%d), sin_family(%d), sin_port(%d)!\n",
client_socket, client_addr.sin_family, client_addr.sin_port);
ret = read(client_socket, u8RecvBuf, sizeof(u8RecvBuf));
if(ret < 0)
{
if(errno == EAGAIN || errno == EINTR)
{
// 超时或者信号中断属于正常情况
printf("tcpServer recv timeout or intrrupt signal continue recv!\n");
// 关闭客户端
close(client_socket);
continue;
}
// socket接收出错
printf("tcpServer socket recv err(%d)\n", errno);
// 关闭客户端
close(client_socket);
continue;
}
else if (ret == 0)
{
// 对方关闭socket连接
printf("tcpServer ret(%d) and client(%d) close!\n", ret, client_socket);
// 关闭客户端
close(client_socket);
}
else
{
if (ret < SOCKET_BUFF)
{
memset(&u8RecvBuf[ret], '\0', 1);
}
printf("tcpServer client(%d) recv(%s)!\n", client_socket, u8RecvBuf);
// 发送数据到客户端,这里开始处理接收的数据
// 返回客户端数据
write(client_socket, JSON_DATA_1, strlen(JSON_DATA_1) + 1);
// 关闭客户端
close(client_socket);
}
}
// 关闭服务端
close(listen_socket);
return;
}
int main()
{
tcpServer();
return 0;
}
3 readv 和 writev 函数
readv 和 writev 函数是 Linux 中的两个系统调用,类似于 read 和 write 函数,不同的是,readv 和 writev 在一次执行过程中可以原子地作用于多个缓冲区,这些缓冲区常常是非连续的。readv和writev的原型如下:
#include <sys/uio.h>
ssize_t readv(int fd, const struct iovec *iov, int iovcnt);
ssize_t writev(int fd, const struct iovec *iov, int iovcnt);
struct iovec {
void *iov_base; /* Starting address */
size_t iov_len; /* Number of bytes to transfer */
};
readv 和 writev 的第一个参数 fd 是个文件描述符,第二个参数是指向 iovec 数据结构的一个指针,其中 iov_base 为缓冲区首地址,iov_len 为缓冲区长度,参数 iovcnt 指定了 iovec 的个数。函数调用成功时返回读、写的总字节数,失败时返回-1并设置相应的 errno。
在一次函数调用中,writev 以顺序 iov[0]、iov[1] 至 iov[iovcnt-1] 从各缓冲区中聚集输出数据到 fd,readv 则将从 fd 读入的数据按同样的顺序散布到各缓冲区中,readv 总是先填满一个缓冲区,然后再填下一个,因此,writev 称为 gather output,readv 称为 scatter input (分散读 readv 和集中写 writev)。
(1) 先来看一个writev的例子,指定了两个缓冲区,str0和str1,内容输出到标准输出,并打印实际输出的字节数。
// writev.cpp
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/uio.h>
int main()
{
char *str0 = "hello ";
char *str1 = "world\n";
struct iovec iov[2];
ssize_t nwritten;
iov[0].iov_base = str0;
iov[0].iov_len = strlen(str0);
iov[1].iov_base = str1;
iov[1].iov_len = strlen(str1);
nwritten = writev(STDOUT_FILENO, iov, 2);
printf("%ld bytes written.\n", nwritten);
exit(EXIT_SUCCESS);
}
编译运行输出
[root@localhost thread]# g++ -o writev ./writev.cpp
[root@localhost thread]#
[root@localhost thread]# ./writev
hello world
12 bytes written.
[root@localhost thread]#
(2) 再来看一个readv的例子,从标准输入读数据,缓冲区为长度是(8 - 1)的buf1和buf2,并打印读到的字节总数和两个缓冲区各自的内容。
// readv.cpp
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/uio.h>
int main()
{
char buf1[8] = { 0 };
char buf2[8] = { 0 };
struct iovec iov[2];
ssize_t nread;
iov[0].iov_base = buf1;
iov[0].iov_len = sizeof(buf1) - 1;
iov[1].iov_base = buf2;
iov[1].iov_len = sizeof(buf2) - 1;
nread = readv(STDIN_FILENO, iov, 2);
printf("%ld bytes read.\n", nread);
printf("buf1: %s\n", buf1);
printf("buf2: %s\n", buf2);
exit(EXIT_SUCCESS);
}
编译运行输出
[root@localhost thread]# g++ -o readv ./readv.cpp
[root@localhost thread]#
[root@localhost thread]#
[root@localhost thread]# ./readv
helloreadv
11 bytes read.
buf1: hellore
buf2: adv
4 recv 和 send 函数
4.1 概念
recv 和 send 函数提供了和 read 和 write 差不多的功能。不过它们提供了第四个参数来控制读写操作。一般第四个参数设置为0即可。
int recv(int sockfd, void *buf, int len, int flags);
int send(int sockfd, void *buf, int len, int flags);
前面的三个参数和read,write一样,第四个参数可以是0或者是以下的组合
_______________________________________________________________
| MSG_DONTROUTE | 不查找表
| MSG_OOB | 接受或者发送带外数据
| MSG_PEEK | 查看数据,并不从系统缓冲区移走数据
| MSG_WAITALL | 等待所有数据
|--------------------------------------------------------------
(1) recv 对应的flags有3个选项
(a) MSG_PEEK :查看数据,并不从系统缓冲区移走数据。
(b) MSG_WAITALL :等待所有数据,等到所有的信息到达时才返回,使用它时,recv 返回一直阻塞,直到指定的条件满足时,或者发生错误。
(c) MSG_OOB :接受或者发送带外数据。
(2) send 对应的flags有2个选项
(a) MSG_DONTROUTE :不查找表,它告诉ip,目的主机在本地网络上,没必要查找表。(一般用在网络诊断和路由程序里面)。
(b) MSG_OOB :接受或者发送带外数据。
4.2 分析
//! recv 在 read 上增加了第四个参数, MSG_PEEK 是不清空缓冲区
//! 这个函数的作用相当是先去瞟一眼缓冲区,看见有多少字符,返回这个字符数
ssize_t recv_peek(int sockfd ,void *buf, size_t len)
{
while(1)
{
int ret = recv(sockfd, buf, len, MSG_PEEK);
if(ret == -1 && errno == EINTR)
{
continue;
}
return ret;
}
}
4.3 注意事项
(1) send、recv 函数在TCP协议下使用,当你对于数据报 socket 调用了connect()函数时,你也可以利用send()和recv()进行数据传输,但该socket仍然是数据报socket,并且利用传输层的UDP服务。但在发送或接收数据报时,内核会自动为之加上目地和源地址信息。
4.4 实例
(1) client.cpp
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/un.h>
#include <errno.h>
#include <stdarg.h>
#include <unistd.h>
#include <pthread.h>
#include <malloc.h>
#include <arpa/inet.h>
//unix socket文件路径
#define SERVER_UNIX_SOCKET_FILE "/tmp/server_unix_socket"
//客户端socket发送缓冲区设置
#define SOCKET_BUFF (1024)
/*
* 功能: 客户端发送数据给服务端接口
* 参数1:服务端监听的文件路径
* 参数2:要发送的数据
* 参数3:要发送的数据长度
* 参数4: 接收的数据存放到数组
* 参数5: 接收数组大小
* 返回值:>=0表示成功, <0表示失败
*/
int socketSendData(const char* server_file,
const char* send_data,
const int send_data_size,
char* recv_data,
const int recv_data_size)
{
// 参数空值检测
if(NULL == server_file || NULL == send_data || NULL == recv_data)
{
printf("socketSendData server_file or send_data or recv_data is null!\n");
return -1;
}
// 参数大小检测
if(0 >= send_data_size || SOCKET_BUFF <= send_data_size ||
0 >= recv_data_size)
{
printf("socketSendData send_data_size(%d) or recv_data_size(%d) error!\n",
send_data_size, recv_data_size);
return -1;
}
// 创建客户端socket
int client_socket = socket(AF_INET, SOCK_STREAM, 0);
if(0 > client_socket)
{
printf("socketSendData create socket err(%d)!\n", errno);
return -1;
}
// 构造服务端地址
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(30);
server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
if(connect(client_socket, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0)
{
printf("socketSendData tcp connect err(%d)!\n", errno);
close(client_socket);
return -1;
}
// 发送数据包
int len = send(client_socket, send_data, send_data_size, 0);
if(len != send_data_size)
{
printf("socketSendData socket send err(%d)!\n", errno);
close(client_socket);
return -1;
}
// 接收返回数据
len = recv(client_socket, recv_data, recv_data_size - 1, 0);
if(len <= 0)
{
printf("socketSendData socket recv err(%d)!\n", errno);
close(client_socket);
return -1;
}
printf("socketSendData recv data(%s)!\n", recv_data);
close(client_socket);
return 0;
}
int main()
{
char send_data[SOCKET_BUFF] = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
char recv_data[SOCKET_BUFF] = {0};
socketSendData(SERVER_UNIX_SOCKET_FILE,
send_data,
strlen(send_data),
recv_data,
SOCKET_BUFF);
return 0;
}
(2) server.cpp
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/un.h>
#include <errno.h>
#include <stdarg.h>
#include <unistd.h>
#include <pthread.h>
#include <malloc.h>
#include <signal.h>
#include <arpa/inet.h>
//客户端socket发送缓冲区设置
#define SOCKET_BUFF (1024)
//设备信息
#define JSON_DATA_1 "{" \
"\"method\": \"HOST_GET_Ret_Eui64\"," \
"\"longAddress\":[" \
"{\"eui64\": \"AAA\", \"type\": 1, \"deviceEndpoint\":1}," \
"{\"eui64\": \"BBB\", \"type\": 2, \"deviceEndpoint\":2}]" \
"}"
/*
* 功能: socket服务器
*/
void tcpServer()
{
int i = 0;
int j = 0;
int ret = 0;
int listen_socket = -1;
int client_socket = -1;
struct sockaddr_in server_addr;
struct sockaddr_in client_addr;
char u8RecvBuf[SOCKET_BUFF] = {0};
unsigned int client_addr_len = sizeof(client_addr);
printf("tcpServer start\n");
// 创建套接字
listen_socket = socket(AF_INET, SOCK_STREAM, 0);
if(listen_socket < 0)
{
printf("tcpServer socket error!\n");
exit(1);
}
// 绑定
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(30);
server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
if((bind(listen_socket, (struct sockaddr *)&server_addr, sizeof(server_addr))) < 0)
{
printf("tcpServer bind error(%d)!\n", errno);
close(listen_socket);
exit(1);
}
// 开始监听
if(listen(listen_socket, 1) < 0)
{
printf("tcpServer listen error !");
close(listen_socket);
exit(1);
}
// 忽略SIGPIPE信号,防止发送数据时客户端关闭导致服务端退出
signal(SIGPIPE, SIG_IGN);
// 循环接收数据
while (1)
{
memset(&client_addr, 0, sizeof(client_addr));
client_socket = accept(listen_socket, (struct sockaddr*)&client_addr, &client_addr_len);
if(client_socket < 0)
{
printf("tcpServer accept error!");
continue;
}
printf("tcpServer Accept a connection: client(%d), sin_family(%d), sin_port(%d)!\n",
client_socket, client_addr.sin_family, client_addr.sin_port);
ret = recv(client_socket, u8RecvBuf, sizeof(u8RecvBuf), 0);
if(ret < 0)
{
if(errno == EAGAIN || errno == EINTR)
{
// 超时或者信号中断属于正常情况
printf("tcpServer recv timeout or intrrupt signal continue recv!\n");
// 关闭客户端
close(client_socket);
continue;
}
// socket接收出错
printf("tcpServer socket recv err(%d)\n", errno);
// 关闭客户端
close(client_socket);
continue;
}
else if (ret == 0)
{
// 对方关闭socket连接
printf("tcpServer ret(%d) and client(%d) close!\n", ret, client_socket);
// 关闭客户端
close(client_socket);
}
else
{
if (ret < SOCKET_BUFF)
{
memset(&u8RecvBuf[ret], '\0', 1);
}
printf("tcpServer client(%d) recv(%s)!\n", client_socket, u8RecvBuf);
// 发送数据到客户端,这里开始处理接收的数据
// 返回客户端数据
send(client_socket, JSON_DATA_1, strlen(JSON_DATA_1) + 1, 0);
// 关闭客户端
close(client_socket);
}
}
// 关闭服务端
close(listen_socket);
return;
}
int main()
{
tcpServer();
return 0;
}
5 sendto 和 recvfrom 函数
5.1 sendto 函数
用来将数据由指定的socket传给对方主机。
在无连接的数据报socket方式下,由于本地socket并没有与远端机器建立连接,所以在发送数据时应指明目的地址。
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
sockfd: 发送端套接字描述符(非监听描述符)
buf: 应用要发送数据的缓存
len: 实际要发送的数据长度
flag: 一般设置为0
dest_addr: 发送数据的目的地址
addrlen: 发送地址长度, 通常为 sizeof(sockaddr) 的长度
通常用于UDP套接口,用数据报方式进行数据传输。由于无连接的数据报模式下,没有建立连接,需指明目的地址,addrlen通常为sizeof(sockaddr)的长度。成功时返回发送的字节数,失败返回-1。
当本地与不同目的地址通信时,只需指定目的地址,可使用同一个UDP套接口描述符sockfd,而TCP要预先建立连接,每个连接都会产生不同的套接口描述符,体现在:客户端要使用不同的fd进行connect,服务端每次accept产生不同的fd。
因为UDP没有真正的发送缓冲区,因为是不可靠连接,不必保存应用进程的数据拷贝,应用进程中的数据在沿协议栈向下传递时,以某种形式拷贝到内核缓冲区,当数据链路层把数据传出后就把内核缓冲区中数据拷贝删除。因此它不需要一个发送缓冲区。写UDP套接口的sendto/write返回表示应用程序的数据或数据分片已经进入链路层的输出队列,如果输出队列没有足够的空间存放数据,将返回错误ENOBUFS.
5.2 recvfrom 函数
用来接收远程主机经指定的 socket 传来的数据, 并把数据传到由参数 buf 指向的内存空间, 参数 len 为可接收数据的最大长度。
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
sockfd: 接收端套接字描述
buf: 用于接收数据的应用缓冲区地址
len: 指名缓冲区大小
flags: 通常为0
src_addr: 数据来源端的地址
addrlen: src_addr 地址的长度
注意后两个参数是输出参数,如果不关心数据发送端的地址,可以将后两者均设置为NULL。
本文参考:
https://blog.csdn.net/petershina/article/details/7946615
https://blog.csdn.net/zhubosa/article/details/38295571
https://blog.csdn.net/G_BrightBoy/article/details/12854117
https://blog.csdn.net/iEearth/article/details/46730669