[네트워크 통신] 소켓 프로그래밍 - TCP 소켓

TCP는 여전히 해당 소켓에 익숙해지기 위해 코드를 사용하는데 udp에서 많은 인터페이스가 사용되었으므로
따로 제목으로 꺼내지 않고 처음 나타나는 인터페이스만 제목으로 사용합니다.

TCP 소켓을 통해 상대방의 응용 계층으로 데이터가 전달되고 두 프로세스 간의 통신이 완료됩니다.

서버 tcp_server

tcpserver.hpp(캡슐화)

tcpServer.hpp에서 캡슐화를 위한 네임스페이스 yzq를 생성합니다
. 네임스페이스에서 TcpServer 클래스를 정의합니다.

이 클래스에는 구성, 소멸, 초기화(initServer) 시작(start)이 포함됩니다.


initServer 초기화

1. 소켓 생성

청취 포트 번호 설정(나중에 설명), 포트 번호는 프로세스의 고유성을 식별하는 데 필요합니다.


클래스 외부의 기본 포트 번호 8888을 생성자 매개변수 port의 기본값으로 설정합니다.


소켓 생성


맨 소켓 입력

첫 번째 매개변수 도메인은 네트워크 통신과 로컬 통신을 구분하기 위해 사용되며,
네트워크 통신을 원할 경우 AF_INET을 사용하고,
로컬 통신을 원할 경우 AF_UNIX를 사용한다.

두 번째 매개변수 유형, 소켓에 해당하는 서비스 유형


SOCK_STREAM 스트림 소켓
SOCK_DGRAM 비연결 신뢰할 수 없는 통신(사용자 데이터그램)

세 번째 매개변수인 protocol은 사용하려는 프로토콜을 나타냅니다. 기본 프로토콜은 0입니다.
스트림 소켓인 경우 시스템은 이를 TCP 프로토콜로 간주하고 사용자 데이터그램인 경우 시스템은 이를 고려합니다. UDP 프로토콜이 됩니다.

소켓의 반환 값: 성공하면 파일 기술자가 반환되고 실패하면 -1이 반환됩니다.


네트워크 통신, 스트림 소켓을 나타내며 시스템은 이를 TCP 프로토콜로 간주합니다.


오류 정보를 저장하기 위해 err.hpp의 열거형 만들기


생성에 실패하면 프로그램을 종료하십시오.

2. 바인드 바인드

결합을 보려면 man 2 bind를 입력하십시오.

소켓에 이름 바인드
첫 번째 매개변수 sockfd는 소켓
두 번째 매개변수 addr은 일반 구조 유형
세 번째 매개변수 addrlen은 두 번째 매개변수의 실제 길이

바인딩 반환 값: 성공하면 0 반환, 실패하면 -1 반환


바인드의 사용은 일반 구조의 도움으로 실현되어야 하므로
네트워크 통신 유형 로컬의 구조를 정의하십시오.

이전 블로그에서 sockaddr_in 구조의 내부 구성에 대해 자세히 설명했는데 이해가 되지 않으면 struct sockaddr_in 이해를
참조하십시오.


htons - 호스트 시퀀스를 네트워크 시퀀스로 변환

짧은 정수의 호스트-네트워크 시퀀스를 나타내는 man htons를 입력합니다 .

그래서 호스트의 port_를 변환해서 로컬 sin_port(포트 번호)로 넘겨줘야 합니다.


INADDR_ANY는 바인드의 모든 IP를 의미합니다.


바인딩 실패 시 -1 반환


3. 모니터

청취 - 청취 상태로 설정

현재 소켓 상태를 청취 상태로 설정하려면 man 2 listen을 입력하십시오.

첫 번째 매개변수 sockfd는 소켓이고
두 번째 매개변수는 임시로 설명하지 않고 일반적으로 정수로 설정하며
성공하면 0을 반환하고 실패하면 -1을 반환한다.


모니터링에 실패하면 -1을 반환하고 프로그램을 종료합니다.


클래스 외부에서 기본 정수를 32로 설정

시작

부울 변수 quit_를 설정합니다. true이면 서버가 시작되었음을 의미하고 false이면 서버가 시작되지 않음을 의미합니다.


서버가 시작되지 않은 경우 while 루프를 입력합니다.

1. 연결, 수락

수용하다

유형 남자 2 수락

누구와 연결되어 있는지 알아야 클라이언트에 대한 관련 정보를 얻을 수 있습니다.

첫 번째 매개변수 sockfd는 소켓,
두 번째 매개변수 addr은 일반 구조 유형의 구조로, 이 구조는 포트 번호, IP 주소, 16비트 주소 유형 및 기타 정보를 클라이언트에 기록하는 데 사용됩니다. 세 번째 매개변수 addrlen
은 구조의 크기

반환 값: 성공하면 파일 디스크립터
인 올바른 정수를 반환하고 , 실패하면 -1을 반환하고 오류 코드를 설정합니다.

accept에 의해 반환된 파일 기술자와 소켓 설정에 의해 성공적으로 반환된 파일 기술자 간의 관계

예: 생선 가게가 있는데 장사가 잘 안 되어서 장산이라는 남자가 밖에 서서 손님을 유치하고 있는데, 어느 날
당신과 친구들이 밖에서 장산을 만나면 장산이 물고기가 몇 마리인지 알려줍니다. , 생선먹는곳에 가보길 추천해
너희 둘도 배고파서 장산이랑 생선먹으러 위좡에 갔는데 너만 어촌에 들어갔고 장산은 안들어갔고 장산은
그냥 안에서 소리치고 손님이 있다가 계속 누군가를 찾았는데
이때 웨이터 리시가 와서 뭘 먹고 싶은지 물어보고 다양한 서비스를 해준다.

Zhang San은 Yuzhuang에 손님을 맞이할 때마다 웨이터가 손님에게 서비스를 제공하고
Zhang San은 작업을 마치면 즉시 직장으로 돌아가 손님을 계속 유치합니다.


Zhang San은 사용자에게 특정 서비스를 제공하지 않고 고객을 도로에서 식당으로 끌어와 소비하는 역할
만 담당 합니다. 파일 디스크립터 반환을 수락하려면 이 파일 디스크립터는 실제로 사용자에게 IO 서비스를 제공합니다.


장산이 계속 손님을 권유하면 길에서 사람을 만나 위좡에 저녁 먹으러 갈 거냐고 묻지만, 그 사람은 고개를 저으며 위좡에 저녁 먹으러 가고 싶지 않다고 표현한다. 시간이 지나면
Zhang San은 거부되지만 Zhang San은 계속해서 고객에게 Yuzhuang으로 가도록 권유하므로
수락에 실패했습니다. 계속 실행하십시오.

2. 새로운 연결을 성공적으로 획득하고 비즈니스 처리를 시작합니다.

서비스 기능을 제공합니다. 매개변수는 기본 읽기 및 쓰기 서비스를 구현하는 데 사용되는 새로운 파일 설명자 소켓입니다
. 즉, 클라이언트가 메시지를 보내고 메시지를 다시 전송해야 합니다.


TCP는 스트리밍 서비스
enter man 2 read

파일 디스크립터 fd에서 원하는 데이터를 데이터 블록 형태로 읽어
반환 값이 나타내는 바이트 수 파일의 끝을 읽으면 0, 실패하면 -1


소켓의 데이터를 버퍼 버퍼로 읽습니다
. 읽기에 성공하면 마지막 비트의 다음 비트를 0으로 할당합니다.


read의 반환 값이 0이면 상대방이 연결을 끊기 때문에 sock도 닫을 수 있습니다.


반환 값이 0보다 작으면 읽기에 실패하고 오류 코드가 반환됩니다.


메시지를 받은 후 메시지에 대해 일부 처리를 수행한 다음 메시지를 다시 전송해야 하므로 래퍼 기능 처리를
사용합니다.

클래스 외부에서 함수 유형을 설정하고 반환 값은 문자열이고 매개 변수는 문자열의 래퍼입니다.


함수 유형을 개인 변수 func로 정의



처리된 메시지를 반환합니다. man 2 write를
입력하여 정보를 파일에 씁니다.

fd는 파일 디스크립터
buf는 버퍼
수를 나타냅니다 버퍼 크기를 나타냅니다
write 버퍼의 카운트 크기의 데이터를 fd에 씁니다


res의 데이터를 양말 파일 설명자에 씁니다.

tcpserver.cc(주요 기능 주요 구현)

./tcp_server와 포트 번호를 입력하고 싶기 때문에 명령줄 매개변수 main 함수 의 두 매개변수를
main 함수에 추가합니다. char* argv[]는 포인터 배열이고 argv는 포인터를 포함하는 테이블입니다. 문자열 int argc 에 대해 argc는 배열의 요소 수입니다.


파라미터 입력이 2가 아니면 프로그램이 종료되고 동시에 해당 입력 파라미터가 출력됩니다.


생성자를 통해 알면 새 TcpServer를 사용하려면 콜백과 포트 번호를 전달해야 합니다.


클라이언트 tcp_client

tcpclient.cc(캡슐화되지 않음, 직접 구현됨)

클라이언트를 사용하려면 해당 실행 프로그램인 serverip serverport를 입력해야 하므로
main 함수에서 명령줄 매개 변수를 사용해야 합니다.


입력 매개변수가 3보다 작으면 프로그램을 종료하고 해당 입력 매개변수를 출력


두 번째 입력된 매개변수의 IP 주소를
serverip로 입력된 세 번째 매개변수의 포트 번호에 할당하고 atoi를 사용하여 문자열을 정수로 변환한 후 serverport에 할당합니다.

1. 소켓 생성

네트워크 통신이며 스트림 소켓이며 기본값은 0, 스트림이므로 TCP 프로토콜이므로
소켓 생성에 실패하면 프로그램이 종료됩니다.


2. 링크 시작

입력 남자 수락

클라이언트는 소켓을 통해 특정 서버에 대한 연결 요청을 시작합니다. sockfd sockfd
: socket
addr: 공개 유형 구조에는 서버의 IP 주소와 포트 번호가 포함됩니다.
addrlen: 구조의 크기

반환 값: 성공하면 0을 반환하고 실패하면 -1을 반환하고 오류 코드를 반환합니다
. 연결이 처음 시작되면 운영 체제가 자동으로 포트를 클라이언트에 바인딩합니다.


따라서 구조 서버를 먼저 정의해야 합니다.

htons를 사용하여 위에서 언급한 호스트 직렬 포트 번호 serverport를 네트워크 직렬 포트 번호로 변환

inet_addr - 네트워크 시퀀스 IP 주소에 대한 문자열 IP 주소

남자 inet_addr 입력

첫 번째 매개변수는 문자열 스타일의 IP 주소이고,
두 번째 매개변수는 네트워크 시퀀스의 IP 주소입니다.
문자열 스타일의 IP 주소를 네트워크 시퀀스의 IP 주소로 변환합니다.


그런 다음 호스트 시퀀스의 IP 주소 serverip를 네트워크 시퀀스의 IP 주소로 변환합니다.


cnt는 재접속 횟수를 나타냅니다
. while 루프를 설정합니다. 0이 아닌 경우 연결이 실패하면 cnt 값을 1씩 줄여 연결을 다시 연결합니다. cnt 값이 0이면 break로 연결을 종료합니다. 루프가 종료되고 cont가
0보다 작거나 같으면 프로그램이 종료됩니다.


3. 링크 성공

문자열 유형의 라인을 생성하고 입력 매개변수를 라인
사용 쓰기에 전달하고 라인의 내용을 파일 설명자
사용 읽기에 전달하고 양말 데이터를 버퍼에 전달
하고 읽기의 반환 값으로 판단합니다. 0보다 크면 내용을 출력하고,
반환값이 0이면 링크가 닫혔다는 의미이고,
반환값이 작으면 생성에 실패한 것이고, 오류 코드가 반환됩니다.

특정 코드 구현

err.hpp(오류 메시지를 저장하는 데 사용됨)

#pragma once 

enum
{
    
    
  USAGE_ERR=1,
  SOCKET_ERR,//2
  BIND_ERR,//3
  LISTEN_ERR//4
};


메이크파일

.PHONY:all
all: tcp_client tcp_server

tcp_client:tcpClient.cc
	g++ -o $@ $^ -std=c++11 -lpthread
tcp_server:tcpServer.cc
	g++ -o $@ $^ -std=c++11 -lpthread
.PHONY:clean
clean:
	rm -f tcp_client tcp_server


tcpServer.hpp(서버 패키지)

#pragma once

#include<iostream>
#include<cstdlib>
#include<string.h>
#include<unistd.h>
#include"err.hpp"
#include <sys/types.h>          
#include <sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<functional>



namespace yzq
{
    
    
    
    static uint16_t defaultport=8888;//默认端口号
    static const int backlog=32;//默认整数为32
    using func_t=std::function<std::string(const std::string&)>;

    class TcpServer;
    class ThreadData//该类用于存放客户端的IP port 套接字
    {
    
     
        public:
        ThreadData(int fd,const std::string&ip,const uint16_t &port,TcpServer*ts)//构造
        :sock(fd),clientip(ip),clientport(port),current(ts)
        {
    
    }
      public:
       int sock;//套接字
       std::string clientip;//客户端IP
       uint16_t clientport;//客户端端口号
       TcpServer*current;
    };
    
   class TcpServer
   {
    
    
    public:
    TcpServer(func_t func,uint16_t port=defaultport)
    :func_(func),port_(port),quit_(true)//表示默认启动
    {
    
    }
    void initServer()//初始化
    {
    
    
        //1.创建socket
        listensock_=socket(AF_INET,SOCK_STREAM,0);
        if(listensock_<0)//创建失败
        {
    
    
            std::cout<<" create socket errno"<<std::endl;
            exit(SOCKET_ERR);//终止程序
        }
        //2. bind 绑定
        struct sockaddr_in local;//网络通信类型
        //清空
        memset(&local,'\0',sizeof(local));
        local.sin_family=AF_INET;//网络通信
        //htons 主机转网络
        local.sin_port=htons(port_);//端口号
        local.sin_addr.s_addr=INADDR_ANY ; //IP地址

        if(bind(listensock_,(struct sockaddr*)&local,sizeof(local))<0)
        //失败返回-1
        {
    
    
           std::cout<<" bind socket errno"<<std::endl;
            exit(BIND_ERR);//终止程序
        }

        // 3.监听
        if(listen(listensock_,backlog)<0)
        {
    
    
            //监听失败返回-1
             std::cout<<" listen socket errno"<<std::endl;
            exit(LISTEN_ERR);//终止程序
        }
       
    }
    void start()//启动
    {
    
     
      quit_=false;//服务器没有启动
      while(!quit_)
      {
    
    
        //4.获取连接,accept
        struct sockaddr_in client;//网络通信类型
        socklen_t len=sizeof(client);//结构体大小
        int sock=accept(listensock_,(struct sockaddr*)&client,&len);    
        if(sock<0)
        {
    
    
            //获取失败
            std::cout<<" accept  errno"<<std::endl;
            continue;//继续执行
        }
        //提取客户端信息
        std::string clientip=inet_ntoa(client.sin_addr);//客户端ip
        uint16_t clientport=ntohs(client.sin_port);//客户端端口号
        //5.获取新连接成功,开始进行业务处理
        std::cout<<"获取新连接成功: "<<sock<<"from "<<listensock_<<std::endl; 
        //service(sock);//多线程版本没有调用函数

       //多线程版本
         pthread_t tid;
         ThreadData*td=new ThreadData(sock,clientip,clientport,this);
         pthread_create(&tid,nullptr,threadRoutine,td);
      }
    }
   static void *threadRoutine(void*args)
    {
    
       
        pthread_detach(pthread_self());//线程分离
        ThreadData*td=(ThreadData*)args;
        td->current->service(td->sock);
        delete td;
        return nullptr;
    }
    void service(int sock)
    {
    
    
        char buffer[1024];
        while(true)
        {
    
    
            //将sock中的数据读取到buffer中
            ssize_t s=read(sock,buffer,sizeof(buffer)-1);
             if(s>0)
             {
    
    
                //读取成功
                buffer[s]=0;
                //使用func 进行回调
                std::string res=func_(buffer);
                std::cout<<res<<std::endl;
                //将res中的数据写给sock中
                write(sock,res.c_str(),res.size());

             }
             else if(s==0)
             {
    
    
                //说明对方将连接关闭了
                close(sock);
                std::cout<<"client quit,me too"<<std::endl;
                break; 
             }
             else 
             {
    
    
                //读取失败返回-1
                std::cout<<"read errno"<<strerror(errno)<<std::endl;
                break;
             }
        } 
    }
    ~TcpServer()
    {
    
    }
      private:
      func_t func_;//函数类型
      int listensock_;//监听套接字 
      bool quit_;//表示服务器是否启动
      uint16_t port_;//端口号
   };
}


tcpServer.cc(서버 주요 기능 구현)

#include"tcpServer.hpp"
#include<memory>//智能指针
using namespace std;
using namespace yzq;

static void usage(string proc)
{
    
    
    std::cout<<"usage:\n\t"<<proc<<"port\n"<<std::endl;
}

std::string echo(const std::string&message)
{
    
    
    return message;
}
// ./tcp_server port
int main(int argc,char*argv[])
{
    
    
    //输入两个参数 所以不等于2
    if(argc!=2)
    {
    
    
       usage(argv[0]);
       exit(USAGE_ERR);//终止程序
    }
    //将输入的端口号 转化为整数 
    uint16_t port=atoi(argv[1]);
   unique_ptr<TcpServer>tsvr(new TcpServer(echo,port));
   tsvr->initServer();//服务器初始化
   tsvr->start();//启动
    return 0;
}

tcpClient.cc(클라이언트가 캡슐화되지 않음)

#include<iostream>
#include<cstring>
#include<unistd.h>
#include <sys/types.h>          
#include <sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include"err.hpp"
using namespace std;

static void usage(string proc)
{
    
    
    std::cout<<"usage:\n\t"<<proc<<"port\n"<<std::endl;
}

//./tcp_client  serverip serverport
int main(int argc,char*argv[])
{
    
    
   if(argc!=3)
   {
    
    
       usage(argv[0]);
       exit(USAGE_ERR);//终止程序
   }
   std::string serverip=argv[1];//IP地址
   uint16_t serverport=atoi(argv[2]);//端口号
   
   //1.创建套接字
   int sock=socket(AF_INET,SOCK_STREAM,0);
    if(sock<0)
    {
    
    
      //创建失败
      cout<<"socket errnr:"<<strerror(errno)<<endl;
      exit(SOCKET_ERR);//终止程序
    }

    //2.发起链接
    struct sockaddr_in server;
    memset(&server,0,sizeof(server));//清空
    server.sin_family=AF_INET;//网络通信类型
    //htons 主机序列转为网络序列
    server.sin_port=htons(serverport);//网络端口号
    inet_aton(serverip.c_str(),&server.sin_addr);//网络IP地址

    int cnt=5;//重连次数
    while(connect(sock,(struct sockaddr*)&server,sizeof(server))!=0)
    {
    
    
      //不等于0则链接失败
      sleep(1);
      cout<<"正在尝试重连,重连次数还有:"<<cnt--<<endl;
      if(cnt<=0)
      {
    
    
        //没有重连次数
        break;
      }
    }
    if(cnt<=0)
    {
    
    
        //链接失败
        cout<<"链接失败.."<<endl;
        exit(SOCKET_ERR);//终止程序
    }

    char buffer[1024];
    //3.链接成功
    while(true)
    {
    
    
        string line;
        cout<<"enter>>";
        getline(cin,line);//从cin中获取内容 写入line中
        write(sock,line.c_str(),line.size());//将line中的内容写入到sock文件描述符中   
        ssize_t s=read(sock,buffer,sizeof(buffer)-1);
        if(s>0)
        {
    
    
            buffer[s]=0;
            cout<<"server echo"<<buffer<<endl;
        }
        else if(s==0)
        {
    
    
            cout<<"server quit"<<endl;
            break;
        }
        else 
        {
    
    
            cout<<"read errno"<<strerror(errno)<<endl;
            break;
        }
    }
    close(sock);
    return 0;
}

추천

출처blog.csdn.net/qq_62939852/article/details/132198349