一、TCP客户端实现
tcp 协议是⾯向连接的协议,在实现 tcp 客户端时,则需要先连接服务器,后⾯才能进⾏通讯。
在整个数据传输流程中,主要涉及以下几个接口:
- socket( ) : 创建套接字, 使⽤的套接字类型为流式套接字
- connect( ) : 连接服务器
- send( ) : 数据发送
- recv( ) : 数据接收
1.socket( )
c
函数头⽂件:
#include <sys/types.h>
#include <sys/socket.h>
函数原型:int socket(int domain,int type,int protocol)
函数功能:创建套接字
函数参数:
domain: 协议族,如 AF_INTE (表示IPV4)
type : 套接字类型
SOCK_STREAM : 流式套接字, 传输层使⽤ tcp 协议
SOCK_DGRAM : 数据包套接字, 传输层使⽤ udp 协议
protocol : 协议, 可以填0
函数返回值
成功 : 返回 套接字⽂件描述符
失败 : 返回 -1,并设置 errno
2.connect( )
c
函数头⽂件:
#include <sys/types.h>
#include <sys/socket.h>
函数原型:int connect(int sockfd,const struct sockaddr *addr,socklen_t addrlen)
函数功能:发起对套接字的连接 (基于连接的协议进行)
函数参数:
sockfd : 套接字⽂件描述符
addr : 连接的套接字的地址结构对象的地址 (⼀般为服务器)
internet 协议族使⽤的 struct sockaddr_in 结构体,⼤⼩与通⽤ struct sockaddr 结构体⼀致
addrlen : 地址结构的⻓度
函数返回值:
成功 : 返回 0
失败 : 返回 -1,并设置 errno
示例: 编码实现客户端主程序,并连接服务器。
c
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<strings.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<unistd.h>
int main(int argc, const char *argv[])
{
if(argc != 3)
{
fprintf(stderr, "Usage : %s <ip> <prot>.\n", argv[0]);
exit(EXIT_FAILURE);
}
//1.创建套接字
int sfd = socket(AF_INET, SOCK_STREAM, 0);
if(sfd == -1){
perror("[ERROR] Fail to socket.");
exit(EXIT_FAILURE);
}
//2.填写协议与服务器网络信息
struct sockaddr_in svr_addr;
bzero(&svr_addr, sizeof(struct sockaddr_in)); //清零
svr_addr.sin_family = AF_INET;
svr_addr.sin_port = htons(atoi(argv[2]));
svr_addr.sin_addr.s_addr = inet_addr(argv[1]);
//3.连接服务器
int ret = connect(sfd, (const struct sockaddr *)&svr_addr, sizeof(struct sockaddr_in));
if(ret == -1){
perror("[ERROR] Fail to connect.");
exit(EXIT_FAILURE);
}
close(sfd);
return 0;
}
3.send( )
基于 socket 发送数据需要调⽤ send 函数, 下⾯是 send 函数的具体信息。
c
函数头⽂件:
#include <sys/types.h>
#include <sys/socket.h>
函数原型:ssize_t send(int sockfd,const void *buf,size_t len,int flags)
函数功能:基于套接字(建⽴连接后)发送数据
函数参数:
sockfd : 套接字⽂件描述符
buf : 发送缓冲区的地址
len : 发送数据的⻓度
flags : 发送标志位
函数返回值:
成功 : 返回 成功发送的字节数
失败 : 返回 -1, 并设置 errno
示例:客户端发送数据给服务器
c
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<strings.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<unistd.h>
int main(int argc, const char *argv[])
{
if(argc != 3)
{
fprintf(stderr, "Usage : %s <ip> <prot>.\n", argv[0]);
exit(EXIT_FAILURE);
}
//1.创建套接字
int sfd = socket(AF_INET, SOCK_STREAM, 0);
if(sfd == -1){
perror("[ERROR] Fail to socket.");
exit(EXIT_FAILURE);
}
struct sockaddr_in svr_addr;
bzero(&svr_addr, sizeof(struct sockaddr_in)); //清零
svr_addr.sin_family = AF_INET;
svr_addr.sin_port = htons(atoi(argv[2]));
svr_addr.sin_addr.s_addr = inet_addr(argv[1]);
int ret = connect(sfd, (const struct sockaddr *)&svr_addr, sizeof(struct sockaddr_in));
if(ret == -1){
perror("[ERROR] Fail to connect.");
exit(EXIT_FAILURE);
}
//2.获取输入并发送
ssize_t sbytes = 0;
char buffer[1024] = {0};
strcpy(buffer, "Hello, server");
sbytes = send(sfd, buffer, strlen(buffer)+1, 0);
if(sbytes == -1){
perror("[ERROR] Fail to send.");
exit(EXIT_FAILURE);
}
close(sfd);
return 0;
}
4.recv( )
基于 socket 接收数据需要调⽤ recv 函数, 具体信息如下。
c
函数头⽂件:
#include <sys/types.h>
#include <sys/socket.h>
函数原型:ssize_t recv(int sockfd,void *buf,size_t len,int flags)
函数功能:基于套接字接收数据
函数参数:
sockfd : 套接字⽂件描述符
buf : 接收缓冲区的地址
len : 接收数据最⼤⻓度
flags : 标志位
函数返回值:
成功 : 返回 成功接收的字节数
失败 : 返回 -1, 并设置 errno
示例:服务器发送数据,客户端接收数据(实现客户端的接收功能)
c
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<strings.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<unistd.h>
int main(int argc, const char *argv[])
{
if(argc != 3)
{
fprintf(stderr, "Usage : %s <ip> <prot>.\n", argv[0]);
exit(EXIT_FAILURE);
}
//1.创建套接字
int sfd = socket(AF_INET, SOCK_STREAM, 0);
if(sfd == -1){
perror("[ERROR] Fail to socket.");
exit(EXIT_FAILURE);
}
struct sockaddr_in svr_addr;
bzero(&svr_addr, sizeof(struct sockaddr_in)); //清零
svr_addr.sin_family = AF_INET;
svr_addr.sin_port = htons(atoi(argv[2]));
svr_addr.sin_addr.s_addr = inet_addr(argv[1]);
int ret = connect(sfd, (const struct sockaddr *)&svr_addr, sizeof(struct sockaddr_in));
if(ret == -1){
perror("[ERROR] Fail to connect.");
exit(EXIT_FAILURE);
}
//2.获取输入并发送
ssize_t sbytes = 0;
char buffer[1024] = {0};
strcpy(buffer, "Hello, server");
sbytes = send(sfd, buffer, strlen(buffer)+1, 0);
if(sbytes == -1){
perror("[ERROR] Fail to send.");
exit(EXIT_FAILURE);
}
bzero(buffer, sizeof(buffer));
//3.接收数据并显示
ssize_t rbytes = 0;
char buffer_recv[1024] = {0};
rbytes = recv(sfd, buffer_recv, sizeof(buffer_recv), 0);
if(ret == -1){
perror("[ERROR] Fail to ercv.");
exit(EXIT_FAILURE);
}else if(rbytes > 0){
printf("buffer : %s\n", buffer_recv);
}else if(rbytes == 0){
printf("server has been shut down.\n");
}
close(sfd);
return 0;
}
二、TCP服务端实现
将服务端的环节拆分,可得到以下流程图:
在上述流程中,相对于客户端,服务端主要增加以下新的操作:
- bind : 绑定 ip 地址与端⼝号,⽤于客户端连接服务器
- listen : 建⽴监听队列,并设置套接字的状态为 listen 状态, 表示可以接收连接请求
- accept : 接受连接, 建⽴三次握⼿, 并创建新的⽂件描述符, ⽤于数据传输
在整个过程中,socket 套接字状态如下图:
- CLOSED : 关闭状态。
- LISTEN:套接字的状态为 listen 状态, 表示可以接收连接请求。
- SYN-SENT : 套接字正在试图主动建⽴连接 [发送 SYN 后还没有收到 ACK],很短暂。
- SYN-RECEIVE : 正在处于连接的初始同步状态 [收到对⽅的 SYN,但还没收到⾃⼰发过去的 SYN 的 ACK]。
- ESTABLISHED : 连接已建⽴。
1.bind( )
c
函数头⽂件:
#include <sys/types.h>
#include <sys/socket.h>
函数原型:int bind(int sockfd,const struct sockaddr *addr,socklen_t addrlen)
函数功能:绑定 ip 地址与端⼝号
函数参数:
sockfd : 套接字⽂件描述符
buf : 接收缓冲区的地址
len : 接收数据最⼤⻓度
flags : 标志位
函数返回值:
成功 : 返回 成功接收的字节数
失败 : 返回 -1, 并设置 errno
示例: 设计⼀个基本的服务器程序, 完成 socket 创建并绑定 ip 地址与端⼝号
c
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<strings.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<unistd.h>
#define BACKLOG 10
int main(int argc, const char *argv[])
{
if(argc != 3)
{
fprintf(stderr, "Usage : %s <ip> <prot>.\n", argv[0]);
exit(EXIT_FAILURE);
}
//1.创建套接字
int sfd = socket(AF_INET, SOCK_STREAM, 0);
if(sfd == -1){
perror("[ERROR] Fail to socket.");
exit(EXIT_FAILURE);
}
struct sockaddr_in svr_addr;
bzero(&svr_addr, sizeof(struct sockaddr_in)); //清零
svr_addr.sin_family = AF_INET;
svr_addr.sin_port = htons(atoi(argv[2]));
svr_addr.sin_addr.s_addr = inet_addr(argv[1]);
//2.绑定ip地址与端口号
int ret = bind(sfd, (const struct sockaddr *)&svr_addr, sizeof(struct sockaddr));
if(ret == -1){
perror("[ERROR] Failed to bind.");
exit(EXIT_FAILURE);
}
//3.设置套接字状态为监听状态,并建立监听队列
ret = listen(sfd, BACKLOG);
if(ret == -1){
perror("[ERROR] Failed to listen.");
exit(EXIT_FAILURE);
}
//4.同意建立连接------accept
struct sockaddr_in cli_addr; //用来保存客户端的地址信息
socklen_t len = sizeof(struct sockaddr_in); //用来保存地址结构体的长度
int cfd = accept(sfd, (struct sockaddr *)&cli_addr, &len);
if(cfd == -1){
perror("[ERROR] Failed to accept.");
exit(EXIT_FAILURE);
}
printf("ip : %s, port : %d\n", inet_ntoa(cli_addr.sin_addr), ntohs(cli_addr.sin_port));
//while(1){}
close(sfd);
return 0;
}
2.listen( )
在服务器绑定 ip 地址与端⼝号之后, 则需要让服务器 socket 套接字设置成被动监听状态,并 创建监听队列,这⾥需要调⽤ listen 函数。
c
函数头⽂件:
#include <sys/types.h>
#include <sys/socket.h>
函数原型:int listen(int sockfd,int backlog)
函数功能:设置套接字状态为被动监听,并创建监听队列
函数参数:
sockfd : 套接字⽂件描述符
backlog : 监听队列的⻓度
函数返回值:
成功 : 返回 0
失败 : 返回 -1, 并设置 errno
3.accept( )
c
函数头⽂件:
#include <sys/types.h>
#include <sys/socket.h>
函数原型:int accept(int sockfd,struct sockaddr *addr,socklen_t *addrlen)
函数功能:接受来⾃于其他 socket 的连接请求,并建⽴连接
函数参数:
sockfd : 套接字⽂件描述符
addr : ⽹络地址结构的指针(输出参数,⽤于保存发送请求端的地址信息)
addrlen : ⽹络地址结构⻓度的指针 (输出参数,但是需要进⾏初始化)
函数返回值:
成功 : 返回新的⽂件描述符
失败 : -1 , 并设置 errno
示例:设计⼀个服务器程序,并和客户端建⽴连接,并打印客户端的 ip 地址和端⼝号
c
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<strings.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<unistd.h>
#define BACKLOG 10
int main(int argc, const char *argv[])
{
ssize_t rbytes, sbytes = 0; //接收send和recv返回值
char buffer[1024] = {0}; //数据缓冲区
if(argc != 3)
{
fprintf(stderr, "Usage : %s <ip> <prot>.\n", argv[0]);
exit(EXIT_FAILURE);
}
//1.创建套接字
int sfd = socket(AF_INET, SOCK_STREAM, 0);
if(sfd == -1){
perror("[ERROR] Fail to socket.");
exit(EXIT_FAILURE);
}
struct sockaddr_in svr_addr;
bzero(&svr_addr, sizeof(struct sockaddr_in)); //清零
svr_addr.sin_family = AF_INET;
svr_addr.sin_port = htons(atoi(argv[2]));
svr_addr.sin_addr.s_addr = inet_addr(argv[1]);
//2.绑定ip地址与端口号
int ret = bind(sfd, (const struct sockaddr *)&svr_addr, sizeof(struct sockaddr));
if(ret == -1){
perror("[ERROR] Failed to bind.");
exit(EXIT_FAILURE);
}
//3.设置套接字状态为监听状态,并建立监听队列
ret = listen(sfd, BACKLOG);
if(ret == -1){
perror("[ERROR] Failed to listen.");
exit(EXIT_FAILURE);
}
//4.同意建立连接------accept
struct sockaddr_in cli_addr; //用来保存客户端的地址信息
bzero(&cli_addr, sizeof(struct sockaddr)); //清空
socklen_t len = sizeof(struct sockaddr_in); //用来保存地址结构体的长度
int cfd = accept(sfd, (struct sockaddr *)&cli_addr, &len);
if(cfd == -1){
perror("[ERROR] Failed to accept.");
exit(EXIT_FAILURE);
}
printf("ip : %s, port : %d\n", inet_ntoa(cli_addr.sin_addr), ntohs(cli_addr.sin_port));
close(cfd);
close(sfd);
return 0;
}
添加功能:服务端打印出客户端的网络信息之后,进入死循环持续接收来自客户端发送来的消息,并将接收的消息以相同的内容发送回应给客户端。
c
//服务端
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<strings.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<unistd.h>
#define BACKLOG 10
int main(int argc, const char *argv[])
{
ssize_t rbytes, sbytes = 0; //接收send和recv返回值
char buffer[1024] = {0}; //数据缓冲区
if(argc != 3)
{
fprintf(stderr, "Usage : %s <ip> <prot>.\n", argv[0]);
exit(EXIT_FAILURE);
}
//1.创建套接字
int sfd = socket(AF_INET, SOCK_STREAM, 0);
if(sfd == -1){
perror("[ERROR] Fail to socket.");
exit(EXIT_FAILURE);
}
struct sockaddr_in svr_addr;
bzero(&svr_addr, sizeof(struct sockaddr_in)); //清零
svr_addr.sin_family = AF_INET;
svr_addr.sin_port = htons(atoi(argv[2]));
svr_addr.sin_addr.s_addr = inet_addr(argv[1]);
//2.绑定ip地址与端口号
int ret = bind(sfd, (const struct sockaddr *)&svr_addr, sizeof(struct sockaddr));
if(ret == -1){
perror("[ERROR] Failed to bind.");
exit(EXIT_FAILURE);
}
//3.设置套接字状态为监听状态,并建立监听队列
ret = listen(sfd, BACKLOG);
if(ret == -1){
perror("[ERROR] Failed to listen.");
exit(EXIT_FAILURE);
}
//4.同意建立连接------accept
struct sockaddr_in cli_addr; //用来保存客户端的地址信息
socklen_t len = sizeof(struct sockaddr_in); //用来保存地址结构体的长度
int cfd = accept(sfd, (struct sockaddr *)&cli_addr, &len);
if(cfd == -1){
perror("[ERROR] Failed to accept.");
exit(EXIT_FAILURE);
}
printf("ip : %s, port : %d\n", inet_ntoa(cli_addr.sin_addr), ntohs(cli_addr.sin_port));
while(1)
{
rbytes = recv(cfd, buffer, sizeof(buffer), 0);
if(rbytes == -1){
perror("[ERROR] Failed to recv.");
exit(EXIT_FAILURE);
}
else if(rbytes > 0)
{
sbytes = send(cfd, buffer, sizeof(buffer), 0);
if(rbytes == -1)
{
perror("[ERROR] Failed to recv.");
exit(EXIT_FAILURE);
}
}
else if(rbytes == 0)
{
printf("the client has been shut down.\n");
exit(EXIT_SUCCESS);
}
}
close(cfd);
close(sfd);
return 0;
}
c
//客户端
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<strings.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<unistd.h>
int main(int argc, const char *argv[])
{
if(argc != 3)
{
fprintf(stderr, "Usage : %s <ip> <prot>.\n", argv[0]);
exit(EXIT_FAILURE);
}
//1.创建套接字
int sfd = socket(AF_INET, SOCK_STREAM, 0);
if(sfd == -1){
perror("[ERROR] Fail to socket.");
exit(EXIT_FAILURE);
}
struct sockaddr_in svr_addr;
bzero(&svr_addr, sizeof(struct sockaddr_in)); //清零
svr_addr.sin_family = AF_INET;
svr_addr.sin_port = htons(atoi(argv[2]));
svr_addr.sin_addr.s_addr = inet_addr(argv[1]);
int ret = connect(sfd, (const struct sockaddr *)&svr_addr, sizeof(struct sockaddr_in));
if(ret == -1){
perror("[ERROR] Fail to connect.");
exit(EXIT_FAILURE);
}
//2.获取输入并发送
ssize_t sbytes = 0;
char buffer[1024] = {0};
strcpy(buffer, "Hello"); //这里以 Hello 作为输入
sbytes = send(sfd, buffer, strlen(buffer)+1, 0);
if(sbytes == -1){
perror("[ERROR] Fail to send.");
exit(EXIT_FAILURE);
}
bzero(buffer, sizeof(buffer));
//3.接收数据并显示
ssize_t rbytes = 0;
char buffer_recv[1024] = {0};
rbytes = recv(sfd, buffer_recv, sizeof(buffer_recv), 0);
if(ret == -1)
{
perror("[ERROR] Fail to ercv.");
exit(EXIT_FAILURE);
}
else if(rbytes > 0)
{
printf("buffer : %s\n", buffer_recv);
}
else if(rbytes == 0)
{
printf("server has been shut down.\n");
}
close(sfd);
return 0;
}
三、粘包问题
TCP是面向字节流的协议,流就像河流中的水,TCP对数据包是以"组"的方式进行发送,而并非是一次发送全部的数据包,这么做是为了提升传输效率。在这个过程中,TCP默认使用Nagle算法,而Nagle算法主要做两件事:
1)只有上一个分组得到确认,才会发送下一个分组;
2)收集多个小分组,在一个确认到来时一起发送。所以,正是Nagle算法造成了发送方有可能造成粘包现象,也就是说数据发送出来它已经是粘包的状态了。
由于分组的存在,包与包之间没有明确的界限,所以会在发送时产生粘包现象。当网络传输数据的速度大于接收方处理数据的速度时,这时候就会导致,接收方在读取缓冲区时,缓冲区存在多个数据包。而且在 TCP 协议中,接收方是一次读取缓冲区中的所有内容,所以不能反映原本的数据信息。
而UDP是基于数据报的协议,它有消息边界,所以不会出现粘包现象。
TCP粘包的解决方案主要有两种:
- 方法⼀ : 使⽤定⻓数据包,每次必须要读取固定⻓度的数据,适⽤于数据⻓度是固定的场景。
- 方法⼆ : 使⽤数据⻓度 + 数据的⽅式,先接收一个4字节存储的数据⻓度,再根据数据⻓度值接收数据,这⾥就结合第⼀种⽅式,进⾏固定⻓度接收,这种⽅式适⽤于不定⻓数据场景。
第二种方法是最常用的。
以第二种方法为思路,编写客户端与服务端程序。
c
//客户端
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
int main(int argc,char *argv[])
{
int sfd,ret;
struct sockaddr_in svr_addr;
ssize_t sbytes = 0;
char buffer[] = "Hello,server";
char *pbuffer = NULL;
int length = 0;
if (argc != 3)
{
fprintf(stderr,"Usage : %s < ip > < port >.\n",argv[0]) ;
exit(EXIT_FAILURE);
}
sfd = socket(AF_INET,SOCK_STREAM,0);
if (sfd == -1)
{
perror("[ERROR] Failed to socket.");
exit(EXIT_FAILURE);
}
bzero(&svr_addr,sizeof(struct sockaddr_in));
svr_addr.sin_family = AF_INET;
svr_addr.sin_port = htons(atoi(argv[2]));
svr_addr.sin_addr.s_addr = inet_addr(argv[1]);
ret = connect(sfd,(const struct sockaddr *)&svr_addr,sizeof(struct sockaddr_in));
if (ret == -1){
perror("[ERROR] Failed to connect.");
exit(EXIT_FAILURE);
}
for(;;){
length = strlen(buffer);
pbuffer = (char *)malloc(length + 4);
memcpy(pbuffer, &length,4); //给前4个字节写入数据长度
memcpy(pbuffer + 4,buffer,length); //写入数据
sbytes = send(sfd,pbuffer,length + 4,0);
if (sbytes == -1){
perror("[ERROR] Failed to send.");
exit(EXIT_FAILURE);
}
}
close(sfd);
return 0;
}
c
//服务端
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#define BACKLOG 10
int main(int argc,char *argv[])
{
int sfd,ret,cfd;
struct sockaddr_in svr_addr,cli_addr;
ssize_t rbytes = 0,sbytes = 0;
char buffer[1024] = {0};
int length;
int total_received;
socklen_t len = sizeof(struct sockaddr_in);
if (argc != 3){
fprintf(stderr,"Usage : %s < ip > < port >.\n",argv[0]) ;
exit(EXIT_FAILURE);
}
//1.创建套接字
sfd = socket(AF_INET,SOCK_STREAM,0);
if (sfd == -1){
perror("[ERROR] Failed to socket.");
exit(EXIT_FAILURE);
}
bzero(&svr_addr,sizeof(struct sockaddr_in));
svr_addr.sin_family = AF_INET;
svr_addr.sin_port = htons(atoi(argv[2]));
svr_addr.sin_addr.s_addr = inet_addr(argv[1]);
//2.绑定ip地址与端口号
ret = bind(sfd,(const struct sockaddr *)&svr_addr,sizeof(struct sockaddr));
if (ret == -1){
perror("[ERROR] Failed to bind.");
exit(EXIT_FAILURE);
}
ret = listen(sfd,BACKLOG);
if (ret == -1){
perror("[ERROR] Failed to listen.");
exit(EXIT_FAILURE);
}
cfd = accept(sfd,(struct sockaddr *)&cli_addr,&len);
if (cfd == -1){
perror("[ERROR] Failed to accept.");
exit(EXIT_FAILURE);
}
printf("ip : %s port : %d\n",inet_ntoa(cli_addr.sin_addr),ntohs(cli_addr.sin_port));
for(;;)
{
length = 0; //存储数据长度
total_received = 0; //存储总长
rbytes = recv(cfd,&length,4,0); //读4个字节放到length里,length的值现在是数据长度
if (rbytes == -1)
{
perror("[ERROR] Failed to recv.");
exit(EXIT_FAILURE);
}
for(;;)
{
rbytes = recv(cfd, buffer + total_received, length - total_received, 0);
if (rbytes == -1)
{
perror("[ERROR] Failed to recv.");
exit(EXIT_FAILURE);
}
else if (rbytes > 0)
{
printf("buffer : %s\n",buffer);
total_received += rbytes;
if (total_received == length)
break;
}
else if (rbytes == 0)
{
printf("The client has been shutdown.\n");
break;
}
}
printf("buffer : %s\n",buffer);
sleep(1);
}
close(sfd);
return 0;
}