可以用以下三个函数发送数据。每个函数都是单独使用的,使用的场景略有不同。
ssize_t write (int socketfd, const void *buffer, size_t size); #include <sys/socket.h> ssize_t send (int socketfd, const void *buffer, size_t size, int flags); ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
write
就是常见的文件写函数(Linux中一切皆为文件)。
send
可以指定选项,发送带外数据(一种基于 TCP 协议的紧急数据,用于客户端 - 服务器在特定场景下的紧急处理)。
sendmsg
指定多重缓冲区传输数据,以结构体 msghdr 的方式发送数据。
msghdr定义:
struct iovec { /* Scatter/gather array items */ void *iov_base; /* Starting address */ size_t iov_len; /* Number of bytes to transfer */ }; struct msghdr { void *msg_name; /* Optional address */ socklen_t msg_namelen; /* Size of address */ struct iovec *msg_iov; /* Scatter/gather array */ size_t msg_iovlen; /* # elements in msg_iov */ void *msg_control; /* Ancillary data, see below */ size_t msg_controllen; /* Ancillary data buffer len */ int msg_flags; /* Flags on received message */ };
当 TCP 三次握手成功,TCP 连接成功建立后,操作系统内核会为每一个连接创建配套的基础设施,比如发送缓冲区。
发送缓冲区的大小可以通过套接字选项来改变,当应用程序调用 write 函数时,实际所做的事情是把数据从应用程序中拷贝到操作系统内核的发送缓冲区中,并不一定是把数据通过套接字写出去。
wirte()
的返回时机:直到某一个时刻,应用程序的数据可以完全放置到发送缓冲区里,write()
阻塞调用返回。
ssize_t read (int socketfd, void *buffer, size_t size); #include <sys/socket.h> ssize_t recv(int sockfd, void *buf, size_t len, int flags); ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
read
函数要求操作系统内核从套接字描述字 socketfd 读取最多多少个字节(size),并将结果存储到 buffer 中。
返回值表示实际读取的字节数目,如果返回值为 0,表示 EOF(end-of-file),这在网络中表示对端发送了 FIN 包,要处理断连的情况;如果返回值为 -1,表示出错。
read
是最多读取 size 个字节。循环读取,每次都读到 size 个字节的函数:
size_t readn(int fd, void *buffer, size_t size) { char *buffer_pointer = (char *)buffer; int length = size; while (length > 0) { int result = read(fd, buffer_pointer, length); if (result < 0) { if (errno == EINTR) continue; /* 考虑非阻塞的情况,这里需要再次调用read */ else return (-1); } else if (result == 0) break; /* EOF(End of File)表示套接字关闭 */ length -= result; buffer_pointer += result; } return (size - length); /* 返回的是实际读取的字节数*/ }
common.h :
#pragma once #include <iostream> #include <stdlib.h> #include <stdarg.h> #include <string.h> #include <string> #include <unistd.h> #include <errno.h> #include <netinet/in.h> #include <sys/socket.h> #include <arpa/inet.h> /* error - print a diagnostic and optionally exit */ void error(int status, int err, char *fmt, ...) { va_list ap; va_start(ap, fmt); vfprintf(stderr, fmt, ap); va_end(ap); if (err) { fprintf(stderr, ": %s (%d)\n", strerror(err), err); } if (status) { exit(status); } } int tcp_client(char *address, int port) { int socket_fd; socket_fd = socket(AF_INET, SOCK_STREAM, 0); struct sockaddr_in server_addr; bzero(&server_addr, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_port = htons(port); inet_pton(AF_INET, address, &server_addr.sin_addr); socklen_t server_len = sizeof(server_addr); int connect_rt = connect(socket_fd, (struct sockaddr *) &server_addr, server_len); if (connect_rt < 0) { error(1, errno, "connect failed "); } return socket_fd; } size_t readn(int fd, void *buffer, size_t size) { char *buffer_pointer = (char *)buffer; int length = size; while (length > 0) { int result = read(fd, buffer_pointer, length); if (result < 0) { if (errno == EINTR) continue; /* 考虑非阻塞的情况,这里需要再次调用read */ else return (-1); } else if (result == 0) break; /* EOF(End of File)表示套接字关闭 */ length -= result; buffer_pointer += result; } return (size - length); /* 返回的是实际读取的字节数*/ }
Client:
#include "tcp_send_read/common/commom.h" # define MESSAGE_SIZE 1024 * 1024 * 100 void send_data(int sockfd) { char *query; query = (char *)malloc(MESSAGE_SIZE + 1); for (int i = 0; i < MESSAGE_SIZE; i++) { query[i] = 'a'; } query[MESSAGE_SIZE] = '\0'; const char *cp; cp = query; size_t remaining = strlen(query); while (remaining) { int n_written = send(sockfd, cp, remaining, 0); fprintf(stdout, "send into buffer %ld \n", n_written); if (n_written <= 0) { error(1, errno, "send failed"); return; } remaining -= n_written; cp += n_written; } return; } int main(int argc, char **argv) { struct sockaddr_in servaddr; if (argc != 2) error(1, 0, "usage: tcpclient <IPaddress>"); int sockfd = tcp_client(argv[1], 12345); send_data(sockfd); exit(0); }
Server:
#include "tcp_send_read/common/commom.h" #define BUFFER_SIZE 1024 * 1024 void read_data(int sockfd) { ssize_t n; char buf[BUFFER_SIZE]; int time = 0; for (;;) { fprintf(stdout, "block in read\n"); if ((n = readn(sockfd, buf, BUFFER_SIZE)) == 0) { // EOF fprintf(stdout, "read 0, close it!\n"); fflush(stdout); // must flust it! return; } time++; fprintf(stdout, "1M read for %d \n", time); usleep(2000); } } int main(int argc, char **argv) { int listenfd, connfd; socklen_t clilen; struct sockaddr_in cliaddr, servaddr; listenfd = socket(AF_INET, SOCK_STREAM, 0); bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(12345); /* bind到本地地址,端口为12345 */ bind(listenfd, (struct sockaddr *) &servaddr, sizeof(servaddr)); /* listen的backlog为1024 */ listen(listenfd, 1024); /* 循环处理用户请求 */ for (;;) { clilen = sizeof(cliaddr); connfd = accept(listenfd, (struct sockaddr *) &cliaddr, &clilen); read_data(connfd); /* 读取数据 */ close(connfd); /* 关闭连接套接字,注意不是监听套接字*/ } }
此程序能说明几个问题:
阻塞式套接字最终发送返回的实际写入字节数和请求字节数是相等的
对于 send 来说,返回成功仅表示数据写到发送缓冲区成功,并不意味着对端已经成功收到(对端何时收到,对发送者透明)
[1] 极客时间 · 网络编程实战 :05 | 使用套接字进行读写:开始交流吧
[2] Linux高性能服务器编程