网络编程(22)—— socket客户端和服务端收发数据包需要一对一的关系吗?

        这个题目是一个疑问句,这也是很多刚刚接触网络编程的新手的疑问,那么客户端和服务端分别进行读和写时,write和read要成对出现的吗?
        下面,就针对这个问题进行试验,以解答长久以来的疑惑。

         我们写这样一个服务端,每当接收到客户端的数据后,它重复发送三次发回给客户端。

#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



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值