透過 Visual Studio 來開發 Linux 上的程式,主要是透過 SSH 來連到一個 Linux 系統來進行的;而偵錯的部分,則是會使用 gdbserver 來進行,也可以支援中斷點、以及中斷時的變數監控。
本文章將示範在 透過 Visual Studio 透過SSH連線到Ubuntu 18.04 上編譯開發TCP Socket程式碼。
本次教學檔案:
電腦環境:
作業系統:Windows 10
開發軟體:Visual Studio 2022
虛擬機軟體:VirtualBox 7.0.2
一、安裝 Visual Studio 2022
從 Visual Studio官網下載Community 2022版本安裝器後安裝
安裝 Visual Studio 2022 工作套件
這裡我們要勾選2樣,第一樣在使用傳統與行動裝置中的使用C++的桌面開發及其他工具組中的使用C++進行Linux和內嵌開發,安裝大約10Gb左右。
二、安裝跟設定Ubuntu 18.04
要使用Visual Studio編譯開發 Linux 上的程式,會需要安裝Windows子系統Ubuntu或是安裝其他的VM來連線開發,本次使用的是 VirtualBox 來安裝 Ubuntu 18.04 並設定WSL 來讓 Visual Studio 可以遠程連線到 Linux 中做編譯。
7.0以前安裝方式請參考以下文章
關於Linux的基本指令可以參考以下文章
安裝完 VirtualBox 後按新增
設定名稱及選取ISO檔
安裝完畢後需要來設定WSL
安裝必要的套件
由於實際上會在 WSL 的環境下,進行程式的編譯,所以必須要安裝編譯程式時,所需要使用到的套件;一般來說就是 g++ 、make 等套件了~而根據專案的不同,也會需要安裝必要的函式庫。
官方建議的安裝命令,是直接安裝「build-essential」:
sudo apt install build-essential
如果需要遠端偵錯還需要安裝gdb:
sudo apt-get install g++ gdb make ninja-build rsync zip
開啟終端機貼上指令安裝
不過,由於接下來要使用 Visual Studio 透過 SSH 連線來做遠端編譯的控制,所以還需要安裝「openssh-server」;而如果還要遠端偵錯的話,也還需要安裝「gdbserver」。
所以這邊也可以透過下面的指令,來安裝這兩個套件:
sudo apt install openssh-server gdbserver
設定 SSH Server
在將套件安裝好了之後,接下來則是要針對 OpenSSH Server 做基本的設定。他的設定檔是「/etc/ssh/sshd_config」,只要以系統管理員權限、使用文字編輯器打開就可以了。
這邊官方的範例是使用 nano 這個簡易式的文字編輯器,其指令為:
sudo nano /etc/ssh/sshd_config
總共要修改二個地方才行:
# What ports, IPs and protocols we listen for
Port 22
# Change to no to disable tunnelled clear text passwords
PasswordAuthentication yes
在修改好了之後,只要按 Ctrl + X,然後再按 y,就可以儲存修改的內容了。
而在都設定好了之後,接下來還要產生一組 SSH 的金鑰,其指令是:
sudo ssh-keygen -A
啟動 SSH Server
上面的設定步驟,基本上都是只要做一次的設定,當設定好了之後,以後就會存下來,不用每次都重新跑一次。
不過,由於 WSL 的設計並不支援自動啟動的背景服務,所以 SSH Server 不但無法自動啟動、當每次把 bash 視窗關閉時,已經開啟的 SSH Server 也都會被自動關閉。
所以,每次要使用時,都需要手動啟動 OpenSSH Server!其指令是:
sudo service ssh start
三、Visual Studio 的基本使用
來實作一個網路上的教學:
https://www.geeksforgeeks.org/socket-programming-cc/
Visual Studio 安裝好 Visual C++ for Linux Development 後,在建立新專案時,可以在 Visual C++ 下的「跨平台」中,找到「Linux」的分類,這邊會有一些範例專案可以選擇:
我們先去查詢虛擬機的ip地址,打開終端機先安裝net-tools工具
sudo apt install net-tools
然後輸入ifconfig查詢:
ifconfig
接著到 Visual Studio 中設定到工具-跨平台-連線管理員中新增Linux系統連線資訊
這時候可以對專案右鍵點選建置來看建置的狀況
這時候可以到Ubuntu系統資料夾中查到已經編譯的專案
資料夾projects/demo/bin/x64/Debug裡面會有一個.out的執行檔可以執行
打上要執行的檔案
./demo.out
接下來依照https://www.geeksforgeeks.org/socket-programming-cc/的內容來實作
首先雖然專案創建時語言是寫C++,但我們把程式檔名改成.c就可以撰寫c語言了,我們在原有的專案中加入一個新的專案。
在 Server.c 貼入以下程式碼:
// Server side C/C++ program to demonstrate Socket
// programming
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>
#define PORT 8080
int main(int argc, char const* argv[])
{
int server_fd, new_socket, valread;
struct sockaddr_in address;
int opt = 1;
int addrlen = sizeof(address);
char buffer[1024] = { 0 };
char* hello = "Hello from server";
// Creating socket file descriptor
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
perror("socket failed");
exit(EXIT_FAILURE);
}
// Forcefully attaching socket to the port 8080
if (setsockopt(server_fd, SOL_SOCKET,
SO_REUSEADDR | SO_REUSEPORT, &opt,
sizeof(opt))) {
perror("setsockopt");
exit(EXIT_FAILURE);
}
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(PORT);
// Forcefully attaching socket to the port 8080
if (bind(server_fd, (struct sockaddr*)&address,
sizeof(address))
< 0) {
perror("bind failed");
exit(EXIT_FAILURE);
}
if (listen(server_fd, 3) < 0) {
perror("listen");
exit(EXIT_FAILURE);
}
if ((new_socket
= accept(server_fd, (struct sockaddr*)&address,
(socklen_t*)&addrlen))
< 0) {
perror("accept");
exit(EXIT_FAILURE);
}
valread = read(new_socket, buffer, 1024);
printf("%s\n", buffer);
send(new_socket, hello, strlen(hello), 0);
printf("Hello message sent\n");
// closing the connected socket
close(new_socket);
// closing the listening socket
shutdown(server_fd, SHUT_RDWR);
return 0;
}
接下來用一樣的方法再創一個client端的專案
在client.c中貼上以下程式碼:
// Client side C/C++ program to demonstrate Socket
// programming
#include <arpa/inet.h>
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>
#define PORT 8080
int main(int argc, char const* argv[])
{
int sock = 0, valread, client_fd;
struct sockaddr_in serv_addr;
char* hello = "Hello from client";
char buffer[1024] = { 0 };
if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
printf("\n Socket creation error \n");
return -1;
}
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(PORT);
// Convert IPv4 and IPv6 addresses from text to binary
// form
if (inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr)
<= 0) {
printf(
"\nInvalid address/ Address not supported \n");
return -1;
}
if ((client_fd
= connect(sock, (struct sockaddr*)&serv_addr,
sizeof(serv_addr)))
< 0) {
printf("\nConnection Failed \n");
return -1;
}
send(sock, hello, strlen(hello), 0);
printf("Hello message sent\n");
valread = read(sock, buffer, 1024);
printf("%s\n", buffer);
// closing the connected socket
close(client_fd);
return 0;
}