socket发送数据
操作系统提供了如下函数,供TCP的socket发送数据:
#include <sys/socket.h>
ssize_t send(int socket, const void *buffer, size_t length, int flags);
ssize_t sendmsg(int socket, const struct msghdr *message, int flags);
ssize_t sendto(int socket, const void *buffer, size_t length, int flags, const struct sockaddr *dest_addr, socklen_t dest_len);
#include <unistd.h>
ssize_t write(int fildes, const void *buf, size_t nbyte);
write 函数把socket 当作普通文件,往里面写数据。写操作有两种方式:
- 阻塞式。
write 函数会把应用数据写到内核相应的缓冲区里,直到完成该操作才返回。有时write 的返回值小于指定的要写入的字节数,这是因为内核缓冲区满了,需要等待内核把缓冲区的数据写到真实的硬件介质上,腾出空间,才能继续调用write 。 - 非阻塞式。
write 函数先返回,内核后续再慢慢地把数据写到对应的位置。
默认是阻塞式写。 普通文件有文件系统的缓冲区,socket 有网络组件的缓冲区。真实的写操作由操作系统内核去执行。 write 作用到普通文件,是把数据写到硬盘;write 作用到socket ,是把数据从网卡发出去。
两者有个明显的区别:
- 对普通文件而言,当
write 函数返回时,应用进程可以认为数据已经写到了文件里,即文件收到了应用程序传给它的数据。 - 对
socket 而言,当write 函数返回时,应用进程并不能认为socket 对端已经接收到了数据。因为数据通过网络发送到对端存在无法忽视的时延。
通过一个例子看下:
- 服务器进程。不停地从连接套接字接收数据,直到读到
EOF 。 - 客户机进程。构造一大块数据, 通过
write 发送给服务器。
socket接收数据
ssize_t recv(int socket, void *buffer, size_t length, int flags);
ssize_t recvfrom(int socket, void *restrict buffer, size_t length, int flags, struct sockaddr *restrict address, socklen_t *restrict address_len);
ssize_t recvmsg(int socket, struct msghdr *message, int flags);
ssize_t read(int fildes, void *buf, size_t nbyte);
服务器进程
common.h
#ifndef COMMON_H
#define COMMON_H
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/uio.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
inline const char *g_srvIp = "127.0.0.1";
inline uint16_t g_srvPort = 1234;
inline uint32_t g_backLog = 1024;
inline int RET_OK = 0;
inline int RET_ERR = -1;
inline const uint32_t MESSAGE_SIZE = 10240;
inline const uint32_t BUF_SIZE = 1024;
inline void PrintTime()
{
time_t rawTime;
time(&rawTime);
fprintf(stdout, "%ld\n", rawTime);
}
#endif
server.c
#include "common.h"
uint32_t MakeListenSocket()
{
int listenFd = socket(AF_INET, SOCK_STREAM, 0);
if (listenFd == -1) {
perror("socket");
return RET_ERR;
}
struct sockaddr_in srvAddr;
bzero(&srvAddr, sizeof(srvAddr));
srvAddr.sin_family = AF_INET;
srvAddr.sin_addr.s_addr = htonl(INADDR_ANY);
srvAddr.sin_port = htons(g_srvPort);
if (bind(listenFd, (struct sockaddr *)&srvAddr, sizeof(srvAddr)) == -1) {
perror("bind");
return RET_ERR;
}
if (listen(listenFd, g_backLog) == -1) {
perror("listen");
return RET_ERR;
}
return listenFd;
}
int ReadN(int fd, char *buf, uint32_t len)
{
int remain = len;
while (remain)
{
int num = read(fd, buf, remain);
if (num == -1) {
perror("read");
return -1;
}
if (num == 0) {
break;
}
remain -= num;
buf += num;
}
return len - remain;
}
void ReadData(int fd)
{
char buf[BUF_SIZE];
int time = 0;
for (;;) {
int n = ReadN(fd, buf, BUF_SIZE);
if ((n == -1) || (n == 0)) {
return;
}
PrintTime();
fprintf(stdout, "%d bytes read, time = %d\n", n, time);
++time;
sleep(1);
}
}
int main()
{
int listenFd = MakeListenSocket();
if (listenFd == RET_ERR) {
perror("MakeSocket");
return RET_ERR;
}
PrintTime();
fprintf(stdout, "server start ...\n");
for (;;) {
struct sockaddr_in cliAddr;
socklen_t cliLen = 0;
bzero(&cliAddr, sizeof(cliAddr));
int connectFd = accept(listenFd, (struct sockaddr *)&cliAddr, &cliLen);
if (connectFd == -1) {
perror("accept");
continue;
}
PrintTime();
fprintf(stdout, "connection established ...\n");
fprintf(stdout, "read start ...\n");
ReadData(connectFd);
PrintTime();
fprintf(stdout, "read finish ...\n");
close(connectFd);
}
return 0;
}
客户机进程
client.c
#include "common.h"
int MakeSocket()
{
int socketFd = socket(AF_INET, SOCK_STREAM, 0);
if (socketFd == -1) {
perror("socket");
return RET_ERR;
}
struct sockaddr_in srvAddr;
bzero(&srvAddr, sizeof(srvAddr));
srvAddr.sin_family = AF_INET;
srvAddr.sin_port = htons(g_srvPort);
if (inet_pton(AF_INET, g_srvIp, &srvAddr.sin_addr.s_addr) != 1) {
perror("inet_pton");
return RET_ERR;
}
if (connect(socketFd, (struct sockaddr *)&srvAddr, sizeof(srvAddr)) == -1) {
perror("connect");
return RET_ERR;
}
return socketFd;
}
void SendData(int fd)
{
char *query = (char *)malloc(MESSAGE_SIZE + 1);
if (!query) {
perror("malloc");
return;
}
for (int i = 0; i < MESSAGE_SIZE; ++i) {
query[i] = 'a';
}
query[MESSAGE_SIZE] = '\0';
const char *data = query;
uint32_t remain = strlen(data);
while (remain) {
int num = write(fd, data, remain);
if (num < 1) {
perror("write");
free(query);
query = NULL;
return;
}
remain -= num;
data += num;
}
free(query);
query = NULL;
}
int main()
{
int socketFd = MakeSocket();
if (socketFd == RET_ERR) {
perror("MakeSocket");
return RET_ERR;
}
PrintTime();
fprintf(stdout, "send start ...\n");
SendData(socketFd);
PrintTime();
fprintf(stdout, "send finish ...\n");
close(socketFd);
return 0;
}
运行结果
使用g++ -std=c++17 是因为程序中使用了inline变量 通过时间戳可以看到,在客户端的阻塞式写操作返回后,服务器才陆续收到客户端发来的所有数据。
|