《TCP IP网络编程》第五章 基于TCP的服务端/客户端(2)

第五章 基于TCP的服务端/客户端(2)

5.1 回声客户端的完美实现

第四章提到的问题解决方法:
因为可以提前确定接收数据的大小,若之前传输了20字节长的字符串,那么在接收时循环调用read函数读取20个字节即可。

#include
#include
#include
#include
#include
#include

#define BUF_SIZE 1024

void error_handling(char * message);

int main(int argc, char* argv[]){
        int sock;
        char message[BUF_SIZE];
        int str_len, recv_len, recv_cnt;
        struct sockaddr_in serv_adr;

        if(argc != 3){
                printf("Usage:%s  \n",argv[0]);
                exit(1);
        }

        sock = socket(PF_INET, SOCK_STREAM, 0);
        if(sock == -1){
                error_handling("socket() error");
        }

        memset(&serv_adr, 0, sizeof(serv_adr));
        serv_adr.sin_family = AF_INET;
        serv_adr.sin_addr.s_addr = inet_addr(argv[1]);
        serv_adr.sin_port = htons(atoi(argv[2]));

        if(connect(sock,(struct sockaddr*)& serv_adr, sizeof(serv_adr)) == -1){
                error_handling("connect() error");
        }else{
                puts("Connected.....");
        }

        while(1){
                fputs("Input message(Q to quit):",stdout);
                fgets(message, BUF_SIZE, stdin);

                if(!strcmp(message,"q\n") || !strcmp(message,"Q\n")){
                        break;
                }

				/* 
				把下面这两行代码改写成下面那一大坨
                write(sock, message, strlen(message));
                str_len = read(sock, message, BUF_SIZE-1);
                */
				str_len = write(sock, message, strlen(message));
				recv_len = 0;
				while(recv_len < str_len){
					recv_cnt = read(sock, message, BUF_SIZE-1);//recv_cnt是单次读到的长度
					if(recv_cnt == -1){
						error_handling("read() error!");
					}
					recv_len += recv_cnt;//recv_len是累计到目前为止读到的长度
				}


                message[str_len] = 0;
                printf("Message from server:%s",message);
        }
        close(sock);
        return 0;
}

void error_handling(char * message){
        fputs(message,stderr);
        fputc('\n',stderr);
        exit(1);
}
如果问题不在于回声客户端,定义应用层协议

回声客户端,可以提前知道自己即将接收的数据长度,但是,大多数情况下,无法预知接收数据长度,这时该如何收发数据?

收发数据过程中需要制定好规则来表述数据的边界,如收到Q就立即终止连接。

5.2 TCP原理

TCP套接字中的I/O缓冲

write函数调用的瞬间,并非立即传输数据,而是将数据移至输出缓冲;read函数调用瞬间,并非马上接收数据,而是从输入缓冲读取数据。

调用write函数时,数据将移动到输出缓冲,在适当的时候(不管是分别传送还是一次性传送)传向对方的输入缓冲。这时对方将调用read函数从输入缓冲读取数据。

情形:客户端缓冲区为50字节,而服务器传了100字节给它,会出现这种情况吗?
TCP会控制数据流,TCP中有滑动窗口协议,套接字A会告诉套接字B你现在最多可以向我传递50字节,B说OK。所以TCP不会因为缓冲溢出而丢失数据。

write函数和send函数,并不会在完成向对方主机的数据传输时返回,而是在数据移动到输出缓冲时返回。剩下的就交给TCP了,TCP会保证对输出缓冲数据的传输。

TCP内部工作原理1:与对方套接字的连接

套接字是以全双工的方式工作的。可以双向传递数据。

3次握手:

主机A——————————————————————————————————————————————————————————主机B
SYN(SEQ:1000;ACK: -  )——>
现在传递的数据包序号是1000,
如果接收无误,请通知我向您传递1001号数据包
												<——SYN+ACK(SEQ:2000;SCK:1001)
												现在传递的数据包序号为2000,
												如果接收无误,请通知我向您传递2001号数据包
												刚才传输的SEQ为1000的数据包接受无误,
												现在请传递SEQ为1001的数据包
ACK(SEQ:1001;ACK:2001)——>
TCP连接过程中发送数据包时需分配序号,
在之前的1000基础上加1,分配了1001,
已正确收到传输的SEQ为2000的数据包,
现在可以传输SEQ为2001的数据包

这样就完成了数据交换前的准备。

TCP内部工作原理2:与对方主机的数据交换
主机A——————————————————————————————————————————————主机B
(SEQ:1200100 byte data)——>

												<——ACK 1301
												
(SEQ:1301100 byte data)——>

												<——ACK 1402
ACK	号  = SEQ号 + 传递的字节数 + 1

最后加1是为了告诉对方下次要传递的SEQ号。

如果超过一段时间后,发送端仍未收到接收端的ACK确认,那么说明中间可能发生错误,发送端会试着重传该数据包。

为了完成数据包超时重传,TCP套接字启动计时器以等待ACK的应答,若响应的计时器发生超时则重传。

TCP内部工作原理3:断开与套接字的连接

如果对方还有数据需要传输时,直接断掉连接会出现问题,所以断开连接时需要双方协商:

4次挥手:

主机A——————————————————————————————————————————————主机B
我希望断开连接		   ——>		
FIN(SEQ:5000;ACK:-)——>
										<——	哦,是吗?请稍后				
										<——	ACK(SEQ:7500;ACK:5001<——	我也准备就绪,可以断开连接		
										<——	FIN(SEQ:7501;ACK:5001)
好的,谢谢合作			  ——>		
ACK(SEQ:5001;ACK:7502)——>

FIN表示断开连接。双方各发送一次FIN后断开连接。

5.3 习题

(1)请说明TCP套接字连接设置的三次握手过程。尤其是3次数据交换过程每次收发的数据内容。

《TCP IP网络编程》第五章 基于TCP的服务端/客户端(2)_第1张图片

(2)TCP是可靠的数据传输协议,但在通过网络通信的过程中可能丢失数据。请通过ACK和SEQ说明TCP通过何种机制保证丢失数据的可靠传输。

SEQ顺序标识符是给信息编号ACK是用于回复带有编号的信息。也就是说,每次传输信息时,都同时发送SEQ标识,而接收端应以SEQ信息为基础回复发送端。通过这种机制,传输数据的主机就可以确认数据是否被正确接收。在传输失败时,可以重新传送。

(3)TCP套接字中调用write和read函数时数据如何移动?结合I/O缓冲进行说明

write函数被调用时,数据就会向端口的输出缓冲区移动。然后经过网络传输传输到对方主机套接字的输入缓冲。这样,输入缓冲中存储的数据通过read函数的响应来读取。

(4)对方主机的输入缓冲剩余50字节空间时,若本方主机通过write函数请求传输70字节,问TCP如何处理这种情况?

对方主机会把输入缓冲中可存储的数据大小传送给要传输数据的数据(本方)。因此,在剩余空间为50字节的情况,即使要求传送70字节的数据,也不能传输50字节以上,剩余的部分保存在传输方的输出缓冲中,等待对方主机的输入缓冲出现空间。而且,这种交换缓冲多余空间信息的协议被称为滑动窗口协议

(5)更改程序,使服务器端和客户端各传送1次字符串…

sendrecv_serv.c:

/**********************************sendrecv_serv.c***********************************/
#include 
#include 
#include 
#include 
#include 
#include 
#include 

void error_handling(char *message);

int main(int argc, char *argv[])
{
	int serv_sock;
	int clnt_sock;
	int str_len, i;

	struct sockaddr_in serv_addr;
	struct sockaddr_in clnt_addr;
	socklen_t clnt_addr_size;

	char msg1[]="Hello client!";
	char msg2[]="I'm server.";
	char msg3[]="Nice to meet you.";
	char* str_arr[]={msg1, msg2, msg3};
	char read_buf[100];
	
	if(argc!=2){
		printf("Usage : %s \n", argv[0]);
		exit(1);
	}
	
	serv_sock=socket(PF_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]));
	
	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");
	
	clnt_addr_size=sizeof(clnt_addr);  
	clnt_sock=accept(serv_sock, (struct sockaddr*)&clnt_addr,&clnt_addr_size);
	if(clnt_sock==-1)
		error_handling("accept() error");  
	
	for(i=0; i<3; i++)
	{
		str_len=strlen(str_arr[i])+1;
		write(clnt_sock, (char*)(&str_len), 4);//传递字符串之前,先以4字节整数型方式传递字符串长度
		write(clnt_sock, str_arr[i], str_len);//传递字符串,第三个位置是写出去的字符串长度
		
		read(clnt_sock, (char*)(&str_len), 4);//读取字符串长度
		read(clnt_sock, read_buf, str_len);//读取字符串,第三个位置是拟读进来的字符串长度
		puts(read_buf);
	}
	
	close(clnt_sock);
	close(serv_sock);
	return 0;
}

void error_handling(char *message)
{
	fputs(message, stderr);
	fputc('\n', stderr);
	exit(1);
}

recvsend_clnt.c:

/***************************************recvsend_clnt.c***************************/
#include 
#include 
#include 
#include 
#include 
#include 
#include 

void error_handling(char *message);

int main(int argc, char* argv[])
{
	int sock;
	struct sockaddr_in serv_addr;

	char msg1[]="Hello server!";
	char msg2[]="I'm client.";
	char msg3[]="Nice to meet you too!";
	char* str_arr[]={msg1, msg2, msg3};
	char read_buf[100];

	int str_len, i;
	
	if(argc!=3){
		printf("Usage : %s  \n", argv[0]);
		exit(1);
	}
	
	sock=socket(PF_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!");

	for(i=0; i<3; i++)
	{
		read(sock, (char*)(&str_len), 4);
		read(sock, read_buf, str_len);
		puts(read_buf);

		str_len=strlen(str_arr[i])+1;
		write(sock, (char*)(&str_len), 4);
		write(sock, str_arr[i], str_len);
	}
	close(sock);
	return 0;
}

void error_handling(char *message)
{
	fputs(message, stderr);
	fputc('\n', stderr);
	exit(1);
}

(6)创建收发文件的服务器端/客户端

file_serv.c:

/**********************************file_serv.c***********************************/
#include 
#include 
#include 
#include 
#include 
#include 

#define BUF_SIZE 30
void error_handling(char *message);

int main(int argc, char *argv[])
{
	int serv_sd, clnt_sd;
	FILE * fp;
	char buf[BUF_SIZE];
	char file_name[BUF_SIZE];
	int read_cnt;
	
	struct sockaddr_in serv_adr, clnt_adr;
	socklen_t clnt_adr_sz;
	
	if(argc!=2) {
		printf("Usage: %s \n", argv[0]);
		exit(1);
	}
	
	serv_sd=socket(PF_INET, SOCK_STREAM, 0);   
	
	memset(&serv_adr, 0, sizeof(serv_adr));
	serv_adr.sin_family=AF_INET;
	serv_adr.sin_addr.s_addr=htonl(INADDR_ANY);
	serv_adr.sin_port=htons(atoi(argv[1]));
	
	bind(serv_sd, (struct sockaddr*)&serv_adr, sizeof(serv_adr));
	listen(serv_sd, 5);
	
	clnt_adr_sz=sizeof(clnt_adr);    
	clnt_sd=accept(serv_sd, (struct sockaddr*)&clnt_adr, &clnt_adr_sz);
	
	read(clnt_sd, file_name, BUF_SIZE);
	fp=fopen(file_name, "rb");
	if(fp!=NULL)
	{
		while(1)
		{
			read_cnt=fread((void*)buf, 1, BUF_SIZE, fp);
			if(read_cnt<BUF_SIZE)
			{
				write(clnt_sd, buf, read_cnt);
				break;
			}
			write(clnt_sd, buf, BUF_SIZE);
		}
	}
	
	fclose(fp);
	close(clnt_sd); close(serv_sd);
	return 0;
}

void error_handling(char *message)
{
	fputs(message, stderr);
	fputc('\n', stderr);
	exit(1);
}

recvsend_clnt.c:

/***************************************recvsend_clnt.c***************************/
#include 
#include 
#include 
#include 
#include 
#include 

#define BUF_SIZE 30
void error_handling(char *message);

int main(int argc, char *argv[])
{
	int sd;
	FILE *fp;
	
	char buf[BUF_SIZE];
	char file_name[BUF_SIZE];
	int read_cnt;
	struct sockaddr_in serv_adr;
	if(argc!=3) {
		printf("Usage: %s  \n", argv[0]);
		exit(1);
	}
	
	printf("Input file name: ");
	scanf("%s", file_name);
	fp=fopen(file_name, "wb");

	sd=socket(PF_INET, SOCK_STREAM, 0);   
	memset(&serv_adr, 0, sizeof(serv_adr));
	serv_adr.sin_family=AF_INET;
	serv_adr.sin_addr.s_addr=inet_addr(argv[1]);
	serv_adr.sin_port=htons(atoi(argv[2]));

	connect(sd, (struct sockaddr*)&serv_adr, sizeof(serv_adr));
	write(sd, file_name, strlen(file_name)+1);	

	while((read_cnt=read(sd, buf, BUF_SIZE))!=0)
		fwrite((void*)buf, 1, read_cnt, fp);
	
	fclose(fp);
	close(sd);
	return 0;
}

void error_handling(char *message)
{
	fputs(message, stderr);
	fputc('\n', stderr);
	exit(1);
}

你可能感兴趣的:(Linux网络编程,网络,socket,网络通信,MODBUS&TCP)