Linux系统下TCP迭代服务器的实现

tech2022-09-11  96

TCP/IP协议栈

在写代码之前我们先了解什么是协议栈。

根据数据传输方式的不同,基于网络协议的套接字一般分为TCP套接字和UDP套接字。TCP套接字是面向连接的,因此又称基于流(stream)的套接字。TCP是 Trasmission Control Protocol (传输控制协议)的简写,意为“对数据传输过程的控制”。

协议栈一般分为4个层次:

第一层次:数据链路层

链路层是物理链接领域标准化的结果,也是最基本的领域,专门定义LAN、WAN、MAN 等网络标准。

第二层次:IP层

IP是面向消息的、不可靠的协议。每次传输数据时会帮我们选择路径,但并不一致。如果传输中发生路径错误,则选择其他路径;但如果发生数据丢失或错误,则无法解决。换言之,IP协议是无法应对数据错误的。

第三层次:TCP/UDP 层

IP层解决数据传输中的路径选择问题,只需照此路径传输数据即可。

TCP和UDP 层以 IP层提供的路径信息为基础完成实际的数据传输,故该层又称传输层(Transport)。

第四层次:应用层

前面三个层次,套接字通信过程中都是自动处理的。为了”使程序员从这些细节中解放出来“。选择数据传输路径、数据确认过程都被隐藏到套接字内部。前面三个层次都是为了给应用层提供服务的。

简单了解了一下TCP的底层原理我们就开始写一个简单的迭代服务器来进一步理解TCP

首先我们先来编写服务端代码

1. 第一步准备好需要使用的头文件

#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <arpa/inet.h> #include <sys/socket.h> #include <netinet/in.h> #include <pthread.h>

2. 第二步开始创建套接字

void sokc_ls() { //创建套接字(socket) int serv_sock; //这是一个基于IPv4的TCP套接字 serv_sock = socket(PF_INET,SOCK_STREAM,0); if(serv_sock < 0)//判断套接字(socket)是否创建成功 { //在一般的编程中防御性编程是十分重要的 std::cout<<"creat sock failed!"<<std::endl; return; } }

3. 分配套接字地址

struct sockaddr_in servaddr; memset(&servaddr,0,sizeof(servaddr));//将servaddr清0 预防申请的变量内有残留信息存在造成错误 //配置地址 servaddr.sin_family = AF_INET;//IPv4协议族 servaddr.sin_addr.s_addr = htonl(INADDR_ANY);//相当于”0.0.0.0“ 监听所有地址 servaddr.sin_port = htons(9527);//分配端口 //配置完地址 就可以开始分配了 int ret = bind(serv_sock,(struct sockaddr*)&servaddr,sizeof(servaddr)); if(ret == -1)//成功时返回0 失败时返回-1 { std::cout<<"bind failed!"<<std::endl; close(server); return; }

4. 等待连接请求(监听)

int ret = listen(serv_sock,3);//第二个参数表示可监听的数量 if(ret == -1)//成功返回0 失败返回-1 { std::cout<<"listen failed!"<<std::endl; close(server); return; }

5. 创建分机 ,并与客户端连接

int client; struct sockaddr_in cliaddr; socklen_t cliaddrlen; char buffer[1024]; while(1) { memset(buffer,0,sizeof(buffer));//将buffer清0 防止有残留数据存在 client = accept(serv_sock,(struct sockaddr*)&cliaddr,&cliaddrlen); if(client == -1)//成功时返回创建的套接字文件描述符,失败时返回-1 { std::cout << "accept failed !" << std::endl; close(server); return; } //读取数据 read(client,buffer,sizeof(buffer)); //写入数据 ssize_t len = write(client,buffer,stelen(buffer)); if(len != (ssize_t)strlen(buffer))//返回的长度与发送的长度不一致 { std::cout<<"write failed!"<<std::endl; close(serv_sock); return; } //关闭套接字 close(client);//这步可不执行 } close(serv_sock);//当服务端关闭时,客户端也会同时关闭。

服务端代码写完,我们再来写客户端代码

void client_ls() { //创建套接字 int client; struct sockaddr_in servaddr; client = socket(PF_INET,SOCK_STREAM.0); if(serv_sock < 0)//判断套接字(socket)是否创建成功 { std::cout<<"creat sock failed!"<<std::endl; return; } memset(servaddr,0,sizeof(servaddr));//清0 //配置地址 servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");//”127.0.0.1“为回环地址 一般在本机调试时使用 servaddr.sin_port = htons(9527); //与服务端连接 int ret = connect(client,(struct sockaddr*)&servaddr,sizeof(servaddr)); if(ret==0)//成功时返回0 失败时返回-1 { char buffer[256] = "hello,here is client!"; //往服务端写入数据 write(client,buffer,strlen(buffer)); memset(buffer,0,sizeof(buffer));//清0 //读取从服务端返回的数据 read(client,buffer,sizeof(buffer)); std::cout<<buffer;//输出读取的数据 } else { printf("%s(%d):%s %d\n", __FILE__, __LINE__, __FUNCTION__, ret); } //关闭套接字 close(client); std::cout<<"client done !"<<std::endl; }

服务端与客户端的代码都写完了,我们来测试一下

int main(int argc,char* argv[]) { //使用创建子进程的方法来进行测试 pid_t pid = fork(); if(pid == 0)//当pid等于0 时,代表的是子进程 所以我们调用客户端函数 { sleep(1);//暂停一秒 防止第一次连接失败 client_ls(); client_ls(); } else if(pid > 0)//当pid大于0时,代表的是父进程,所以我们调用服务端函数 { sokc_ls(); int status = 0; writ(&status);//为防止僵尸进程的出现 使用fork函数时,推荐将writ函数与fork函数同时写入,在进行内容编辑 } else { std::cout << "fork failed!" << pid << std::endl;//进程创建失败 } }

因为没有设置退出机制所以会在俩次客户端执行结束后卡住,可使用Ctrl+C进行退出。

最新回复(0)