├── README.md └── src ├── CMakeLists.txt ├── sample ├── sample_client.cpp └── sample_server.cpp ├── socket_client.cpp ├── socket_client.hpp ├── socket_server.cpp └── socket_server.hpp /README.md: -------------------------------------------------------------------------------- 1 | # Toy OpenCV Socket Server 2 | 3 | `socket_server.cpp` implements a TCP socket server listening for incoming OpenCV images (cv::Mat). `socket_client.cpp` provides a class that connects to a TCP socket and sends it images of configurable size. 4 | 5 | ## Running the samples 6 | 7 | `src/sample_server.cpp` and `src/sample_client.cpp` demonstrate how to use these classes in another program. Build the samples with cmake: 8 | ```bash 9 | mkdir build 10 | cd build 11 | cmake ../src 12 | ``` 13 | 14 | Start the server, providing which port to listen on, followed by the path to save images to: 15 | ```bash 16 | ./sample_server 5005 ~/Desktop/socket_images 17 | ``` 18 | 19 | Start the client, specifying the port of the server's socket, and dimensions of the generated images (cols, rows). Careful! This can fill up your hard drive fast. 20 | ```bash 21 | ./sample_client 5005 1920 1080 22 | ``` 23 | 24 | ## Client-Server Protocol 25 | The first items the server expects from a client is two integers, specifying the incoming images' columns (width) and rows (height). From the sample server: 26 | ```c++ 27 | server_ptr->ConnectToNetwork(); 28 | server_ptr->ReceiveImageDims(); 29 | while(1) { 30 | cv::Mat image; 31 | server_ptr->ReceiveImage(image); 32 | server_ptr->WriteImage(image); 33 | } 34 | return 1; 35 | ``` 36 | 37 | After the server has received the dimensions, it saves received images to the user-supplied path. 38 | The sample client obeys this protocol by first sending the image dimensions, followed by a stream of images, until the program is killed. 39 | ```c++ 40 | client_ptr->ConnectToServer(); 41 | client_ptr->SendImageDims(cols, rows); 42 | while (1) { 43 | cv::Mat image; 44 | GenerateImage(cols, rows, image); 45 | client_ptr->SendImage(image); 46 | } 47 | return 1; 48 | ``` 49 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8) 2 | project(toy-opencv-sockets) 3 | 4 | find_package(OpenCV 3 REQUIRED) 5 | 6 | set(CMAKE_CXX_FLAGS "-std=c++11") 7 | 8 | include_directories( 9 | ${CMAKE_CURRENT_SOURCE_DIR} 10 | ${OpenCV_INCLUDE_DIRS} 11 | ) 12 | 13 | add_executable(sample_server 14 | sample/sample_server.cpp 15 | socket_server.cpp 16 | ) 17 | 18 | add_executable(sample_client 19 | sample/sample_client.cpp 20 | socket_client.cpp 21 | ) 22 | 23 | target_link_libraries(sample_server ${OpenCV_LIBS}) 24 | target_link_libraries(sample_client ${OpenCV_LIBS}) 25 | -------------------------------------------------------------------------------- /src/sample/sample_client.cpp: -------------------------------------------------------------------------------- 1 | #include "socket_client.hpp" 2 | 3 | #include // unique_ptr 4 | #include // atoi 5 | 6 | void GenerateImage(int rows, int cols, cv::Mat& image) { 7 | int lol = 0; 8 | image = cv::Mat::zeros(rows, cols, CV_8UC3); 9 | for (int row = 0; row < rows; ++row) { 10 | for (int col = 0; col < cols; ++col) { 11 | int idk = (lol % 23 == 0) ? rand() % 255 : 35; 12 | image.at(row, col)[0] = idk; 13 | image.at(row, col)[1] = idk; 14 | image.at(row, col)[2] = idk; 15 | lol += 7; 16 | } 17 | } 18 | } 19 | 20 | void AssertCond(bool assert_cond, const char* fail_msg) { 21 | if (!assert_cond) { 22 | printf("Error: %s\nUsage: ./pic-client \n", fail_msg); 23 | exit(1); 24 | } 25 | } 26 | 27 | void ParseArgs(int argc, char** argv) { 28 | AssertCond(argc == 4, "Wrong number of arguments"); 29 | } 30 | 31 | int main(int argc, char** argv) { 32 | ParseArgs(argc, argv); 33 | int port = atoi(argv[1]); 34 | int cols = atoi(argv[2]); 35 | int rows = atoi(argv[3]); 36 | const char hostname[] = "localhost"; 37 | std::unique_ptr client_ptr(new SocketClient(hostname, port)); 38 | client_ptr->ConnectToServer(); 39 | client_ptr->SendImageDims(cols, rows); 40 | while (1) { 41 | cv::Mat image; 42 | GenerateImage(cols, rows, image); 43 | client_ptr->SendImage(image); 44 | } 45 | return 1; // Should not return 46 | } 47 | -------------------------------------------------------------------------------- /src/sample/sample_server.cpp: -------------------------------------------------------------------------------- 1 | #include // unique_ptr 2 | #include 3 | 4 | #include // Mat 5 | 6 | #include "socket_server.hpp" 7 | 8 | bool DirExists(const char* path) { 9 | struct stat info; 10 | if (stat(path, &info) != 0) { 11 | perror("Can't access path"); 12 | return 0; 13 | } 14 | else if(info.st_mode & S_IFDIR) { 15 | return 1; 16 | } 17 | else return 0; 18 | } 19 | 20 | void AssertCond(bool assert_cond, const char* fail_msg) { 21 | if (!assert_cond) { 22 | printf("Error: %s\nUsage: ./pic-server \n", fail_msg); 23 | exit(1); 24 | } 25 | } 26 | 27 | void ParseArgs(int argc, char** argv) { 28 | AssertCond(argc == 3, "Wrong number of arguments"); 29 | AssertCond(DirExists(argv[2]), "Supplied directory does not exist"); 30 | } 31 | 32 | int main(int argc, char** argv) { 33 | ParseArgs(argc, argv); 34 | int port = atoi(argv[1]); 35 | std::unique_ptr server_ptr(new SocketServer(port, argv[2])); 36 | server_ptr->ConnectToNetwork(); 37 | server_ptr->ReceiveImageDims(); 38 | while(1) { 39 | cv::Mat image; 40 | server_ptr->ReceiveImage(image); 41 | server_ptr->WriteImage(image); 42 | } 43 | return 1; // Should not return 44 | } 45 | -------------------------------------------------------------------------------- /src/socket_client.cpp: -------------------------------------------------------------------------------- 1 | #include "socket_client.hpp" 2 | 3 | #include // unique_ptr 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | 12 | SocketClient::SocketClient(const char* hostname, int port) : 13 | hostname_ (hostname), 14 | port_(port), 15 | pic_num_(0), 16 | socket_fdesc_(0) {} 17 | 18 | void SocketClient::ConnectToServer() { 19 | struct addrinfo addrinfo_hints; 20 | struct addrinfo* addrinfo_resp; 21 | 22 | // Specify criteria for address structs to be returned by getAddrinfo 23 | memset(&addrinfo_hints, 0, sizeof(addrinfo_hints)); 24 | addrinfo_hints.ai_socktype = SOCK_STREAM; 25 | addrinfo_hints.ai_family = AF_INET; 26 | 27 | // Populate addr_info_resp with address responses matching hints 28 | if (getaddrinfo(hostname_, std::to_string(port_).c_str(), 29 | &addrinfo_hints, &addrinfo_resp) != 0) { 30 | perror("Couldn't connect to host!"); 31 | exit(1); 32 | } 33 | 34 | // Create socket file descriptor for server 35 | socket_fdesc_ = socket(addrinfo_resp->ai_family, addrinfo_resp->ai_socktype, 36 | addrinfo_resp->ai_protocol); 37 | if (socket_fdesc_ == -1) { 38 | perror("Error opening socket"); 39 | exit(1); 40 | } 41 | 42 | // Connect to server specified in address struct, assign process to server 43 | // file descriptor 44 | if (connect(socket_fdesc_, addrinfo_resp->ai_addr, 45 | addrinfo_resp->ai_addrlen) == -1) { 46 | perror("Error connecting to address"); 47 | exit(1); 48 | } 49 | 50 | free(addrinfo_resp); 51 | } 52 | 53 | void SocketClient::SendImageDims(int cols, int rows) { 54 | // Send number of rows to server 55 | if (send(socket_fdesc_, (char*)&cols, sizeof(cols), 0) == -1) { 56 | perror("Error sending rows"); 57 | exit(1); 58 | } 59 | 60 | // Send number of cols to server 61 | if (send(socket_fdesc_, (char*)&rows, sizeof(rows), 0) == -1) { 62 | perror("Error sending cols"); 63 | exit(1); 64 | } 65 | } 66 | 67 | void SocketClient::SendImage(cv::Mat& image) { 68 | image = image.reshape(0,1); 69 | int image_size = image.total() * image.elemSize(); 70 | int num_bytes = send(socket_fdesc_, image.data, image_size, 0); 71 | printf("Sent %d bytes of %d-byte image to port %d\n", 72 | num_bytes, image_size, port_); 73 | } 74 | -------------------------------------------------------------------------------- /src/socket_client.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SOCKET_CLIENT_HPP 2 | #define SOCKET_CLIENT_HPP 3 | 4 | #include 5 | 6 | class SocketClient { 7 | public: 8 | SocketClient(const char* hostname, int port); 9 | void ConnectToServer(); 10 | void SendImageDims(const int image_rows, const int image_cols); 11 | void SendImage(cv::Mat& image); 12 | private: 13 | const char* hostname_; 14 | int port_; 15 | int pic_num_; 16 | int socket_fdesc_; 17 | }; 18 | 19 | #endif 20 | -------------------------------------------------------------------------------- /src/socket_server.cpp: -------------------------------------------------------------------------------- 1 | #include "socket_server.hpp" 2 | 3 | #include // close 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | SocketServer::SocketServer(int port, std::string out_path) : 10 | image_dims_(cv::Size2i(0, 0)), 11 | out_path_(out_path), 12 | client_len_(0), 13 | server_addr_size_(sizeof(server_addr_)), 14 | port_(port), 15 | pic_count_(0), 16 | sock_fdesc_init_(0), 17 | sock_fdesc_conn_(0) { 18 | client_len_ = server_addr_size_; 19 | } 20 | 21 | void SocketServer::ConnectToNetwork() { 22 | 23 | // Initialize Socket 24 | sock_fdesc_init_ = socket(AF_INET, SOCK_STREAM, 0); 25 | if (sock_fdesc_init_ == -1) { 26 | close(sock_fdesc_init_); 27 | perror("Couldn't create socket!\n"); 28 | exit(1); 29 | } 30 | 31 | // Zero out server address struct 32 | memset((char*)&server_addr_, 0, server_addr_size_); 33 | 34 | // Set server address struct 35 | server_addr_.sin_family = AF_INET; 36 | server_addr_.sin_addr.s_addr = INADDR_ANY; 37 | server_addr_.sin_port = htons(port_); 38 | 39 | // Assign server address to initial socket file descriptor 40 | if (bind(sock_fdesc_init_, (struct sockaddr*)&server_addr_, 41 | server_addr_size_) == -1) { 42 | perror("Couldn't bind initial socket file descriptor!"); 43 | printf("Trying again after killing existing process on port %d...\n", 44 | port_); 45 | close(sock_fdesc_init_); 46 | if (bind(sock_fdesc_init_, (struct sockaddr*)&server_addr_, 47 | server_addr_size_) == -1) { 48 | perror("Couldn't bind initial socket file descriptor after retry!"); 49 | exit(1); 50 | } 51 | printf("Successful bind to port %d after killing previous process\n", 52 | port_); 53 | } 54 | 55 | // Enable listening on initial socket file descriptor 56 | listen(sock_fdesc_init_, 5); 57 | 58 | // Block process until connection with client has been established. 59 | // 'client_fdesc' set as new file descriptor to be used for communication 60 | sock_fdesc_conn_ = accept(sock_fdesc_init_, (struct sockaddr*)&client_addr_, 61 | &client_len_); 62 | if (sock_fdesc_conn_ == -1) { 63 | perror("ERROR! Client couldn't connect!"); 64 | exit(1); 65 | } 66 | } 67 | 68 | void SocketServer::ReceiveImageDims() { 69 | 70 | ssize_t bytes_sent = 0; 71 | size_t dims_size = 0; 72 | 73 | int cols = 0; 74 | int rows = 0; 75 | 76 | size_t sizeof_dims = sizeof(image_dims_.height); 77 | 78 | if (bytes_sent = recv(sock_fdesc_conn_, (char*)&cols, sizeof_dims, 0) == -1) { 79 | printf("ERROR!: recv failed\n" 80 | "sock_fdesc: %d\n" 81 | "image_size: %zu\n" 82 | "bytes_sent: %zu\n", sock_fdesc_conn_, dims_size, bytes_sent); 83 | exit(1); 84 | } 85 | else { 86 | printf("Received rows: %d, cols: %d\n", rows, cols); 87 | } 88 | 89 | if (bytes_sent = recv(sock_fdesc_conn_, (char*)&rows, sizeof_dims, 0) == -1) { 90 | printf("ERROR!: recv failed\n" 91 | "sock_fdesc: %d\n" 92 | "image_size: %zu\n" 93 | "bytes_sent: %zu\n", sock_fdesc_conn_, dims_size, bytes_sent); 94 | exit(1); 95 | } 96 | image_dims_ = cv::Size2i(cols, rows); 97 | printf("Image dimensions: [%dx%d]\n", cols, rows); 98 | } 99 | 100 | void SocketServer::ReceiveImage(cv::Mat& image) { 101 | 102 | int num_bytes = 0; 103 | int image_ptr = 0; 104 | int image_size = 0; 105 | 106 | // Reset image 107 | image = cv::Mat::zeros(image_dims_, CV_8UC4); 108 | 109 | // Get image size 110 | image_size = image.total() * image.elemSize(); 111 | 112 | // Allocate space for image buffer 113 | uchar sock_data[image_size]; 114 | 115 | // Save image data to buffer 116 | for (int i = 0; i < image_size; i += num_bytes) { 117 | num_bytes = recv(sock_fdesc_conn_, sock_data + i, image_size - i, 0); 118 | if (num_bytes == -1) { 119 | printf("ERROR!: recv failed\n" 120 | "i: %d\n" 121 | "sock_fdesc: %d\n" 122 | "image_size: %d\n" 123 | "num_bytes: %d\n", i, sock_fdesc_conn_, image_size, num_bytes); 124 | exit(1); 125 | } 126 | } 127 | 128 | // Write image data to cv::Mat 129 | for (int i = 0; i < image_dims_.height; ++i) { 130 | for (int j = 0; j < image_dims_.width; ++j) { 131 | image.at(i,j) = cv::Vec4b(sock_data[image_ptr+0], 132 | sock_data[image_ptr+1], 133 | sock_data[image_ptr+2], 134 | sock_data[image_ptr+3]); 135 | image_ptr += 4; 136 | } 137 | } 138 | std::ostringstream oss; 139 | oss << out_path_ << "/pic_" << std::to_string(pic_count_++) << ".jpg"; 140 | pic_filename_ = oss.str(); 141 | } 142 | 143 | void SocketServer::WriteImage(cv::Mat& image) { 144 | cv::imwrite(pic_filename_, image); 145 | } 146 | 147 | int SocketServer::GetWidth() { 148 | return image_dims_.width; 149 | } 150 | 151 | int SocketServer::GetHeight() { 152 | return image_dims_.height; 153 | } 154 | -------------------------------------------------------------------------------- /src/socket_server.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SOCKET_SERVER_HPP 2 | #define SOCKET_SERVER_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include // isdigit 9 | #include 10 | #include 11 | 12 | #include 13 | 14 | class SocketServer { 15 | public: 16 | SocketServer(int port, std::string out_path); 17 | void ConnectToNetwork(); 18 | void ReceiveImageDims(); 19 | void ReceiveImage(cv::Mat& image); 20 | void WriteImage(cv::Mat& image); 21 | int GetWidth(); 22 | int GetHeight(); 23 | 24 | private: 25 | cv::Size2i image_dims_; 26 | struct sockaddr_in server_addr_; 27 | struct sockaddr_in client_addr_; 28 | std::string pic_filename_; 29 | std::string out_path_; 30 | socklen_t client_len_; 31 | size_t server_addr_size_; 32 | int port_; 33 | int pic_count_; 34 | int sock_fdesc_init_; 35 | int sock_fdesc_conn_; 36 | }; 37 | 38 | #endif 39 | --------------------------------------------------------------------------------