IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 网络协议 -> 【TCP/IP网络编程】(02):基于TCP的客户端/服务端 -> 正文阅读

[网络协议]【TCP/IP网络编程】(02):基于TCP的客户端/服务端

TCP服务端/客户端

TCP与UDP

TCP与UDP是传输层常见的网络协议。TCP/IP协议栈如图:
在这里插入图片描述

最底层为数据链路层,往上是IP层,再通过TCP或UDP到达应用层。

链路层负责点到点的传输,而IP层负责端到端的传输。但IP协议是不可靠的协议,数据丢失或者错误的情况无法解决。于是TCP就负责数据传输的可靠性。收到后确认,需要则重传。

应用层

我们所说的socket编程,其实都是发生在应用层的,大部分内容是设计和实现应用层的协议,上面的细节由socket自动处理。

TCP服务端的调用顺序为:

  1. socket()创建套接字
  2. bind()分配IP地址和端口号
  3. listen()等待连接请求
  4. accept()允许连接
  5. read()/write()数据交换
  6. close()断开连接

服务端:等待连接请求

在调用了listen()之后,客户端才可以调用connect()(想要建立连接先要接起电话)

#include <sys/socket.h>
int listen (int sock,int backlog);

第一个参数接收进入等待连接请求状态的套接字文件描述符(可以理解为编号),第二个参数是等待连接队列的长度。

可以理解为listen会生成一个门卫(服务端套接字),并设定等候室的大小,让请求的连接进入等候室。

服务端:允许连接

当有了新的请求时,需要新的套接字来进入这种状态,这时需要accept函数。

#include <sys/socket.h>
int accept(int sockfd,struct sockaddr *addr,socklen_t *addrlen)

accept生成一个用于数据I/O的套接字,返回其文件描述符sockfd。第二个参数用于保存客户端地址信息。第三个参数用于保存客户端地址的长度。

简单来说就是从等待队列中取出一个连接请求,创建套接字然后完成连接。

客户端:请求连接

客户端请求连接时调用的是connect函数。

#include <sys/socket.h>
int connect(int sockfd,struct sockaddr *servaddr,socklen_t addrlen);

在服务端接收连接请求后,或者中断连接请求后,connect函数会返回。(接收连接不是accept,是进入到了服务端的请求队列)

服务端没有bind的过程。在调用connect函数时自动分配。

客户端与服务端的关系是:

请添加图片描述

回声服务器

回声的意思就是把服务端将客户端传来的字符数据再原封不动的传回去。

我们之前的模式都是处理完一个客户端请求就退出服务端。如果想要连续服务就需要用循环调用accept。

(如果想要同时服务多个客户端需要学习进程与线程)

我们来看回声服务器的核心代码:

int main(int argc,char* argv[])
{
	int serv_sock,clnt_sock;//服务端和客户端的文件描述符
	char message[BUF_SIZE];
	int strlen,i;
	
	struct sockaddr_in serv_adr,clnt_adr;//服务端和客户端的地址信息
	socklen_t clnt_adr_sz;
	...
	serv_sock=socket(PF_INET,SOCK_STREAM,0);//创建TCP套接字
	if(serv_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=htonl(INADDR_ANY);
	serv_adr.sin_port=htons(atoi(argv[1]));
	
	//bind
	if(bind(serv_sock,(struct sockaddr*)&serv_adr),sizeof(serv_adr))==-1)
		error_handling("bind()error");
	
	//listen
	if(listen(serv_sock,5)==-1)
		error_handling("listen() error");
		
	clnt_adr_sz=sizeof(clnt_adr);
	
	//重点 accept
	for(int i=0;i<5;i++)//等待连接队列是5
	{
		clnt_sock=accept(serv_sock,(struct sockaddr*)&clnt_adr,&clnt_adr_sz);
		if(clnt_sock==-1)
			error_handling("accept() error");
		else
			printf("Connected client %d \n",i+1);//已连上客户端
		
		while((str_len=read(clnt_sock,message,BUF_SIZE))!=0)
			wirte(clnt_sock,message,str_len);//读到什么写什么
		
		close(clnt_sock);//关闭一个客户端连接
	}
	close(serv_sock);//关闭服务端
	return 0;
}

客户端我们只看socket建立之后的部分,也就是调用connect:

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);
	message[str_len]=0;
	printf("Message from server: %s",message);
}
close(sock);
return 0;

客户端是真正进行操作的地方。这里message存储输入的内容,如果是q则退出,不是就写入,再读出服务器内容。

两边相互协作。客户端写入的内容被服务端读出,再写回。

存在的问题

上述代码是有一些问题的。客户端中wirte想要写入message的内容,但对于TCP客户端来说,有可能出现多次写入一次性读出的情况。这样客户端就会一次性收到服务端传来的大量信息。

而且当字符串太长时,操作系统可能会把数据分成多个数据包发送,这时客户端可能没有收到全部数据包就调用read。

解决方案

1.提前确认接收数据的大小,然后在接受时循环调用read来读取

while(1)
{
	fputs("Input message(Q to quit):",stdout);
	fgets(message,BUF_SIZE,stdin);
	
	if(!strcmp(message,"q\n")||!strcmp(message,"Q\n"))
		break;
	
	str_len=write(sock,message,strlen(message));
	recv_len=0;
	while(recv_len<str_len)
	{
		recv_cnt=read(sock,message,BUF_SIZE-1)if(recv_cnt==-1)
			error_handling...;
		recv_len+=rev_cnt;
	}
	message[str_len]=0;
	printf("Message from server: %s",message);
}
close(sock);
return 0;

可以对照一下与上面的区别,这样可以让write和read对等。

2.定义应用层协议

定义客户端和服务端传递数据的字节数,这样使得TCP传输变的有数据边界。

TCP原理补充

与对方套接字连接

再在TCP套接字与对方套接字连接时,会经历一个三次握手的过程。

套接字是全双工的。例如A到B

请添加图片描述

1.主机A发送数据包序号信息SYN

2.主机B返回确认以及B的数据包序号SYN+ACK

3.主机A返回确认信号ACK

与对方主机交换数据

交换数据时返回的确认号为:

ACK=SEQ+传递的字节数+1,这样可以看出是否丢失字节。

与对方套接字断开连接

断开连接的阶段也叫作四次挥手。

请添加图片描述

1.主机A发送数据包序号,希望断开

2.主机B返回确认号和数据包序号,开始准备

3.主机B发送确认号和数据包序号,可以断开

4.主机A返回确认号和数据包序号,确认断开

  网络协议 最新文章
使用Easyswoole 搭建简单的Websoket服务
常见的数据通信方式有哪些?
Openssl 1024bit RSA算法---公私钥获取和处
HTTPS协议的密钥交换流程
《小白WEB安全入门》03. 漏洞篇
HttpRunner4.x 安装与使用
2021-07-04
手写RPC学习笔记
K8S高可用版本部署
mySQL计算IP地址范围
上一篇文章      下一篇文章      查看所有文章
加:2022-03-17 22:35:21  更:2022-03-17 22:36:35 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/26 5:47:51-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码