前面介绍了与套接字相关的函数, 本节就来运用这些函数实现一个简单的网络通信吧.
回射思路
回射, 将客户端发送过来的所有数据全部原封不动的发送回去. 可以使用write
和read
函数来实现, 客户端使用write函数向套接字中写数据, 对端用read函数读数据并调用write函数又将数据写回套接字, 客户端最后使用read读即可.
recv和send函数
回射可以用上面分析到的read
和write
函数, 也可以使用recv
和send
函数实现.
函数原型
#include <sys/socket.h>
int recv(int sockfd, void *buf, size_t nbytes, int flags);
int send(int sockfd, const void *buf, size_t nbytes, int flags);
成功 : 返回读/写的字节数
失败 : 返回-1.
recv
跟read功能基本一样, 前者只支持套接字并多了一个flags选项, 通常都设置为0.
send
跟write功能基本一样, 前者只支持套接字并多了一个flags选项, 通常都设置为0.
深度理解
send的功能是拷贝指定长度的数据到发送缓冲区, 只有当数据被全部拷贝完成后函数才会正确返回, 否则进入阻塞状态或等待超时.
recv的功能是从接收缓冲区读取(其实就是拷贝)指定长度的数据. 如果将接收缓冲区大小设为0, recv将直接从协议缓冲区(滑动窗口区)读取数据.
函数flags参数
flags值 | 描述 |
---|---|
MSG_OOB | 发送或接收外来数据(紧急数据) |
MSG_DONTROUTE | 绕过路由表查找 |
MSG_DONTWAIT | 仅操作非阻塞 |
MSG_PEEK | 窥看外来数据 |
MSG_WAITALL | 等待达到nbytes字节数后才返回 |
MSG_NOSIGNAL | 往读端关闭的管道或者socket中写数据不产生SIGPIPE信号 |
回射实现
先看服务端完整代码 1.0_service.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <errno.h>
#define EXIT(msg) do{ \
perror(msg); \
exit(-1); \
}while(0)
int main(int argc, char *argv[])
{
int sockfd;
struct sockaddr_in serviced_addr;
serviced_addr.sin_family = AF_INET;
serviced_addr.sin_addr.s_addr = inet_addr("192.168.1.16"); // 为本机IP地址
serviced_addr.sin_port = htons(8080);
// 创建套接字
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(sockfd < 0)
EXIT("socket");
socklen_t service_len = sizeof(serviced_addr);
int ret;
// 服务端端口绑定
ret = bind(sockfd, (struct sockaddr *)&serviced_addr, service_len);
if(ret < 0)
if(errno == EADDRINUSE)
EXIT("port impropriate");
else
EXIT("bind");
// 服务端开始监听
ret = listen(sockfd, 5);
if(ret < 0)
EXIT("listen");
int clientfd;
struct sockaddr_in clientAddr;
socklen_t clientLen;
// 接收连接
if((clientfd = accept(sockfd, (struct sockaddr *)&clientAddr, &clientLen)) < 0)
EXIT("accept");
char buf[1024];
int n;
// 回射
while(1)
{
n = recv(clientfd, buf, sizeof(buf), 0);
if(n == 0)
break;
send(clientfd, buf, n, 0);
}
exit(EXIT_SUCCESS);
}
再来看客户端完整的代码1.0_client.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#define EXIT(msg) do {\
perror(msg); \
exit(-1); \
}while(0)
int main(int argc, char *argv[])
{
int sockfd;
struct sockaddr_in service_addr;
service_addr.sin_port = htons(8080); // 服务端熟知端口
service_addr.sin_addr.s_addr = inet_addr("192.168.1.16");// 服务端IP地址
service_addr.sin_family = AF_INET;
// 获取套接字
if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
EXIT("socket");
// 建立连接
if(connect(sockfd, (struct sockaddr *)&service_addr, sizeof(service_addr)) != 0)
EXIT("connect");
char buf[1024];
int n;
// 发送
while(1)
{
n = read(STDIN_FILENO, buf, sizeof(buf));
send(sockfd, buf, n, 0);
n = recv(sockfd, buf, sizeof(buf), 0);
write(STDOUT_FILENO, buf, n);
}
exit(EXIT_SUCCESS);
}
编译运行
./service
./client
问题
现在我们完成的简单的socket
编程, 但是有很多的不足, 如: 只能够连接一个客户端; 服务端关闭客户端并没有退出等等. 接下来准备用几篇文张来实现一个正确的客户端/服务端.
总结
- 掌握recv和send函数
- 实现回射C/S程序