四 基于TCP的服务器端/客户端

**

第四章 基于TCP的服务器端/客户端(1)

**


主要内容:
这里我们讨论通过套接字收发数据
1.TCP与UDP的区别数据传输方式不同
2.协议栈的层次关系:链路层->IP层->tcp/udp层->应用层。重点!
3.基于TCP的服务器端和客户端详解
服务器端定义的两个套接字的用途 serv_sock和clnt_sock(一个当做门卫,一个用于连接)
4.详解connect函数(可以分配地址)
5.回声服务器端和回声客户端的实现
之前介绍套接字创建时我们选择的是 面向连接的套接字,本章主要讨论这种面向连接的服务器端/客户端的编写


4.1 理解TCP和UDP

TCP(transmission control protocol:传输控制协议)
这两者的根本区别是数据传输方式不同。
基于网络协议的套接字一般分为TCP和UDP套接字。 TCP是面向连接的的套接字,也称 基于流(stream)的套接字


4.1.1 TCP/IP协议栈
四层网络模型:
在这里插入图片描述在这里插入图片描述
TCP/IP 4层模型包括:
网络接口层:MAC VLAN
网络层:IP ARP ICMP
传输层:TCP UDP
应用层:HTTP DNS SMTP

我们在进行"基于互联网的有效数据传输"时,并非一个庞大的协议解决问题,而是通过借助着4层的相互配合完成的,当然里面很多都是底层的原理,在编程时我们不需要写出来,但是一定要理解这个过程才能编程正确。


补充关于OSl七层模式及其协议:
在这里插入图片描述OSI七层模型及其包含的协议如下:
物理层: 比特流传输 传输单位:比特
数据链路层: 提供介质访问,链路管理,传输单位为帧,主要包括的协议为MAC VLAN PPP
网络层:负责数据包从源到宿的传递和网际互连,传输单位为包,主要包括的协议为IP ARP ICMP
传输层:提供端到端的可靠报文传递和错误恢复,传输单位为报文,主要包括的协议为TCP UDP
会话层:建立、维护和管理会话,传输单位为SPDU,主要包括的协议为RPC NFS
表示层: 处理数据格式和加密等,传输单位为PPDU,主要包括的协议为JPEG ASII
应用层: 提供应用程序间通信,传输单位为APDU,主要包括的协议为FTP HTTP DNS

4.1.2 TCP/IP协议的诞生


下面具体讲解这几个层:
4.1.3 链路层
链路层是物理链接领域标准化的结果,也是最基本的领域,专门定义LAN、WAN、MAN等网络标准。
若两台主机通过网络进行数据交换,则需要下图的物理链接,链路层就负责这些标准。
网络连接结构图
在这里插入图片描述


4.1.4 IP层
物理链接好后,就要传输数据了。
在复杂的网络中首先我们要考虑路径的选择。IP层解决的就是这个问题。
IP本身是面向消息的、不可靠的协议。每次传输数据时都会帮我们选择路径,但并不一致。如果传输路径发生错误,则选择其他路径,如果发生数据丢失或错误,则无法解决
这就需要我们后面的TCP来搞定这个问题


4.1.5 TCP/IP层
**IP层解决的是数据传输中的路径选择问题,**只要按照这个路径传输数据就行了。
但是IP传输并不稳定,这里需要我们的TCP/UDP层以IP层提供的路径信息为基础,完成实际的数据传输,因此 TCP/UDP层 又称为 传输层(transport)。

TCP层又是如何保证传输稳定的呢?
IP层关注一个**数据包(数据传输的基本单位)的传输过程。因此即使有多个数据包,每个数据包也是由IP层实际传输的,也就是说数据实际上就是IP层传输的,并且这个面向消息的传输过程是 无序的、容易丢失的。
TCP层的作用是在数据交换过程中可以确认对方已经接到数据,并重传丢失的数据,那么即使IP层不保证数据传输,这类通信也是可靠的。
利用的TCP的
”三次握手四次挥手“**的机制。


三次握手机制:
在这里插入图片描述---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
四次挥手机制

在这里插入图片描述


4.1.6 应用层

在编程过程中,根据实际的业务我们需要改变 服务器端和客户端之间的 数据传输规则,这就是 应用层协议。
网络编程大部分内容就是设计并实现应用层协议。


4.2 实现基于TCP的服务器端/客户端

实现完整的TCP服务器端,这个过程中要关注套接字的使用方法和数据传输方法!!

4.2.1 TCP服务器端的默认函数调用过程
在这里插入图片描述
调用socket函数创建套接字,声明并初始化地址信息结构体变量,调用bind函数向套接字分配地址、以上这两个阶段已经讨论过了,下面讲解后面的过程。


4.2.2 进入等待连接请求状态
我们在使用bind函数给套接字分配了地址之后,接下来就要通过调用listen函数进入等待连接请求状态。
只有服务器端调用listen函数之后,客户端才能进入可发出连接请求的状态,换言之这时客户端才能调用connect函数(若提前调用会发生错误。)

#include <sys/socket.h>

int listen(int sock,int backlog);
-> 成功时返回0.失败返回-1
sock:希望进入等待连接状态的套接字文件描述符,传递的描述符套接字参数成为服务器端套接字(监听套接字)
backlog: 连接请求等待队列的长度,表示最多使 几个 连接请求进入队列。
在这里插入代码片

服务器的等待连接请求状态:客户端请求连接时,服务器在受理连接前一直使客户端的请求处于等待状态

在这里插入图片描述
等待连接请求状态

由上图可知作为listen函数的第一个参数传递的文件描述符套接字的用途。

客户端连接请求本身也是从网络中接收到的一种数据,而要想接收就需要套接字。此任务就由服务器端套接字完成。服务器端套接字是接收连接请求的一名门卫或一扇门。

客户端如果向服务器端询问:“请冋我是否可以发起连接?”
服务器端套接字就会亲切应答:“您好!当然可以,但系统正忙,请到等候室排号等待,准备好后会立即受理您的连接。” 同时将连接请求请到等候室。

调用 listen函数即可生成这种门卫(服务器端套接字),listen函数的第二个参数决定了等候室的大小。
等候室称为连接请求等待队列,准备好服务器端套接字和连接请求等待队列后,这种可接收连接请求的状态称为等待连接请求状态。

listen函数的第二个参数值与服务器端的特性有关,像频繁接收请求的Web服务器端至少应为15。
另外,连接请求队列的大小始终根据实验结果而定。


4.2.3 受理客户端连接请求

调用 listen函数后,若有新的连接请求,则应按序受理。受理请求意味着进入可接受数据的状态。

include <sys/socket.h>
int accept(int sock,struct sockaddr *addr,socklen_t *addrlen);
-> 成功时返回创建的套接字文件描述符,失败时返回-1
sock:服务器套接字的文件描述符
addr:保存发起连接请求的客户端地址信息的变量地址值,调用函数后向传递进入函数的地址变量参数填充客户端地址信息
addrlen:第二个参数addr结构体的长度,这里是存有长度的变量地址。函数调用完成后,该变量即被填入客户端地址长度。
在这里插入代码片

进入这种 可以接收数据的状态所需参数中sock是服务器套接字,但是服务器端套接字之前已经做了门卫,如果还用一个套接字的话,既要用来接收客户端的连接请求,一会又要和客户端进行数据传输,这不行呀。

是不是需要另一个套接字来完成呢?
是的~ accept函数会自动创建一个新的套接字,并连接到发起请求的客户端,并作为返回值返回。
accept 函数受理连接请求等待队列中 待处理的客户端连接请求。 函数调用成功时,accept函数内部将产生用于 数据I/O的套接字,并返回其文件描述符。
![在这里插入图片描述](https://img-blog.csdnimg.cn/0e6555d18f31427286fc12de7189d842.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBATXIubGlhbmflkYA=,size_16,color_FFFFFF,t_70,g_se,x_16#pic_left =)从上图可以看出,accept函数调用时,从等待队列中取出一个连接请求,创建套接字并完成连接请求~
使用这个新创建的套接字 来作为 客户端的代表


4.2.4 回顾Hello World服务器端

1 服务器端 hello_server.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
void error_handling(char *message);

int main(int argc, char *argv[])
{
   
	int serv_sock;											// 服务器端套接字文件描述符
	int clnt_sock;											// 客户端套接字文件描述符

	struct sockaddr_in serv_addr;							// IPv4地址信息结构体
	struct sockaddr_in clnt_addr;
	socklen_t clnt_addr_size;

	char message[] = "Hello world";

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

	serv_sock = socket(PF_INET, SOCK_STREAM, 0);			// 服务器端分配套接字 IPv4面向连接基于流的套接字
	if(serv_sock == -1){
   
		error_handling("socket() error");
	}

	memset(&serv_addr, 0, sizeof(serv_addr));				// 初始化服务器端的IPv4地址信息结构体 信息
	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){
   	// 把服务器端的套接字和IPv4地址信息结合
		error_handling("bind() error");
	}
															
	if(listen(serv_sock, 5) == -1){
   							// 服务器端 进入等待连接请求状态 队列长度为5  
		error_handling("listen() error");					// 此时套接字成为服务器端套接字
	}

	clnt_addr_size = sizeof(clnt_addr);						 
	//调用accept函数从队头去一个连接请求与客户端建立连接,并返回创建的套接字文件描述符。若等待队列为空,则不会返回,直至队列中出现新的客户端连接
	clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_addr, &clnt_addr_size);

	if(clnt_sock == -1){
   
		error_handling("accept() error");
	}
	
	write(clnt_sock, message, sizeof(message));				// 调用write函数向客户端传输数据
	close(clnt_sock);
	close(serv_sock);
	return 0;
}
void error_handling(char* message)
{
   
	fputs(message,stderr);
	fputc('\n',stderr);
	exit(1);
}
在这里插入代码片

4.2.5 TCP客户端的默认函数调用顺序

在这里插入图片描述
有个问题:客户端套接字不用分配地址信息嘛???

实现服务器端必经过程之一就是給套接字分配P和端口号。但客户端实现过程中并未出现套接宇地址分配,而是创建套接字后立即调connect函数。
难道客户端套接宇无需分配IP和端口?
当然不是!网络数据交換必须分配IP和端口。既然如此,那客户端套接字何时、何地、如何分配地址呢?
何时? 调用 connect函数时。
何地? 操作系统,更准确地说是在内核中。
如何? IP用计算机(主机)的IP,端口随机。
客户端的IP地址和端口在调用 connect函数时自动分配,无需调用标记的bind函数进行分配。


4.2.6 回顾Hello World客户端

hello_client.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
void error_handling(char *message);

int main(int argc, char *argv[])
{
   
	int sock;
	struct sockaddr_in serv_addr;
	char message[] = "Hello world";
	int str_len;

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

	sock = socket
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Mr.liang呀

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值