Linux---TCP套接字编程

三次握手协议

基于TCP协议的网络编程的特点是 : 面向连接, 可靠传输, 提供字节流服务, 由于TCP协议的特性, 因此哎客户端与服务端进行数据通信时, 需要两端先建立连接
建立连接时 , 首先由客户端向服务端发起建立连接的请求, 确定服务端是否在线, 服务端在线的话就会对客户端做出回复, 但是为了避免客户端发起请求后进下线, 因此服务端在进行回复的信息中也会加上向客户端的请求,确定客户端是否能在线, 最后客户端接受到服务端的回复与请求信息之后,在对服务端做出回复--------这就是客户端与服务端进行连接的三次握手协议

在三次握手协议中, 我们将发起的请求称为SYN,将回复称为ACK
在这里插入图片描述

  • 接口
  1. 对于服务端而言, 他在绑定问地址与端口之后, 会有一个状态为监听状态, 监听状态就是服务端等待客户端发起链接的请求
int listen(int sockfd, int backlog);

sockfd : 传入的套接字描述符
backlog : backlog指的是在sockfd所指向的结构体中的挂起链接队列中的最大数目
当服务端开始监听,并且客户端已经向服务端发器请求时,在服务端的套接字结构体中,就有两个队列, 一个是未完成的链接队列, 里面存放着正在建立连接的客户端的请求信息, 另一个是已经完成链接的队列,而backlog就描述的是已经完成链接的队列的最大个数,在进行数据通信的时候,服务端会从以完成链接队列中获取客户端信息进行通信
返回值 : 成功返回0,失败返回-1

  1. 对于客户端而言, 往往都是先进行数据的发送, 因此需要主动发起连接请求
int connect(int sockfd, const struct sockaddr *addr,
                   socklen_t addrlen);

connect接口就是用于客户端向服务端发起三次握手链接的请求的接口, 其中的参数都是描述服务端的信息.
返回值 : 成功返回0,失败返回-1

  1. 在建立好链接之后, 客户端与服务端在进行数据通信之前,服务端需要先从已完成链接队列中获取待客户端的结点, accept就是用来获取的接口
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

返回值 : 对于服务端而言, 一开始绑定的套接字的作用在于,让客户端能向其发起连接请求 , 他并不会执行客户端与服务端之间的数据通信, 所以在服务端监听的时候,当客户端发送过来请求信息时, 一开始的套接字就会我发送过来的客户端信息创建出一个新的套接字, 并且保存在已完成链接队列中, 而accept接口在获取到队列中的信息记性通信的时候, 其实是新的套接字在进行数据的通信, 而accept的返回值就是新的套接字的描述符

总结而言 : 一开始创建的套接字只用于与多个客户端之间建立连接, 而新创建的套接字才是与客户端之间进行数据通信的真正本质

封装TcpSocket类

class TcpSocket{
public:
	//构造函数
	 TcpSocket()                                                                                                              
        :_sockfd(-1)
     {}
	//析构函数
	~TcpSocket(){
       Close();
    }
	//套节接字初始化
	 bool SockInit(){
         _sockfd=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
         if(_sockfd<0){
             cout<<"创建套接字失败!!"<<endl;
             return false;
         }
         return true;
     }
	//绑定地址
	bool Bind(string& ip,uint16_t port){
         struct sockaddr_in addr;
         addr.sin_family=AF_INET;
         addr.sin_port=htons(port);
         addr.sin_addr.s_addr=inet_addr(ip.c_str());
         socklen_t len=sizeof(addr);
         int ret=bind(_sockfd,(struct sockaddr*)&addr,len);
         if(ret<0){
              cout<<"绑定地址失败!!"<<endl;
              return false;
         }
         return true;                                                                                                         
     }
	//监听
	bool Listen(int backlog=5){
         int ret=listen(_sockfd,backlog);
         if(ret<0){
             cout<<"服务端监听失败!!"<<endl;
             return false;
         }
         return true;
     }
	//发送链接请求
	 bool Connect(string& ip,uint16_t port){
         struct sockaddr_in addr;
         addr.sin_family=AF_INET;
         addr.sin_port=htons(port);
         addr.sin_addr.s_addr=inet_addr(ip.c_str());
         socklen_t len=sizeof(addr);
         int ret=connect(_sockfd,(struct sockaddr*)&addr,len);
         if(ret<0){
             cout<<"发送链接请求失败!!"<<endl;
             return false;
         }
         return true;
     }
	//获取新连接
	void Setfd(int fd){
         _sockfd=fd;
     }
     //获取已完成的新连接
     bool Accept(TcpSocket& newsock){
         struct sockaddr_in addr;
         socklen_t len=sizeof(addr);
         //accept的返回值为获取到的新的套接字的描述符
         int newfd=accept(_sockfd,(struct sockaddr*)&addr, &len);
         if(newfd<0){
             cout<<"获取新连接失败!!"<<endl;
             return false;
         }
         newsock.Setfd(newfd);
         return true;
     }
	//发送数据
	bool Send(string& buf){
         int ret=send(_sockfd,&buf[0],buf.size(),0);
         if(ret<0){
             cout<<"接受数据失败"<<endl;                                                                                      
             return false;
         }
         return true;
     }
	//接受数据
	bool Recv(string& buf){
         char tmp[4096]={0};
         int ret=recv(_sockfd,tmp,4096,0);
         if(ret<0){
             cout<<"接受数据失败"<<endl;
             return false;
         }else if(ret==0){
             cout<<"链接断开"<<endl;
             return false;
         }
         //将tmp中的ret个字节色数据拷贝到buf中
                  buf.assign(tmp,ret);
         return true;
     }
	//关闭套接字
	bool Closr(){
		if(_sockfd>0){
			colse(_sockfd);
			_sockfd=-1;
		}
		return true;
	}
private:
	int _sockfd;
};

多进程服务端

由于急于TCP协议的服务端每次与一个客户端建立连接就会创建一个新的套接字, 为了避免程序卡死, 我们利用多个执行流来完成急于TCP协议的服务端, 我们一个让主进程来负责与客户端建立连接,再创建出子进程来完成与客户端的数据通信

#define CHECK_RET(q) if((q)==false){return -1;}
void gigcb(int signo){
	while(waitpid(-1,NULL,WNOHANG)>0);
}
int main(int argc,char* argv[]){
	if(argc!=3){
		cout<<"./tcp_srv ip port"<<endl;
		return -1;
	}
	signal(SIGCHLD,sigcb)
	string ip=argv[1];
	uint16_t port=argv[2];
	TcpSocket sock;
	CHECK_RET(sock.SocketInit());
	CHECK_RET(sock.Bind(ip,port));
	CHECK_RET(sock.Lisent());
	TcpSocket newsock;
	while(1){
		bool ret=sock.Accept(newsock);
		if(ret==false){
			continue;
		}
		if(fock()==0){
			while(1){
				//接受数据
                string buf;
                ret=newsocket.Recv(buf);
                if(ret==false){
                    newsocket.Close();
                    continue;
                }
                cout<<"客户端说:"<<buf<<endl;
                //发送数据
                buf.clear();
                cin>>buf;                                                                                                      
                newsocket.Send(buf);
			}
			newsock.Close();
			exit(0);
		}
		newsock.Close();
	}
	sock.Close();
	return 0;
}

注意:
在上述代码中, 主线程只用来接受新的客户端信息,在接收到之后就立马创建新的进程,让子进程来执行后续的通信任务 , 通信结束后,关闭 newsock 退出进程, 由于父子进程拥有同样的资源 ( 创建子进程,拷贝父进程的PCB ), 因此子进程直接可以对newsock进行操作, 而父进程的newsock没有作用所以直接进行关闭

为了避免当没有客户端发起连接时,服务端的父进程退出后,使所有用于数据通信的服务端子进程称为僵尸进程, 则对进程退出时返回的 SIGCHLD 信号进行处理, 使用waitpid()函数任意等待一个进程退出,直到所有进程退出为止

TCP的客户端程序与UDP服务端程序相差无几,只需在初始化玩套接字之后,添加一部向服务端发起连接请求即可, 具体代码参考文章—UDP套接字编程

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Linux-C编程API函数(掌握这些足够).pdf》是一本关于Linux系统中C编程的API函数的参考手册。本书内容涵盖了Linux系统下各种常用的C编程API函数,对于学习和开发Linux应用程序非常有帮助。 通过掌握这些API函数,读者可以深入了解Linux系统的底层工作原理,掌握Linux应用程序的开发技巧和规范。该手册详细介绍了Linux系统中文件操作、进程管理、内存管理、网络编程等方面的API函数。 在文件操作方面,读者可以学习到如何打开、创建、读写、关闭文件等常用操作。此外,还介绍了文件描述符、文件权限、文件状态等相关概念和操作方法。 在进程管理方面,读者可以学习到如何创建子进程、执行程序、等待子进程结束等操作。同时,还介绍了进程间通信、进程信号等相关内容。 在内存管理方面,读者可以学习到如何动态分配和释放内存、管理进程地址空间等操作。同时,还介绍了内存映射、共享内存等高级内存管理技术。 在网络编程方面,读者可以学习到如何创建网络套接字、建立TCP/IP连接、进行数据传输等操作。此外,还介绍了网络通信协议、套接字选项等相关内容。 总之,《Linux-C编程API函数(掌握这些足够).pdf》是一本全面系统的Linux-C编程参考手册,通过学习和掌握其中的API函数,读者可以更加深入地了解和掌握Linux系统编程的技术和方法。无论是对于初学者还是有一定经验的开发者来说,都是一本非常有价值的参考书。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值