Linux--Socket编程TCP

诡异森林。 2024-08-18 12:37:02 阅读 79

前文:Socket套接字编程

TCP的特点

面向连接:TCP 在发送数据之前,必须先建立连接。可靠性:TCP 提供了数据传输的可靠性。面向字节流:TCP 是一个面向字节流的协议,这意味着 TCP 将应用程序交下来的数据看成是一连串的无结构的字节流。

TcpServer.hpp

创建一个Tcp服务端

在这里插入图片描述

代码

<code>#pragma once

#include<iostream>

#include<string>

#include<sys/types.h>

#include<sys/socket.h>

#include<netinet/in.h>

#include<cstring>

#include<arpa/inet.h>

#include<unistd.h>

#include <sys/wait.h>

#include<functional>

#include<pthread.h>

#include"InetAddr.hpp"

#include"Log.hpp"

#include"Threadpool.hpp"

enum

{ -- -->

SOCKET_ERROR = 1,

BIND_ERROR,

LISTEN_ERROR,

USAGE_ERROR

};

const static int defaultsockfd = -1;//默认文件描述符

const static int gbacklog = 16;//默认最大连接数

using task_t=std::function<void*()>; //任务函数的类型(V3)

class TcpServer;

//线程类型数据

class ThreadData

{

public:

ThreadData(int fd,InetAddr addr,TcpServer* s)

:sockfd(fd),

clientaddr(addr),

self(s)

{ }

public:

int sockfd;//文件描述符

InetAddr clientaddr;//客户端地址

TcpServer* self;//服务端

};

class TcpServer

{

public:

TcpServer(int port)

:_port(port),

_listensock(defaultsockfd),

_isrunning(false)

{ }

void InitServer()

{

//1.创建字节流套接字

_listensock=socket(AF_INET,SOCK_STREAM,0);

if(_listensock<0)

{

LOG(FATAL, "socket error");

exit(SOCKET_ERROR);

}

LOG(DEBUG, "socket create success, sockfd is : %d\n", _listensock);

//2.bind

struct sockaddr_in local;

memset(&local,0,sizeof(local));

local.sin_family=AF_INET;

local.sin_port=htons(_port);

local.sin_addr.s_addr=INADDR_ANY;

int n= ::bind(_listensock,(struct sockaddr*)&local,sizeof(local));

if(n<0)

{

LOG(FATAL, "bind error\n");

exit(BIND_ERROR);

}

LOG(DEBUG, "bind success, sockfd is : %d\n", _listensock);

//3.监听客户端,等待被连接

n=listen(_listensock,gbacklog);

if(n<0)

{

LOG(FATAL, "listen error");

exit(LISTEN_ERROR);

}

LOG(DEBUG, "listen success, sockfd is : %d\n", _listensock);

}

//将接收到的数据进行处理,完成对应服务

void Service(int sockfd,InetAddr client)

{

LOG(DEBUG, "get a new link, info %s %d ,fd: %d \n ",client.Ip().c_str(),client.Port(),sockfd);

std::string clientaddr = "[" + client.Ip() + ":" + std::to_string(client.Port()) + "]# ";

while(true)

{

char inbuffer[1024];

ssize_t n = read(sockfd,inbuffer,sizeof(inbuffer)-1);//读取数据

if(n>0)

{

inbuffer[n]=0;

std::cout<<clientaddr<<inbuffer<<std::endl;//打印对应的数据

std::string echo_string = "[server echo]# ";

echo_string+=inbuffer;

write(sockfd,echo_string.c_str(),echo_string.size());//返回给客户端

}

else if(n==0) //client退出并且关闭连接了

{

LOG(INFO, "%s quit\n", clientaddr.c_str());

break;

}

else

{

LOG(ERROR, "read error\n");

break;

}

}

close(sockfd);

}

//线程的处理函数

static void* HandlerSock(void* args)

{

pthread_detach(pthread_self());//线程分离

ThreadData* td=static_cast<ThreadData*>(args);//创建对应线程数据类型

td->self->Service(td->sockfd,td->clientaddr);

delete td;

return nullptr;

}

//服务器循环运行着

void Loop()

{

_isrunning=true;

while(_isrunning)

{

struct sockaddr_in peer;//sock地址peer

socklen_t len=sizeof(peer);

//要先通过连接才能够进行通信

int sockfd=::accept(_listensock,(struct sockaddr *)&peer,&len);//这里的监听sock和sockfd是不同的

if(sockfd<0)

{

LOG(WARNING, "accept error\n");

continue;

}

//Version0 这种版本只能接收一次需求,无法多客户端连接

// Service(sockfd,InetAddr(peer));

//Version1 多进程

// pid_t id=fork();

// if(id>0)

// {

// //子进程负责连接,父进程负责监听,

// close(_listensock);

// if(fork()>0) exit(0);

// //孙进程负责服务,由于子进程连接之后,子进程会进行回收,因此孙进程称为孤儿进程,之后不受子进程的影响

// //类似线程分离,是独立的,之后受系统进行回收

// Service(sockfd,InetAddr(peer));

// exit(0);

// }

// //父进程只负责监听,不需要进行连接

// close(sockfd);

// waitpid(id,nullptr,0);

//Version2:采用多线程

pthread_t t;

ThreadData* td=new ThreadData(sockfd,peer,this);

pthread_create(&t,nullptr,HandlerSock,td);//将线程分离

//Version3:线程池

//task_t t = std::bind(&TcpServer::Service,this,sockfd,InetAddr(peer));

//ThreadPool<task_t>::GetInstance()->Enqueue(t);

}

_isrunning=false;

}

~TcpServer()

{

if(_listensock>defaultsockfd)

{

close(_listensock);

}

}

private:

uint16_t _port;//端口号

int _listensock;//监听的文件描述符

bool _isrunning;//启动

};

解释

在这里插入图片描述

初始化服务端,主要完成套接字的创建绑定,已经完成对应的监听客户端,因为Tcp是有连接的,所以需要监听客户端是否有请求连接的需求;

<code>SOCK_STREAM表示字节流

gbacklog:这个参数定义了内核应该为相应套接字排队的最大连接数。这个值至少为0;其实际值由系统限制,可以通过sysctl命令的net.core.somaxconn参数查看和设置。需要注意的是,这个值并不是指系统能处理的并发连接数,而是内核中等待accept(处理的连接队列的最大长度

在这里插入图片描述

启动服务器之后,通过循环让服务端不断运行着,在循环里面,服务端可能接收到多个客户端请求的连接,所以accpet要在循环中不断接收看是否有对应的连接;

连接完之后通过Service服务函数完成双方的通信

在这里插入图片描述

在Loop中,循环表示接收多个客户端,而Service中,循环表示每个服务端与客户端的通信保持;

由于tcp是面向字节流的,所以可以利用文件描述符的性质,运用read函数,读取对应的发送端数据.

当n大于0时,表示对方有数据发送,处理完信息后反馈给对方;

当n==0,表示sockfd被关闭了,也就是连接被断开了;

当n小于0时,表示出现错误;

如果一直没有发送数据,那么会在read函数这里发生阻塞

在这里插入图片描述

而对于服务的处理,这里有多种方法,如果直接使用Service函数的话,是不可行的,因为这样无法多客户端连接:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

通过多进程的方法,让父进程只负责监听,子进程负责连接,孙进程负责服务,由于孙进程是孤儿进程,相当于线程分离,这样处理服务时就不会受到父子进程的影响了;也就能完成多客户端的通信了;

在这里插入图片描述

直接通过多线程的方法,将创建的线程进行分离,完成对应的服务任务

main.cc

<code>#include"TcpServer.hpp"

#include<memory>

void Usage(std::string proc)

{ -- -->

std::cout << "Usage:\n\t" << proc << " local_port\n" << std::endl;

}

// ./main.cc 8080

int main(int argc,char* argv[])

{

if(argc!=2)

{

Usage(argv[0]);

return 1;

}

EnableScreen();//打印到屏幕

uint16_t port=std::stoi(argv[1]);//获取端口号

std::unique_ptr<TcpServer> tsvr=std::make_unique<TcpServer>(port);//服务端的指针

tsvr->InitServer();//初始化

tsvr->Loop();//循环运行

return 0;

}

TcpClient.cc

#include<iostream>

#include<string>

#include<sys/types.h>

#include<sys/socket.h>

#include<arpa/inet.h>

#include<netinet/in.h>

#include<unistd.h>

#include<cstring>

void Usage(std::string proc)

{

std::cout << "Usage:\n\t" << proc << " serverip serverport\n"

<< std::endl;

}

// ./TcpClient.cc 127.0.0.1 8080

int main(int argc,char* argv[])

{

if(argc!=3)

{

Usage(argv[0]);

exit(1);

}

std::string serverip=argv[1];//服务端ip

uint16_t serverport=std::stoi(argv[2]);//服务端端口号

int sockfd = socket(AF_INET, SOCK_STREAM, 0);//创建套接字

if (sockfd < 0)

{

std::cerr << "socket error" << std::endl;

exit(2);

}

struct sockaddr_in server;

memset(&server,0,sizeof(server));

server.sin_family=AF_INET;

server.sin_port=htons(serverport);

server.sin_addr.s_addr=inet_addr(serverip.c_str());

//连接服务端

int n=connect(sockfd,(struct sockaddr*)&server,sizeof(server));

if(n<0)

{

std::cerr << "connect error" << std::endl;

exit(3);

}

while(true)

{

std::cout<<"Please Enter# ";

std::string outstring;

getline(std::cin,outstring);

//发送对应数据

ssize_t s=send(sockfd,outstring.c_str(),outstring.size(),0);

if(s>0)

{

char inbuffer[1024];

ssize_t m=recv(sockfd,inbuffer,sizeof(inbuffer)-1,0);//接收对应数据

if(m>0)

{

inbuffer[m]=0;

std::cout<<inbuffer<<std::endl;

}

else

{

break;

}

}

else

{

break;

}

}

close(sockfd);

return 0;

}

结果

在这里插入图片描述



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。