TCP数据读写
对文件的读写操作read
、write
同样适用于socket
编程。但socket
编程接口提供了几个专门用于socket
数据读写的系统调用:
#include <sys/socket.h>
#include <sys/types.h>
ssize recv(int sockfd, void *buf, size_t len, int flag);
ssize send(int sockfd, const void *buf, size_t len, int flag);
recv
读取sockfd
上的数据。flags
参数通常设置为0。recv
成功时返回读取到的数据长度 ,他可能小于我们期望的长度len
,因此我们可能要多次调用recv
,才能读取到完整的数据。recv
可能返回0,这意味着通信对方已经关闭了。recv
出错返回-1
send
往sockfd
上写入数据。send
成功时返回实际写入的数据长度,失败返回-1
flags
参数为数据首发提供了额外的控制
我们使用MSG_OOB
选项提供了发送和接受带外数据的方法
发送带外数据:sendtest.c
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <libgen.h>
#include <stdlib.h>
int main(int argc, char* argv[]) {
if (argc <= 2) {
printf("usage: %s ip_address port_number\n", basename(argv[0]));
return 1;
}
const char* ip = argv[1];
int port = atoi(argv[2]);
struct sockaddr_in server_address;
bzero(&server_address, sizeof(struct sockaddr_in));
server_address.sin_family = AF_INET;
inet_pton(AF_INET, ip, &server_address.sin_addr);
server_address.sin_port = htons(port);
int sockfd = socket(PF_INET, SOCK_STREAM, 0);
assert( sockfd >= 0);
if (connect(sockfd, (struct sockaddr*)&server_address, sizeof(struct sockaddr_in)) < 0) {
printf("connection failed\n");
} else {
const char* oob_data = "abc";
const char* normal_data = "123";
send(sockfd, normal_data, strlen(normal_data), 0);
send(sockfd, oob_data, strlen(oob_data), MSG_OOB);
send(sockfd, normal_data, strlen(normal_data), 0);
}
close(sockfd);
return 0;
}
接受带外数据:sendtest.c
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <libgen.h>
#define BUF_SIZE 1024
#define BACKLOG 5
int main(int argc, char* argv[]) {
if (argc <= 2) {
printf("usage: %s ip_address port_number", basename(argv[0]));
return 1;
}
const char* ip = argv[1];
int port = atoi(argv[2]);
struct sockaddr_in address;
address.sin_family = AF_INET;
inet_pton(AF_INET, ip, &address.sin_addr);
address.sin_port = htons(port);
int sock = socket(PF_INET, SOCK_STREAM, 0);
assert(socket >= 0);
int ret = bind(sock, (struct sockaddr*)&address, sizeof(address));
assert(ret != -1);
ret = listen(sock, BACKLOG);
assert(ret != -1);
struct sockaddr_in client;
socklen_t client_addrlength = sizeof(client);
int connfd = accept(sock, (struct sockaddr*)&client, &client_addrlength);
if (connfd < 0) {
printf("errno is: %d\n", errno);
} else {
char buffer[BUF_SIZE];
memset(buffer, '\0', BUF_SIZE);
ret = recv(connfd, buffer, BUF_SIZE - 1, 0);
printf("got %d bytes of normal data '%s'\n", ret, buffer);
memset(buffer, '\0', BUF_SIZE);
ret = recv(connfd, buffer, BUF_SIZE - 1, MSG_OOB);
printf("got %d bytes of oob data '%s'\n", ret, buffer);
memset(buffer, '\0', BUF_SIZE);
ret = recv(connfd, buffer, BUF_SIZE - 1, 0);
printf("got %d bytes of normal data '%s'\n", ret, buffer);
close(connfd);
}
close(sock);
return 0;
}
服务器运行recvtest.c
,客户端运行sendtest.c
这个示例中数据发送的顺序是:“正常数据”——“带外数据”——“正常数据”
接受数据的顺序也是:“正常数据”——“带外数据”——“正常数据”
由此可见客户端发送给服务器的3字节数据abc中,仅有最后一个字符c被服务器当做真正的带外数据接受。并且,服务器对正常数据的接受将被带外数据截断,即前一部分正常数据123ab和后续的正常数据123是不能被一个recv调用全部读出的。
如果我们发送数据的顺序不变,而接受数据变成了:“带外数据”——“正常数据”——“正常数据”——“带外数据”
即recvtest.c
部分变成了如下写法:
memset(buffer, '\0', BUF_SIZE);
ret = recv(connfd, buffer, BUF_SIZE - 1, MSG_OOB);
printf("got %d bytes of oob data '%s'\n", ret, buffer);
memset(buffer, '\0', BUF_SIZE);
ret = recv(connfd, buffer, BUF_SIZE - 1, 0);
printf("got %d bytes of normal data '%s'\n", ret, buffer);
memset(buffer, '\0', BUF_SIZE);
ret = recv(connfd, buffer, BUF_SIZE - 1, 0);
printf("got %d bytes of normal data '%s'\n", ret, buffer);
memset(buffer, '\0', BUF_SIZE);
ret = recv(connfd, buffer, BUF_SIZE - 1, MSG_OOB);
printf("got %d bytes of oob data '%s'\n", ret, buffer);
运行之后发现
正常数据被截断了无法被一次性接受,带外数据丢失了无法被接收到
UDP数据读写
socket
编程接口中用于UDP数据报读写的系统调用是:
#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);
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
因为UDP通信没有连接概念,所以我们每次读取数据都需要获取发送端的socket地址,即参数src_addr
所指内容,addrlen
则指定地址长度。
recvfrom
、sendto
系统调用也可以用于面向连接(STREAM)的socket
的数据读写,只需要吧最后两个参数都设置为NULL
以忽略发送端/接收端的socket
地址(因为我么已经和对方建立了连接,所以已经知道其socket
地址)