这个题目是一个疑问句,这也是很多刚刚接触网络编程的新手的疑问,那么客户端和服务端分别进行读和写时,write和read要成对出现的吗?
下面,就针对这个问题进行试验,以解答长久以来的疑惑。
请注意这几行代码:
这里通过一个for循环将buf里存储的客户端发送过来的数据进行发回,需要注意的是,我们发回的时候将字符串结尾的“\000”进行了截断,主要是
考虑客户端在打印这个消息时遇到“\000”打印不全。
接下来是客户端的代码:
客户端没有很特别的操作,只是实现了读标准输入中读取字符串后发给服务器,再从服务器读回。我们先要预判一下试验结果:
1、如果write和read必须是一对一的关系,很显然我们在客户端输入字符串后,会从服务端反射,并在客户端原样打印出该字符串。
2、如果write和read可以是多对一的关系,客户端将会打印出三个连续的我们输入的字符串。
带着这样的疑问,继续试验,首先启动服务器:
然后启动客户端:
剩余的重复的字符串“123123”被打印出来。
所以我们可以进行以下大胆的假设:
在正常情况下,write写入数据时,数据会先传到客户端的输入缓冲区,而输入缓冲区一有数据read函数就会进行读取(read是阻塞函数),
所以write和read应该是一对一的关系。
沿用该思路,我们可以继续进行假设,当我们给人为给read函数预留足够的时间不去读取时,缓冲区会缓存所有服务端发过来的数据,此时
再次read时就会读取所有的数据。
将客户端的read前加入sleep(3),再进行试验:
下面,就针对这个问题进行试验,以解答长久以来的疑惑。
我们写这样一个服务端,每当接收到客户端的数据后,它重复发送三次发回给客户端。
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#define BUF_SIZE 100
void error_handling(char* message);
int main(int argc,char* argv[])
{
int serv_sock,clnt_sock;
struct sockaddr_in serv_addr,clnt_addr;
int clnt_addr_sz;
int str_len,i,j;
char buf[BUF_SIZE];
if(argc!=2)
{
printf("Usage %s <port>\n",argv[0]);
exit(1);
}
//创建socket
serv_sock=socket(AF_INET,SOCK_STREAM,0);
if(serv_sock==-1)
error_handling("socket error");
//填充地址信息
memset(&serv_addr,0,sizeof(serv_addr));
serv_addr.sin_family=AF_INET;
serv_addr.sin_addr.s_addr=htonl(INADDR_ANY);
serv_addr.sin_port=htons(atoi(argv[1]));
//socket和ip地址的绑定
if(bind(serv_sock,(struct sockaddr*)&serv_addr,sizeof(serv_addr))==-1)
error_handling("bind error");
//开启监听
if(listen(serv_sock,5)==-1)
error_handling(" listen error");
for(i=0;i<5;i++)
{
clnt_addr_sz=sizeof(clnt_addr);
clnt_sock=accept(serv_sock,(struct sockaddr*)&clnt_addr,&clnt_addr_sz);
if(clnt_sock==-1)
error_handling("accept error");
else
printf("clnt:%s connected\n",inet_ntoa(clnt_addr.sin_addr));
//接受数据
while(1)
{
str_len=read(clnt_sock,buf,BUF_SIZE);
for(j=0;j<3;j++)
{
write(clnt_sock,buf,str_len-1);
}
memset(buf,0,sizeof(buf));
}
close(clnt_sock);
}
close(serv_sock);
return 0;
}
void error_handling(char* message)
{
fputs(message,stderr);
fputc('\n',stderr);
exit(1);
}
请注意这几行代码:
for(j=0;j<3;j++)
{
write(clnt_sock,buf,str_len-1);
}
这里通过一个for循环将buf里存储的客户端发送过来的数据进行发回,需要注意的是,我们发回的时候将字符串结尾的“\000”进行了截断,主要是
考虑客户端在打印这个消息时遇到“\000”打印不全。
接下来是客户端的代码:
//#头文件和服务端相同,故省略
void error_handling(const char* message);
int main(int argc,char* argv[])
{
int sock;
struct sockaddr_in serv_addr;
int str_len;
char buf[BUF_SIZE];
int recv_len=0;
//创建socket
sock=socket(AF_INET,SOCK_STREAM,0);
if(sock==-1)
error_handling("socket error");
//准备地址
memset(&serv_addr,0,sizeof(serv_addr));
serv_addr.sin_family=AF_INET;
serv_addr.sin_addr.s_addr=inet_addr(argv[1]);
serv_addr.sin_port=htons(atoi(argv[2]));
//链接
if(connect(sock,(struct sockaddr*)&serv_addr,sizeof(serv_addr))==-1)
error_handling("connect error");
while(1)
{
memset(buf,0,BUF_SIZE);
fputs("请输入数据:",stdout);
fgets(buf,BUF_SIZE,stdin);
if(!strcmp(buf,"q\n")||!strcmp(buf,"Q/n"))
break;
str_len=write(sock,buf,strlen(buf));
sizeof(buf,0,sizeof(buf));
recv_len=read(sock,buf,BUF_SIZE-1);
printf("服务器传回信息:%s\n",buf);
}
close(sock);
return 0;
}
void error_handling(const char* message)
{
#和服务端相同,故省略
}
客户端没有很特别的操作,只是实现了读标准输入中读取字符串后发给服务器,再从服务器读回。我们先要预判一下试验结果:
1、如果write和read必须是一对一的关系,很显然我们在客户端输入字符串后,会从服务端反射,并在客户端原样打印出该字符串。
2、如果write和read可以是多对一的关系,客户端将会打印出三个连续的我们输入的字符串。
带着这样的疑问,继续试验,首先启动服务器:
[Hyman@Hyman-PC tcpip]$ ./serv 9190
然后启动客户端:
[Hyman@Hyman-PC tcpip]$ ./serv 9190
请输入数据:123
服务器传回信息:123
请输入数据:
很显然,当我们输入“123”时,客户端只接收到了“123”并打印出来。也就是是符合我们预判的结果一,当我们继续输入其他数据时,
剩余的重复的字符串“123123”被打印出来。
所以我们可以进行以下大胆的假设:
在正常情况下,write写入数据时,数据会先传到客户端的输入缓冲区,而输入缓冲区一有数据read函数就会进行读取(read是阻塞函数),
所以write和read应该是一对一的关系。
沿用该思路,我们可以继续进行假设,当我们给人为给read函数预留足够的时间不去读取时,缓冲区会缓存所有服务端发过来的数据,此时
再次read时就会读取所有的数据。
将客户端的read前加入sleep(3),再进行试验:
sizeof(buf,0,sizeof(buf));
sleep(3);
print("sleep over\n")
recv_len=read(sock,buf,BUF_SIZE-1);
printf("服务器传回信息:%s\n",buf);
客户端试验结果:
[Hyman@Hyman-PC tcpip]$ ./clnt 127.0.0.1 9190
请输入数据:123
sleep over
服务器传回信息:123123123
至此,假设证明成立。。所以正常情况下,socket客户端和服务端收发数据包需要一对一的关系。
Github位置:
https://github.com/HymanLiuTS/NetDevelopment
克隆本项目:
git clone git@github.com:HymanLiuTS/NetDevelopment.git
获取本文源代码:
git checkout NL22