tcp programming: connection oriented, reliable transmission, byte stream oriented
tcp client and server processes
-
Client: create socket, describe address information, initiate connection request, establish connection successfully, send and receive data, and close
-
Server: create a socket, describe the address information, start listening, accept the connection request, create a new socket, obtain the new socket descriptor, communicate with the client through this descriptor, and close
socket interface introduction
- Create socket
int socket(int domain, int type, int protocol) - ( AF_INET, SOCK_STREAM - Streaming socket, IPPROTO_TCP);
- Binding address information
int bind(int sockfd, struct sockaddr *addr, socklen_t len); struct sockaddr_in{ sin_family = AF_INET; sin_port = htons(); sin_addr.s_ddr = int _addr() };
- The server starts listening
int listen(int sockfd, int backlog); - Tell the operating system to start receiving connection requests
parameter
- sockfd: listen socket - get socket for client connection request
- backlog: determines the number of client connection requests that the server can accept at the same time
SYN flooding attack:
- The malicious host constantly sends a large number of connection requests to the server host. If the server establishes a socket for each connection request, the resources will be exhausted instantly.
The server crashes, so the server has a connection pending queue;
- Store the newly created socket node for connection request
- The backlog parameter determines the maximum number of nodes in the queue
- If the queue is full, if there are new connection requests coming, the subsequent requests will be discarded
- Get the operation handle of the new socket
Take a socket from the pending queue of the socket specified by the kernel and return the operation handle
int accept(int sockfd, struct sockaddr *addr, socklen_t *len)
Parameters:
- sockfd: listening socket - specifies which socket in the pending queue to get
- Addr: obtain a socket that communicates with the specified client, and obtain the address information of the client through addr
- len: input / output type parameters - specify the desired length of address information and return the actual address length
Return value: if successful, the descriptor of the newly obtained socket is returned; Failure return -1
- Communicate with the specified client through the newly obtained socket operation handle (descriptor returned by accept)
Receive data:
ssize_t recv(int sockfd - accept New socket descriptor returned, char *buf, int len, int flag);
Return value: the actual read data length is returned successfully, and 0 is returned when the connection is disconnected; Read failure return -1
Send data:
ssize_t send(int sockfd, char *data, int len, int flag);
Return value: the length of the actually sent data is successfully returned; Failure return -1; If the connection is disconnected, an exception is triggered
- Close socket: Free Resources
int close(int fd);
- Client sends connection request to server
int connect(int sockfd, int sockaddr *addr, socklen_t len);
Parameters:
- sockfd: client socket - if the address has not been bound, the operating system will select the appropriate source address for binding
- addr: server address information - struct sockaddr_in; This address information will also be described in the socket after it is connect ed
- len: address information length
Implement tcp communication program
tcpsocket.hpp
// Encapsulate and implement a tcpsocket class to provide simple interfaces to the outside world: // By instantiating a tcpsocket object externally, the tcp communication program can be established #include <cstdio> #include <string> #include <unistd.h> #include <arpa/inet.h> #include <sys/socket.h> #include <netinet/in.h> #define BACKLOG 10 #define CHECK_RET(q) if((q)== false){return -1;} class TcpSocket{ public: TcpSocket():_sockfd(-1){ } int GetFd(){ return _sockfd; } void SetFd(int fd){ _sockfd = fd; } // Create socket bool Socket(){ // Socket (address domain, socket type, protocol type) _sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if(_sockfd < 0){ perror("socket error"); return false; } return true; } void Addr(struct sockaddr_in *addr, const std::string &ip, uint16_t port){ addr->sin_family = AF_INET; addr->sin_port = htons(port); inet_pton(AF_INET, ip.c_str(), &(addr->sin_addr.s_addr)); } // Binding address information bool Bind(const std:: string &ip, const uint16_t port){ // Define IPv4 address structure struct sockaddr_in addr; Addr(&addr, ip, port); socklen_t len = sizeof(struct sockaddr_in); int ret = bind(_sockfd, (struct sockaddr*)&addr, len); if(ret < 0){ perror("bind error"); return false; } return true; } // The server starts listening bool Listen(int backlog = BACKLOG){ // Listen (descriptor, number of concurrent links at the same time) int ret = listen(_sockfd, backlog); if(ret < 0){ perror("listen error"); return false; } return true; } // Client initiates connection request bool Connect(const std::string &ip, const uint16_t port){ // 1. define the IPv4 address structure and give the server address information struct sockaddr_in addr; Addr(&addr, ip, port); // 2. send a request to the server // 3.connect (client descriptor, server address information, address length) socklen_t len = sizeof(struct sockaddr_in); int ret = connect(_sockfd, (struct sockaddr*)&addr, len); if(ret < 0){ perror("connect error"); return false; } return true; } // The server obtains a new connection bool Accept(TcpSocket *sock, std::string *ip = NULL, uint16_t *port = NULL){ // Accept (listening socket, peer address information, address information length) returns a new descriptor struct sockaddr_in addr; socklen_t len = sizeof(struct sockaddr_in); // Get the new socket and the corresponding peer address information of the socket int clisockfd = accept(_sockfd, (struct sockaddr*)&addr, &len); if(clisockfd < 0){ perror("accept error"); return false; } // The user passed in a pointer to a Tcpsocket object // Assign a value to the descriptor of this object --- assign a value to the descriptor of the new socket // Subsequent communication with the client can be completed through this object sock->_sockfd = clisockfd; if(ip != NULL){ *ip = inet_ntoa(addr.sin_addr); // Network byte order IP - > string IP } if(port != NULL){ *port = ntohs(addr.sin_port); } return true; } // send data bool Send(const std::string &data){ // Send (descriptor, data, data length, option parameter) int ret = send(_sockfd, data.c_str(), data.size(), 0); if(ret < 0){ perror("send error"); return false; } return true; } // receive data bool Recv(std::string *buf){ // Recv (descriptor, buffer, data length, option parameters) char tmp[4096] = {0}; int ret = recv(_sockfd, tmp, 4096, 0); if(ret < 0){ perror("recv error"); return false; } else if(ret == 0){ printf("connection break\n"); return false; } buf->assign(tmp, ret); // Copy ret size data from tmp to buf return true; } // Close socket bool Close(){ close(_sockfd); _sockfd = -1; return true; } private: int _sockfd; };
tcp_srv.cpp
// Using encapsulated TcpSocket class instantiation object to implement tcp server program #include <iostream> #include "tcpsocket.hpp" int main(int argc, char *argv[]){ if(argc != 3){ printf("em:./tcp_srv 192.168.122.132 9000\n"); return -1; } std::string ip = argv[1]; uint16_t port = std::stoi(argv[2]); // stoi converting strings to numbers TcpSocket lst_sock; CHECK_RET(lst_sock.Socket()); CHECK_RET(lst_sock.Bind(ip, port)); CHECK_RET(lst_sock.Listen()); while(1){ TcpSocket cli_sock; std::string cli_ip; uint16_t cli_port; // Accept class member function, private member used_ sockfd is LST_ Private member of sock // Cli_ The socket fetches the address and passes it in to obtain the communication socket descriptor returned by the accept interface bool ret = lst_sock.Accept(&cli_sock, &cli_ip, &cli_port); if(ret == false){ // Failed to get a new connection. You can continue to get the next one again continue; } printf("new connect: [%s:%d]\n", cli_ip.c_str(), cli_port); // Communicate with the client through the newly acquired communication socket std::string buf; if(cli_sock.Recv(&buf) == false){ cli_sock.Close(); // Error receiving data from communication socket. Communication socket is closed continue; } printf("client:[%s:%d] say:%s\n", &cli_ip[0], cli_port, &buf[0]); std::cout << "server say:"; fflush(stdout); buf.clear(); std::cin >> buf; if(cli_sock.Send(buf) == false){ cli_sock.Close(); continue; } } lst_sock.Close(); return 0; }
tcp_cli.cpp
// Implement tcp client program through encapsulated TcpSocket class instantiation object #include <iostream> #include "tcpsocket.hpp" int main(int argc, char *argv[]){ if(argc != 3){ printf("em:./tcp_cli 192.168.122.132 9000 - Address of service binding\n"); return -1; } std::string ip = argv[1]; uint16_t port = std::stoi(argv[2]); TcpSocket cli_sock; // Create socket CHECK_RET(cli_sock.Socket()); // Binding address information (not recommended) // Send a request to the server CHECK_RET(cli_sock.Connect(ip, port)); // Cyclic receiving and sending data while(1){ printf("client say:"); fflush(stdout); std::string buf; std::cin >> buf; // Because the client does not have files for multiple sockets, it is OK to exit directly once the current socket has an error // When the process exits, the resource will be released and the socket will be closed CHECK_RET(cli_sock.Send(buf)); buf.clear(); CHECK_RET(cli_sock.Recv(&buf)); printf("server say:%s\n", buf.c_str()); } cli_sock.Close(); return 0; }
The tcp server cannot communicate with the client continuously:
Specific technology:
Multithreading / multiprocessing
Multiprocess
- The parent process creates a child process with unique data and each has a cli_sock; However, the child process passes the cli_sock communication, but the parent process does not need it, so the parent process closes its cli_sock
- The parent process should wait for the child process to exit to avoid zombie processes; In order that the parent process is only responsible for obtaining new connections, it is necessary to customize the callback waiting for SIGCHLD signal processing
Server code
// Using encapsulated TcpSocket class instantiation object to implement tcp server program #include <iostream> #include <stdlib.h> #include <signal.h> #include <sys/wait.h> #include "tcpsocket.hpp" void sigcb(int signo){ // When the child process exits, it will send a SIGCHLD signal to the parent process and return this function // The return value of waitpid >0 indicates that an exiting child process has been processed // Waitpid<=0 indicates that there is no child process exiting while(waitpid(-1, 0, WNOHANG) > 0); // A callback loop will handle all the exiting child processes } int main(int argc, char *argv[]){ if(argc != 3){ printf("em:./tcp_srv 192.168.122.132 9000\n"); return -1; } std::string ip = argv[1]; uint16_t port = std::stoi(argv[2]); // stoi converting strings to numbers signal(SIGCHLD, sigcb); TcpSocket lst_sock; CHECK_RET(lst_sock.Socket()); CHECK_RET(lst_sock.Bind(ip, port)); CHECK_RET(lst_sock.Listen()); while(1){ TcpSocket cli_sock; std::string cli_ip; uint16_t cli_port; bool ret = lst_sock.Accept(&cli_sock, &cli_ip, &cli_port); if(ret == false){ continue; } printf("new connect: [%s:%d]\n", cli_ip.c_str(), cli_port); // ------------------------------------------------------ pid_t pid = fork(); if(pid == 0){ // Child processes copy parent processes - data unique, code shared // Let the child process handle communication with the client while(1){ // Communicate with the client through the newly acquired communication socket std::string buf; if(cli_sock.Recv(&buf) == false){ cli_sock.Close(); // Error receiving data from communication socket. Communication socket is closed exit(0); } printf("client:[%s:%d] say:%s\n", &cli_ip[0], cli_port, &buf[0]); std::cout << "server say:"; fflush(stdout); buf.clear(); std::cin >> buf; if(cli_sock.Send(buf) == false){ cli_sock.Close(); exit(0); } } cli_sock.Close(); exit(0); } // The parent-child process data is unique, and both have cli_sock, but the parent process does not communicate cli_sock.Close(); // This shutdown has no effect on the child process. Each data has its own copy } lst_sock.Close(); return 0; }
Multithreading
-
The main thread obtains a new connection and then creates a new thread to communicate with the client, but the socket descriptor needs to be passed into the thread execution function
-
However, when transmitting this descriptor, the address of the local variable cannot be used for transmission (the space of the local variable will be released after the loop is completed). The value of the descriptor can be transmitted, or the new object can be passed in
-
In c++, there are many restrictions on strong type conversion and passing data values as pointers. We can just try to overcome them
-
CLI is not used in the main thread_ Sock, but cli cannot be closed_ Because threads share resources, one thread will be released, and the other thread will not be able to use it
// Using encapsulated TcpSocket class instantiation object to implement tcp server program #include <iostream> #include <stdlib.h> #include "tcpsocket.hpp" void *thr_start(void *arg){ long fd = (long)arg; TcpSocket cli_sock; cli_sock.SetFd(fd); while(1){ // Communicate with the client through the newly acquired communication socket std::string buf; if(cli_sock.Recv(&buf) == false){ cli_sock.Close(); // Error receiving data from communication socket. Communication socket is closed pthread_exit(NULL); // Exit is to exit the process } printf("client say:%s\n", &buf[0]); std::cout << "server say:"; fflush(stdout); buf.clear(); std::cin >> buf; if(cli_sock.Send(buf) == false){ cli_sock.Close(); pthread_exit(NULL); } } // Close socket if loop exit cli_sock.Close(); return NULL; } int main(int argc, char *argv[]){ if(argc != 3){ printf("em:./tcp_srv 192.168.122.132 9000\n"); return -1; } std::string ip = argv[1]; uint16_t port = std::stoi(argv[2]); // stoi converting strings to numbers TcpSocket lst_sock; CHECK_RET(lst_sock.Socket()); CHECK_RET(lst_sock.Bind(ip, port)); CHECK_RET(lst_sock.Listen()); while(1){ TcpSocket cli_sock; std::string cli_ip; uint16_t cli_port; // Accept class member function, private member used_ sockfd is LST_ Private member of sock // Cli_ The socket fetches the address and passes it in to obtain the communication socket descriptor returned by the accept interface bool ret = lst_sock.Accept(&cli_sock, &cli_ip, &cli_port); if(ret == false){ // Failed to get a new connection. You can continue to get the next one again continue; } printf("new connect: [%s:%d]\n", cli_ip.c_str(), cli_port); // -------------------------------------- pthread_t tid; // Pass the communication socket to the thread as a parameter to let the thread communicate with the client // Cli_ The sock is a local variable - the resource will be released after the loop is completed pthread_create(&tid, NULL, thr_start, (void*)cli_sock.GetFd()); // thread pthread_detach(tid); // Do not care about the thread return value, separate the thread, and automatically release resources after exiting // The main thread cannot close cli_sock socket, because multithreading is a common descriptor } lst_sock.Close(); return 0; }
The performance of disconnection at the sending end and receiving end:
-
Receiving end: if the connection is disconnected, recv returns 0 (socket write end is closed duplex communication)
-
Sender: if the connection is disconnected, send triggers an exception - SIGPIPE, causing the process to exit