[Network communication] socket programming - TCP socket

TCP still uses codes to get familiar with the corresponding sockets. Many interfaces have been used in udp,
so they will not be taken out separately as the title, and only the interface that appears for the first time will be used as the title.

Through the TCP socket, the data is delivered to the application layer of the other party, and the communication between the two processes is completed.

server tcp_server

tcpserver.hpp (encapsulation)

In tcpServer.hpp, create a namespace yzq for encapsulation
In the namespace, define a class TcpServer

This class contains construction, destruction, initialization (initServer) startup (start)


Initialize initServer

1. Create a socket

Set the listening port number (explained later), the port number is required to identify the uniqueness of the process


Set a default port number 8888 outside the class as the default value of the constructor parameter port


create socket


Enter man socket

The first parameter domain is used to distinguish between network communication and local communication.
If you want to communicate over the network, use AF_INET;
if you want to communicate locally, use AF_UNIX

The second parameter type, the service type corresponding to the socket


SOCK_STREAM stream socket
SOCK_DGRAM connectionless unreliable communication (user datagram)

The third parameter, protocol, indicates which protocol you want to use. The default protocol is 0.
If it is a stream socket, the system will consider it to be the TCP protocol. If it is a user datagram, the system will consider it to be the UDP protocol.

The return value of the socket: the file descriptor is returned if successful, and -1 is returned if it fails


Indicates network communication, stream socket, and the system considers it to be TCP protocol


Create an enumeration of err.hpp to store error information


If creation fails, terminate the program

2. bind bind

Enter man 2 bind to view the binding

Bind a name to a socket
The first parameter sockfd is the socket
The second parameter addr is the general structure type
The third parameter addrlen is the actual length of the second parameter

bind return value: if successful, return 0, if failed, return -1


The use of bind needs to be realized with the help of a general structure,
so define a structure of network communication type local

In the last blog, I described the internal composition of the sockaddr_in structure in detail.
If you don’t understand, you can go to see: Understanding of struct sockaddr_in


htons - convert host sequence to network sequence

Enter man htons, which represents the host-to-network sequence of short integers

So you need to convert the port_ of the host, and then hand it over to the local sin_port (port number)


INADDR_ANY means any IP of bind


Returns -1 if binding fails


3. Monitor

listen - set to listening state

Enter man 2 listen
to set the current socket state to the listening state

The first parameter sockfd is the socket.
The second parameter is not explained temporarily, and it is generally set to an integer.
If successful, it returns 0, and if it fails, it returns -1.


If the monitoring fails, return -1 and terminate the program


Set a default integer to 32 outside the class

start

Set a Boolean variable quit_, if it is true, it means that the server is started, if it is false, it means that the server is not started


If the server is not started, enter a while loop

1. Get connection, accept

accept

Type man 2 accept

You need to know who is connected to you, so you need to get relevant information about the client

The first parameter sockfd is the socket,
the second parameter addr is a structure of general structure type. This structure is used to record the port number, IP address, 16-bit address type and other information in the client. The third parameter
addrlen is the size of the structure

Return value:
If successful, return a legal integer that is the file descriptor
; if failed, return -1 and set the error code

The relationship between the file descriptor returned by accept and the file descriptor successfully returned by socket setting

For example: There is a fish shop, the business is not very good, so there is a man named Zhang San standing outside, to attract customers.
One day you and your friends meet Zhang San outside, and Zhang San tells you how many fish farms they have. , I recommend going to where they eat fish.
You two are also hungry, so I went to Yuzhuang to eat fish with Zhang San, but only you entered the fish village, Zhang San did not go in, Zhang San
just shouted inside, there are guests , and then continued to find someone.
At this time, a waiter Li Si came to ask you what you want to eat and provide you with various services

Every time Zhang San welcomes guests to Yuzhuang, there will be a waiter to provide service to the guests.
When Zhang San finishes his work, he immediately returns to his job and continues to attract guests

Zhang San does not provide specific services to users, but is only responsible for pulling
customers from the road to the restaurant for consumption. The sockfd, which is called the listening socket in the first parameter of accept , acts like Li Si, which is equivalent to accept returning a file descriptor. This file descriptor is really providing IO services to users.



If Zhang San continues to solicit customers, he meets a person on the road and asks him if he wants to go to Yuzhuang for dinner, but the person shakes his head and expresses that he does not want to go to Yuzhuang for dinner. At this time,
Zhang San will be rejected, but this does not affect Zhang San continued to solicit customers to go to Yuzhuang
, so the accept failed, just continue to execute

2. Obtain a new connection successfully and start business processing

Provide a service function, the parameter is the new file descriptor sock
used to implement basic read and write services, that is, the client sends a message, and the message needs to be transferred back


TCP is a streaming service
enter man 2 read

Read the data we want from the file descriptor fd in the form of data blocks.
How many bytes the return value represents. When the end of the file is read, it is 0, and if it fails, it is -1.


Read the data in the sock into the buffer buffer
If the reading is successful, assign the next bit of the last bit to 0


If the return value of read is 0, the other party will close the connection, so the sock can also be closed


If the return value is less than 0, the reading fails and an error code is returned


After receiving the message, you need to do some processing on the message, and then transfer the message back,
so use the wrapper functional processing

Set a function type outside the class, the return value is string, and the parameter is a wrapper of string


Define the function type as a private variable func



Return the processed message
Enter man 2 write
to write information to a file

fd represents the file descriptor
buf represents the buffer
count represents the buffer size
write writes the data of the count size of the buffer into fd


Write the data in res to the sock file descriptor

tcpserver.cc (main function main implementation)

I want to just enter ./tcp_server plus the port number , so add two parameters of
the command line parameter main function to the main function , char* argv[] is an array of pointers, and argv is a table containing pointers, which point to strings int argc, argc is the number of elements in the array


When the parameter input is not 2, the program will be terminated and the corresponding input parameters will be printed out at the same time


Knowing through the constructor, if you want to use new TcpServer, you need to pass in the callback and port number


client tcp_client

tcpclient.cc (not encapsulated, directly implemented)

In order to use the client, you need to enter the corresponding executable program serverip serverport
so you need to use command line parameters in the main function


If the input parameters are less than 3, terminate the program and print out the corresponding input parameters


Assign the IP address of the second parameter input to
the port number of the third parameter input by serverip, use atoi to convert the string into an integer, and then assign it to serverport

1. Create a socket

Network communication, and it is a stream socket, the default is 0, because it is a stream, so it is a TCP protocol
. If the socket creation fails, the program will be terminated


2. Initiate link

Type man accept

The client initiates a connection request to a specific server through the socket sockfd
sockfd: socket
addr: the public type structure contains the server's IP address and port number
addrlen: the size of the structure

Return value: If successful, it will return 0, if it fails, it will return -1 and an error code
When the connection is initiated for the first time, the operating system will automatically bind the port to the client


So you need to define a structure server first

Use htons to convert the above-mentioned host serial port number serverport into a network serial port number

inet_addr - string IP address to network sequence IP address

Enter man inet_addr

The first parameter is the IP address of the string style,
and the second parameter is the IP address of the network sequence
Convert the IP address of the string style to the IP address of the network sequence


Then convert the IP address serverip of the host sequence into the IP address of the network sequence


cnt indicates the number of reconnections
. Set the while loop. When the connection fails when it is not equal to 0, the cnt value will be reduced by 1 and the connection will be reconnected. If the cnt value is 0, break will terminate the loop. If the while loop exits and cont is less than or
equal to 0, the program will be terminated.


3. The link is successful

Create a line of string type, pass the input parameters into the line
use write, pass the content of the line into the file descriptor
use read, pass the sock data into the buffer
and judge by the return value of read, if the return value If it is greater than 0, output the content
. If the return value is equal to 0, it means that the link is closed, and then exit the while loop.
If the return value is less than, it means that the creation failed and an error code is returned.

Specific code implementation

err.hpp (used to store error messages)

#pragma once 

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


makefile

.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 (server package)

#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 (server main function implementation)

#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 (the client is not encapsulated)

#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;
}

Guess you like

Origin blog.csdn.net/qq_62939852/article/details/132198349