JavaScript onclick event submit form 用法範例

本篇介紹如何在 JavaScript onclick event 事件中用 form.submit() 函式來 submit form 送出表單。

HTML 基本的 submit form 用法

HTML 基本的 submit form 寫法如下,有個 form 標籤,裡面有個 input 標籤 type="submit"

submit-form.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<html>
<head>
<title>Form Submit Example</title>
<!-- Include CSS File Here
<link rel="stylesheet" href="css/xxx.css"/>
-->
<!-- Include JS File Here
<script src="js/xxx.js"></script>
-->
</head>
<body>
<div class="container">
<div class="main">
<h2>Form Submit Example</h2>
<form action="#" method="post" name="form_name" id="form_id" class="form_class" >
<label>Name :</label><input type="text" name="name" id="name" placeholder="Name" />
<label>Email :</label><input type="text" name="email" id="email" placeholder="Valid Email" />
<input type="submit" name="submit_id" id="btn_id" value="Submit"/>
</form>
</div>
</div>
</body>
</html>

Read More

6 種查詢 SQLite 版本的方法

本篇 ShengYu 介紹 6 種查詢 SQLite 版本的方法,分為這幾種方法,

  • sqlite3.h 標頭檔查詢 SQLite 版本
  • C 語言查詢 SQLite 版本
  • Python 查詢 SQLite 版本
  • SQL 語法查詢 SQLite 版本
  • Command 下指令查詢 SQLite 版本
  • 從 database 檔案本身查詢 SQLite 版本

sqlite3.h 標頭檔查詢 SQLite 版本

一種是從 sqlite3.h 標頭檔去看,在 sqlite3.h 搜尋 SQLITE_VERSION 這個定義就可以找到版本號碼了。

sqlite3.h
1
2
3
#define SQLITE_VERSION        "3.40.1"
#define SQLITE_VERSION_NUMBER 3040001
#define SQLITE_SOURCE_ID "2022-12-28 14:03:47 df5c253c0b3dd24916e4ec7cf77d3db5294cc9fd45ae7b9c5e82ad8197f38a24"

C 語言查詢 SQLite 版本

C 語言的話可呼叫 sqlite3_libversion() API, 有時候系統已經有安裝一份 SQLite 的標頭檔了,但是你想用下載的新版的 SQLite,可能會因為你設置不對造成程式跑去連結系統的那一份,這時用呼叫 sqlite3_libversion() API 來看 SQLite 版本就會比較準確知道你是使用哪一份 SQLite,

Read More

C/C++ rand 產生亂數用法與範例

本篇 ShengYu 介紹 C/C++ rand 產生亂數用法與範例。

C/C++ 要使用 rand 的話需要引入的標頭檔 <stdlib.h>,如果要使用 C++ 的標頭檔則是引入 <cstdlib>
rand 函式原型為

1
int rand (void);

rand() 所產生的亂數是一個整數,其值介於 0 到 RAND_MAX 之間,RAND_MAX 的值會是 2147483647,跟 INT_MAX 一樣。

C/C++ rand 用法

rand() 產生 0-9 亂數寫法如下,

1
2
3
4
5
6
7
8
9
10
// g++ cpp-rand.cpp -o a.out
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main() {
int n = rand() % 10; // 產生 0-9 亂數
printf("n = %d\n", n);
return 0;
}

結果如下,會發現執行3次的結果亂數都一樣,

1
2
3
4
5
6
$ ./a.out 
n = 3
$ ./a.out
n = 3
$ ./a.out
n = 3

這次我們以時間做為亂數種子,再試看看會變成怎麼樣?

cpp-rand-2.cpp
1
2
3
4
5
6
7
8
9
10
11
12
// g++ cpp-rand-2.cpp -o a.out
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main() {
srand(time(NULL)); // 設定時間為亂數種子

int n = rand() % 10; // 產生 0-9 亂數
printf("n = %d\n", n);
return 0;
}

Read More

C/C++ Linux shared memory 與 mmap 用法範例

今天 ShengYu 來介紹 Linux 跨行程通訊 IPC 中的其中一種方式:共享記憶體 Shared Memory 以及 named semaphore,這通常也是 OS (operating system)作業系統或linux系統程式課程中的一部分,以下範例分為生產者 producer 與消費者 consumer,基本上會使用到 mmap 與 shm_open 這幾個函式。

共享記憶體傳遞 string 字串的範例

生產者 producer 寫入 string 字串的範例如下,
這邊的 shm_open 是使用 O_CREAT | O_RDWR 能建立且能讀取寫入,同樣的 mmap 也使用 PROT_READ | PROT_WRITE 能讀取跟寫入。

shm-posix-producer-string.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
// gcc shm-posix-producer-string.c -o produce -lrt && ./produce
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <sys/shm.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <sys/types.h>

void display(char *prog, char *bytes, int n) {
printf("display: %s\n", prog);
for (int i = 0; i < n; i++) {
printf("%02x%c", bytes[i], ((i+1)%16) ? ' ' : '\n');
}
printf("\n");
}

int main() {
const int SIZE = 4096;
const char *name = "OS";
const char *message0= "Studying ";
const char *message1= "Operating Systems ";
const char *message2= "Is Fun!";

int shm_fd;
void *ptr;

/* create the shared memory segment */
shm_fd = shm_open(name, O_CREAT | O_RDWR, 0666);

/* configure the size of the shared memory segment */
ftruncate(shm_fd,SIZE);

/* now map the shared memory segment in the address space of the process */
ptr = mmap(0,SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0);
if (ptr == MAP_FAILED) {
printf("Map failed\n");
return -1;
}

/**
* Now write to the shared memory region.
*
* Note we must increment the value of ptr after each write.
*/
char *str = (char *)ptr;
sprintf(str,"%s",message0);
str += strlen(message0);
sprintf(str,"%s",message1);
str += strlen(message1);
sprintf(str,"%s",message2);
str += strlen(message2);
display("prod", ptr, 64);

return 0;
}

Read More

Windows XAMPP 攜帶版安裝教學

本篇 ShengYu 介紹 Windows XAMPP 攜帶版安裝教學,以往要安裝 Apache + MySQL + PHP + Perl 這些環境就花費許久時間,所以出現了 XAMPP 讓開發者快速地建置後端環境,以前 XAMPP 是 Apache + MySQL + PHP + Perl 的縮寫。MySQL 為目前市佔率最高的資料庫系統。因為當初 MySQL 被甲骨文公司收購後,開發者擔心會有後患,像是轉為商用等等,於是社群就 clone 出 MariaDB,所以現在 XAMPP 上使用的是 MariaDB 而非 MySQL。但是這兩個系統幾乎完全相同。現在 XAMPP 則是 Apache + MariaDB + PHP 的縮寫。

XAMPP 到官網 https://www.apachefriends.org/zh_tw/download.html 或者 sourceforge 下載 xampp portable 隨身攜帶版

把解壓縮後的 xampp 資料夾放在硬碟或USB隨身(硬)碟的最上層,例如: C:\xampp 或 E:\xampp。

Read More

C/C++ Linux sem_wait 與 sem_post 用法範例

今天 ShengYu 來介紹 C/C++ Linux sem_wait 與 sem_post 用法範例,Semaphore 跟 Mutex 相比,雖然 Semaphore 同樣可以用來保護 Critical section,不過它更常被用來確保多執行緒的執行順序。在 Semaphore 中是用 sem_wait 減少與 sem_post 增加 Semaphore 號誌,不像是 Mutex 解鎖還需要同一個人解的 owner ship 特性,也就像解鈴還需繫鈴人概念,以下就來看看 sem_wait 與 sem_post 用法範例吧!

sem_wait() 函式原型:

1
int sem_wait(sem_t *sem);

解釋:若 semaphore 為非 0,則 semaphore 值減 1;若 semaphore 為 0,則呼叫此 function 的 thread 會被 block ,直到 semaphore 值大於 0。

sem_post() 函式原型:

1
int sem_post(sem_t *sem);

解釋:對 semaphore 值加 1。

同行程內的 sem_wait() 跟 sem_post() 用法

以下範例是在一個行程內使用 sem_wait() 跟 sem_post(),在 main 函式 會產生 producer 執行緒跟 consumer 執行緒,producer 執行緒負責產生 1-20 之間的亂數並呼叫 insert_item 放入 buffer 中,而 consumer 執行緒負責用 remove_item 來消耗這 中的亂數。

如果 consumer 都沒有消耗任何變數的情況下,在 producer 會因為 sem_wait(&empty) 從初始值 5 一直減 1 直到 0 不能再減的情況下就會被 block 卡住,直到大於 0 為止才會繼續往下執行。

反之,如果 producer 都沒有生產任何變數的情況下,在 consumer 會因為 sem_wait(&full) 從初始值 0 一直減 1 直到 0 不能再減的情況下就會被 block 卡住,直到大於 0 為止才會繼續往下執行。

linux-sem_wait.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
// gcc linux-sem_wait.c -pthread
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>

#define BUFFER_SIZE 5
int buffer[BUFFER_SIZE];

pthread_mutex_t mutex;
sem_t empty;
sem_t full;

int insertPointer = 0, removePointer = 0;

void *producer(void *param);
void *consumer(void *param);

void display_int_array(char *prog, int *arr, int n) {
printf("%s: ", prog);
for (int i = 0; i < n; i++) {
printf("%d%c", arr[i], ((i+1)%16) ? ' ' : '\n');
}
printf("\n");
}

int insert_item(int item) {
/* Acquire Empty Semaphore */
sem_wait(&empty);

/* Acquire mutex lock to protect buffer */
pthread_mutex_lock(&mutex);

buffer[insertPointer++] = item;
insertPointer = insertPointer % BUFFER_SIZE;
display_int_array("prod", buffer, BUFFER_SIZE);

/* Release mutex lock and full semaphore */
pthread_mutex_unlock(&mutex);
sem_post(&full);

return 0;
}

int remove_item(int *item) {
/* Acquire Full Semaphore */
sem_wait(&full);

/* Acquire mutex lock to protect buffer */
pthread_mutex_lock(&mutex);

*item = buffer[removePointer];
buffer[removePointer++] = -1;
removePointer = removePointer % BUFFER_SIZE;
display_int_array("cons", buffer, BUFFER_SIZE);

/* Release mutex lock and empty semaphore */
pthread_mutex_unlock(&mutex);
sem_post(&empty);

return 0;
}

int main(int argc, char *argv[]) {
int sleepTime = 10, producerThreads, consumerThreads;

/* Initialize the synchronization tools */
pthread_mutex_init(&mutex, NULL);
sem_init(&empty, 0, BUFFER_SIZE);
sem_init(&full, 0, 0);
srand(time(0));

/* Create the producer and consumer threads */
{
pthread_t tid;
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_create(&tid, &attr, producer, NULL);
}

{
pthread_t tid;
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_create(&tid, &attr, consumer, NULL);
}

/* Sleep for user specified time */
sleep(sleepTime);
return 0;
}

void *producer(void *param) {
int random;
int r;

while (1) {
r = rand() % 3;
sleep(r);
random = rand() % 20 + 1;

if (insert_item(random))
fprintf(stderr, "Error");

printf("Producer produced %d \n", random);
}
}

void *consumer(void *param) {
int random;
int r;

while (1) {
r = rand() % 3;
sleep(r);

if (remove_item(&random))
fprintf(stderr, "Error Consuming");
else
printf("Consumer consumed %d \n", random);
}
}

輸出如下,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
prod: 7 0 0 0 0 
Producer produced 7
cons: -1 0 0 0 0
Consumer consumed 7
prod: -1 14 0 0 0
Producer produced 14
cons: -1 -1 0 0 0
Consumer consumed 14
prod: -1 -1 3 0 0
Producer produced 3
cons: -1 -1 -1 0 0
Consumer consumed 3
prod: -1 -1 -1 8 0
Producer produced 8
cons: -1 -1 -1 -1 0
Consumer consumed 8
prod: -1 -1 -1 -1 10
Producer produced 10
cons: -1 -1 -1 -1 -1
Consumer consumed 10
prod: 10 -1 -1 -1 -1
Producer produced 10

跨行程的 sem_wait() 跟 sem_post() 用法

跨行程的 sem_wait() 跟 sem_post() 用法,用 fork 分離出兩個行程,並在這父行程與子行程兩行程間用共享記憶體傳遞資料,使用 mmap 共享記憶體來當作 mutex,原理跟上述範例類似,只是這是跨行程。

shm_open / ftruncate / mmap 跟 sem_open 這兩種方式都可以成功。

linux-sem_wait-2.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
// gcc linux-sem_wait-2.c -lrt -pthread
#include <semaphore.h>
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <fcntl.h>

void display(char *prog, char *bytes, int n) {
printf("%s: ", prog);
for (int i = 0; i < n; i++) {
printf("%02x%c", bytes[i], ((i+1)%16) ? ' ' : '\n');
}
printf("\n");
}

int main(int argc, char **argv) {
int fd, i, count = 0,nloop = 2,zero = 0, *ptr;
sem_t *mutex;

int shm;
if ((shm = shm_open("myshm", O_RDWR | O_CREAT, S_IRWXU)) == 0) {
perror("shm_open");
exit(1);
}

if (ftruncate(shm, sizeof(sem_t)) < 0 ) {
perror("ftruncate");
exit(1);
}

if ((mutex = mmap(NULL, sizeof(sem_t), PROT_READ | PROT_WRITE,
MAP_SHARED, shm, 0)) == MAP_FAILED) {
perror("mmap");
exit(1);
}

// open a file and map it into memory
fd = open("tmp.txt",O_RDWR | O_CREAT, S_IRWXU);
write(fd, &zero, sizeof(int));
ptr = mmap(NULL, sizeof(int), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
close(fd);

/*if ((mutex = sem_open("mysemaphore", O_CREAT, 0644, 1)) == SEM_FAILED) {
perror("semaphore initilization");
exit(1);
}*/

// create, initialize semaphore
if (sem_init(mutex, 1, 1) < 0) {
perror("semaphore initilization");
exit(0);
}

if (fork() == 0) { // child process
for (i = 0; i < nloop; i++) {
sem_wait(mutex);
printf("child entered crititical section: %d\n", (*ptr)++);
display("child", (char *)ptr, 16);
sleep(2);
printf("child leaving critical section\n");
sem_post(mutex);
sleep(1);
}
exit(0);
}

// back to parent process
for (i = 0; i < nloop; i++) {
sem_wait(mutex);
printf("parent entered critical section: %d\n", (*ptr)++);
display("parent", (char *)ptr, 16);
sleep(2);
printf("parent leaving critical section\n");
sem_post(mutex);
sleep(1);
}

return 0;
}

以上就是 C/C++ Linux sem_wait 與 sem_post 用法範例介紹,
如果你覺得我的文章寫得不錯、對你有幫助的話記得 Facebook 按讚支持一下!

其它參考
Semaphores on Linux - sem_init() vs sem_open() - Superpatterns
https://blog.superpat.com/semaphores-on-linux-sem_init-vs-sem_open
在這篇當中提到 shm_open / ftruncate / mmap 跟 sem_open 這兩種方式

其它相關文章推薦
如果你想學習 C++ 相關技術,可以參考看看下面的文章,
C/C++ 新手入門教學懶人包
C/C++ Linux shared memory 與 mmap 用法範例

C/C++ Windows Socket (Winsock) TCP Socket Server/Client 網路通訊教學

本篇 ShengYu 介紹如何寫 C/C++ Windows Socket TCP Socket Server/Client 網路通訊程式,Windows Socket 簡稱 Winsock,在這個網路盛行的時代,網路通訊已成為基礎,想要精通學習網路通訊必須先了解 TCP/IP 協定,其中又以 TCP 通訊最常被使用,TCP 通訊程式通常分成伺服器端與客戶端的兩部份程式,接下來教學內容將介紹如何使用 socket API 來搭建一個典型的 TCP 通訊程式,甚至可以寫出一個聊天室的程式,或者像 LINE 這樣的通訊程式。

以下 C/C++ Winsock TCP 內容將分為幾部分,分別為:

  • 常見的 Socket API 函式 Overview 總覽
  • C/C++ Winsock Socket TCP Server/Client 通訊流程
  • C/C++ Winsock TCP Server 伺服器端程式 (Echo Sever)
  • C/C++ Winsock TCP Client 客戶端程式 (傳送使用者的輸入)
  • C/C++ Winsock TCP Client 客戶端程式 (定時傳送資料)
  • C/C++ Winsock TCP 常見問題
  • Winsock 的 ip 字串轉 sockaddr_in
  • Winsock 的 sockaddr_in 轉 ip 字串

常見的 Socket API 函式 Overview 總覽

C/C++ Winsock 的 socket 模組它提供了標準的 BSD Socket API,主要的 socket API 函式如下:
socket():建立 socket 與設定使用哪種通訊協定
bind(sock_fd, addr):將 socket 綁定到地址
listen(sock_fd, n):開始監聽 TCP 傳入連接,n 指定在拒絕連線前,操作系統可以掛起的最大連接數,該值最少為1,通常設為5就夠用了
accept(sock_fd, addr):等待連線,接受到 TCP 連線後,可以從 addr 得知連線客戶端的地址。
connect(address):連線到 address 處的 socket
recv():接收 TCP 資料
send():發送 TCP 資料
closesocket():關閉 socket

C/C++ Winsock Socket TCP Server/Client 通訊流程

以下 ShengYu 講解 C/C++ Winsock TCP Server 端與 TCP Client 端的程式流程以及會如何使用這些 socket API,
TCP Server 的流程分為以下幾大步驟:

  1. 建立socket:sock_fd = socket(AF_INET, SOCK_STREAM, 0);,指定 AF_INET (Internet Protocol) family 的通訊協定,類型使用 SOCK_STREAM (Stream Socket) 也就是 TCP 傳輸方式
  2. 綁定 socket 到本地 IP 與 port:bind(sock_fd, ...)
  3. 開始監聽:listen(sock_fd, ...)
  4. 等待與接受客戶端的請求連線:new_fd = accept(sock_fd, ...)
  5. 接收客戶端傳來的資料:recv(new_fd, ...)
  6. 傳送給對方發送資料:send(new_fd, ...)
  7. 傳輸完畢後,關閉 socket:closesocket(new_fd)

TCP Client 的流程分為以下幾大步驟:

  1. 建立 socket:sock_fd = socket(AF_INET, SOCK_STREAM, 0);
  2. 連線至遠端地址:connect(sock_fd, ...)
  3. 傳送資料:send(sock_fd, ...)
  4. 接收資料:recv(sock_fd, ...)
  5. 傳輸完畢後,關閉 socket:closesocket(sock_fd)

以上是 TCP Server/Client 通訊的重點流程,實際的 C/C++ Winsock socket API 用法與範例詳見下列章節,接下來就來看看怎麼寫 TCP Server/Client 通訊程式吧!

C/C++ Winsock TCP Server 伺服器端程式 (Echo Sever)

這邊 ShengYu 就開始介紹怎麼寫 C/C++ Winsock TCP Server 程式,下列範例這是一個典型的 Echo Server,Echo Server 就是收到什麼資料就回覆什麼資料,很簡單吧!
跟網路上其他範例不同的是此範例建立連線後不是傳輸一次資料就關閉連線,而是使用迴圈可以一直傳輸資料直到客戶端不想傳關閉連線為止,並且伺服器端再次地等待新的客戶端連線來服務。

要使用 Winsock 的話要 include winsock2.h 標頭檔,winsock2.h 是用來取代 winsock.h,除非你要使用 Winsock 1.1 否則你應該使用 winsock2.h 標頭檔,winsock.h 要連結的函式庫為 wsock32.lib,而 winsock2.h 要連結為 Ws2_32.lib 函式庫。

另外一點要注意的是 windows.h 預設會 include winsock.h,所以如果你在 windows.h 後面在 include winsock2.h 的話會編譯錯誤,原因是因為 winsock.h 跟 winsock2.h 這兩個不應該同時存在,winsock2.h 的設計是用來取代 winsock.h 的而不是擴充的概念,所以正確做法會是 winsock2.h 要在 windows.h 之前被 include。

跟 linux socket 相比,winsock 需要在最一開始要呼叫 WSAStartup 函式,以及最後要呼叫 WSACleanup 函式。

如下例所示,伺服器端一開始建立 socket,用 bind() 綁定,這裡是使用 0.0.0.0, port 為 7000
使用 listen() 開始監聽,上限連線數為5,之後進入主迴圈,accept() 等待接受客戶端的連線請求,
一旦有客戶端連線的話,就會從 accept() 繼續往下執行,
之後是另一個迴圈來服務這個連線,不斷地從這個連線 recv 接收資料與 send 傳送資料,
如果 recv() 的回傳值為0,表示客戶端已斷開連線,此時我們也關閉這個連線,
之後回到 accept() 等待新的客戶端連線,等到新的客戶端連線連上便跟之前的流程一樣,這樣便是一個完整的 C/C++ Winsock TCP 伺服器程式。

cpp-windows-tcp-socket-server.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
#define _CRT_SECURE_NO_WARNINGS
#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// Link with ws2_32.lib
#pragma comment(lib, "Ws2_32.lib")

const char* host = "0.0.0.0";
int port = 7000;

int main()
{
SOCKET sock, new_sock;
socklen_t addrlen;
struct sockaddr_in my_addr, client_addr;
int status;
char indata[1024] = {0}, outdata[1024] = {0};
int on = 1;

// init winsock
WSADATA wsa = { 0 };
WORD wVer = MAKEWORD(2, 2);
WSAStartup(wVer, &wsa);
if (WSAStartup(MAKEWORD(2, 2), &wsa) != NO_ERROR) {
printf("Error: init winsock\n");
exit(1);
}

// create a socket
sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock == INVALID_SOCKET) {
perror("Socket creation error");
exit(1);
}

// for "Address already in use" error message
if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char *)&on, sizeof(int)) == -1) {
perror("Setsockopt error");
exit(1);
}

// server address
my_addr.sin_family = AF_INET;
inet_pton(AF_INET, host, &my_addr.sin_addr);
my_addr.sin_port = htons(port);

status = bind(sock, (struct sockaddr *)&my_addr, sizeof(my_addr));
if (status == -1) {
perror("Binding error");
exit(1);
}
char my_ip[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &my_addr.sin_addr, my_ip, sizeof(my_ip));
printf("server start at: %s:%d\n", my_ip, port);

status = listen(sock, 5);
if (status == -1) {
perror("Listening error");
exit(1);
}
printf("wait for connection...\n");

addrlen = sizeof(client_addr);

while (1) {
new_sock = accept(sock, (struct sockaddr *)&client_addr, &addrlen);
char client_ip[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &client_addr.sin_addr, client_ip, sizeof(client_ip));
printf("connected by %s:%d\n", client_ip, ntohs(client_addr.sin_port));

while (1) {
int nbytes = recv(new_sock, indata, sizeof(indata), 0);
if (nbytes <= 0) {
closesocket(new_sock);
printf("client closed connection.\n");
break;
}
printf("recv: %s\n", indata);

sprintf(outdata, "echo %s", indata);
send(new_sock, outdata, strlen(outdata), 0);
}
}
closesocket(sock);
WSACleanup();

return 0;
}

如果 Server 伺服器端不正常關閉後再次啟動時可能會遇到 Binding error: Address already in use 這種錯誤訊息的話,那麼你可以在 bind() 之前設定 REUSEADDR 可以解決這個問題,

1
2
int on = 1;
setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, (char *)&on, sizeof(int));

C/C++ Winsock TCP Client 客戶端程式 (傳送使用者的輸入)

先用一個終端機來啟動前述的 TCP 伺服器端的程式,接著再用另一個終端機執行 TCP 客戶端的程式。C/C++ Winsock TCP Client 範例如下,這邊要示範的是傳送使用者的輸入訊息,將使用者的輸入訊息傳送給伺服器端,通常應用於一般聊天軟體上,學習之後就可以寫一個簡單的聊天軟體了。

如下例所示,客戶端一開始建立 socket,之後 connect() 連線伺服器主機的 host 與 port,
之後進入主迴圈,不斷地傳送使用者的輸入,這邊是使用 gets_s() 取得使用者輸入的資料,也可以使用 scanf()fgets() 等函式,
使用者輸入完後按下 Enter 便會將資料發送給伺服器端,接著等待伺服器端傳送資料,接收到來自伺服器端的資料就把它印出來,

cpp-windows-tcp-socket-client.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
#define _CRT_SECURE_NO_WARNINGS
#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// Link with ws2_32.lib
#pragma comment(lib, "Ws2_32.lib")

const char* host = "127.0.0.1";
int port = 7000;

int main()
{
SOCKET sock;
struct sockaddr_in serv_name;
int status;
char indata[1024] = {0}, outdata[1024] = {0};

// init winsock
WSADATA wsa = { 0 };
WORD wVer = MAKEWORD(2, 2);
WSAStartup(wVer, &wsa);
if (WSAStartup(MAKEWORD(2, 2), &wsa) != NO_ERROR) {
printf("Error: init winsock\n");
exit(1);
}

// create a socket
sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock == -1) {
perror("Socket creation error");
exit(1);
}

// server address
serv_name.sin_family = AF_INET;
inet_pton(AF_INET, host, &serv_name.sin_addr);
serv_name.sin_port = htons(port);

status = connect(sock, (struct sockaddr *)&serv_name, sizeof(serv_name));
if (status == -1) {
perror("Connection error");
exit(1);
}

while (1) {
printf("please input message: ");
gets_s(outdata);
printf("send: %s\n", outdata);
send(sock, outdata, strlen(outdata), 0);

int nbytes = recv(sock, indata, sizeof(indata), 0);
if (nbytes <= 0) {
closesocket(sock);
printf("server closed connection.\n");
break;
}
printf("recv: %s\n", indata);
}
WSACleanup();

return 0;
}

以下示範一下程式的啟動過程,過程中我在客戶端輸入了兩次的訊息,最後按 ctrl+c 結束了程式,
客戶端輸出結果如下,

client
1
2
3
4
5
6
7
8
9
> client.exe
please input message: hello
send: hello
recv: echo hello
please input message: hello tcp
send: hello tcp
recv: echo hello tcp
please input message: send:
^C

伺服器端輸出結果如下,

server
1
2
3
4
5
6
7
> server.exe
server start at: 0.0.0.0:7000
wait for connection...
connected by 127.0.0.1:25886
recv: hello
recv: hello tcp
client closed connection.

C/C++ Winsock TCP Client 客戶端程式 (定時傳送資料)

前一章節示範了 Echo Sever 與 Client 通訊程式,這時可以打鐵趁熱,除了前一章節 TCP Client 使用者手動輸入的情形之外,這邊也介紹另一種客戶端會定時地傳送資料給伺服器端,同時這也適用於各種通訊情形。

步驟跟前一章節 TCP Client 幾乎相同,傳輸字串為 'heartbeat',這邊傳送後使用 Sleep(1000) 來讓程式睡眠1秒,之後再繼續傳送資料,進而達成定時傳送的功能,

cpp-windows-tcp-socket-client-heartbeat.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
#define _CRT_SECURE_NO_WARNINGS
#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// Link with ws2_32.lib
#pragma comment(lib, "Ws2_32.lib")

const char* host = "127.0.0.1";
int port = 7000;

int main()
{
SOCKET sock;
struct sockaddr_in serv_name;
int status;
char indata[1024] = {0}, outdata[1024] = {0};

// init winsock
WSADATA wsa = { 0 };
WORD wVer = MAKEWORD(2, 2);
WSAStartup(wVer, &wsa);
if (WSAStartup(MAKEWORD(2, 2), &wsa) != NO_ERROR) {
printf("Error: init winsock\n");
exit(1);
}

// create a socket
sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock == -1) {
perror("Socket creation error");
exit(1);
}

// server address
serv_name.sin_family = AF_INET;
inet_pton(AF_INET, host, &serv_name.sin_addr);
serv_name.sin_port = htons(port);

status = connect(sock, (struct sockaddr *)&serv_name, sizeof(serv_name));
if (status == -1) {
perror("Connection error");
exit(1);
}

while (1) {
strcpy(outdata, "heartbeat");
printf("send: %s\n", outdata);
send(sock, outdata, strlen(outdata), 0);

int nbytes = recv(sock, indata, sizeof(indata), 0);
if (nbytes <= 0) {
closesocket(sock);
printf("server closed connection.\n");
break;
}
printf("recv: %s\n", indata);

Sleep(1000);
}
WSACleanup();

return 0;
}

客戶端輸出結果如下,

client
1
2
3
4
5
6
7
8
9
10
11
12
> client.exe
send: heartbeat
recv: echo heartbeat
send: heartbeat
recv: echo heartbeat
send: heartbeat
recv: echo heartbeat
send: heartbeat
recv: echo heartbeat
send: heartbeat
recv: echo heartbeat
^C

伺服器端輸出結果如下,

server
1
2
3
4
5
6
7
8
9
10
> server.exe
server start at: 0.0.0.0:7000
wait for connection...
connected by 127.0.0.1:25910
recv: heartbeat
recv: heartbeat
recv: heartbeat
recv: heartbeat
recv: heartbeat
client closed connection.

C/C++ Winsock TCP 常見問題

在 TCP 的傳輸裡,為什麼伺服器還要回傳給客戶端?
因為這只是個示範用的通訊程式,讓你了解通訊的過程,就像打電話或者跟別人對話一樣,你一句我一句的來回互動,你可以根據實際的需求而修改程式,你也可以改成一直傳,例如客戶端一直傳送,伺服器一直接收。

為什麼 recv 還沒收到資料前會卡住一直等?
因為預設是 blocking 非阻塞模式,recv 還沒收到資料前會卡住一直等,沒法做其他事情,直到 recv 接收到資料才會從 recv 函式返回,解決辦法是改用 Non-blocking 非阻塞模式,Non-blocking 模式是這次沒接收到資料就會從 recv 函式返回,接著繼續往下執行;另一個解決方式是另外建立執行緒去做其他事情。

Winsock 的 ip 字串轉 sockaddr_in

在 linux 中 ip 字串轉換成 sockaddr_in 結構可用 inet_aton (ipv4 only)跟 inet_pton (ipv4 & ipv6) 兩個 API 達成,但在 windows 中沒有 inet_aton,而是 inet_addr (ipv4) 可用,
inet_addr 用法如下,

1
2
3
const char* host = "0.0.0.0";
struct sockaddr_in my_addr;
my_addr.sin_addr.s_addr = inet_addr(host);

但現代的 MS 編譯器都出現 C4996 編譯錯誤,內容如下,

1
'inet_addr': Use inet_pton() or InetPton() instead or define _WINSOCK_DEPRECATED_NO_WARNINGS to disable deprecated API warnings

除非你要使用 define _WINSOCK_DEPRECATED_NO_WARNINGS,否則我建議改使用 inet_pton,使用 inet_pton 的話好處就是 windows 跟 linux 都可以通用,跨平台時程式碼修改的比較少,所以基本上在 windows 平台中使用 inet_pton 就對了!
inet_pton 使用方法如下,

1
2
3
const char* host = "0.0.0.0";
struct sockaddr_in my_addr;
inet_pton(AF_INET, host, &my_addr.sin_addr); // AF_INET -> ipv4

Winsock 的 sockaddr_in 轉 ip 字串

承上節,在 Winsock 中 要將 sockaddr_in 結構轉換成 ip 字串的話可用 inet_ntop (ipv4 & ipv6) API 達成,
inet_ntop 用法如下,

1
2
3
struct sockaddr_in client_addr;
char ip[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &client_addr.sin_addr, ip, sizeof(ip));

以上就是 C/C++ Windows Socket (Winsock) TCP Socket Server/Client 網路通訊教學,
如果你覺得我的文章寫得不錯、對你有幫助的話記得 Facebook 按讚支持一下!

Linux su 切換使用者指令用法與範例

本篇介紹 Linux su 指令用法與範例,su 指令可以用來切換當前使用者到其他使用者,su 切換使用者時需輸入要切換的帳號跟密碼,切換使用者後可以測試該使用者的執行權限。

su 後面接使用者名稱,範例如下,

1
2
# 切換成 root
su root

切換成某 user,

1
su <user_name>

su -c <指令>:執行完指定的指令後,就恢復原來的使用者。

su 執行檔路徑在哪

想知道 su 執行檔路徑在哪的話,可以透過 which su 查詢得知,通常在 在 /bin/ 下,macOS 則是在 /usr/bin/su
如果想要知道目前的使用者是誰的話可以使用 whoami,

以上就是 Linux su 切換使用者指令範例介紹,
如果你覺得我的文章寫得不錯、對你有幫助的話記得 Facebook 按讚支持一下!

其它相關文章推薦
Linux 常用指令教學懶人包

C/C++ Linux TCP Socket Server/Client 網路通訊教學

本篇 ShengYu 介紹如何寫 C/C++ Linux TCP Socket Server/Client 網路通訊程式,在這個網路盛行的時代,網路通訊已成為基礎,想要精通學習網路通訊必須先了解 TCP/IP 協定,其中又以 TCP 通訊最常被使用,TCP 通訊程式通常分成伺服器端與客戶端的兩部份程式,接下來教學內容將介紹如何使用 socket API 來搭建一個典型的 TCP 通訊程式,甚至可以寫出一個聊天室的程式,或者像 LINE 這樣的通訊程式。

以下 C/C++ Linux TCP 內容將分為幾部分,分別為:

  • 常見的 Socket API 函式 Overview 總覽
  • C/C++ Linux Socket TCP Server/Client 通訊流程
  • C/C++ Linux TCP Server 伺服器端程式 (Echo Sever)
  • C/C++ Linux TCP Client 客戶端程式 (傳送使用者的輸入)
  • C/C++ Linux TCP Client 客戶端程式 (定時傳送資料)
  • C/C++ Linux TCP 常見問題
  • Linux sokcet 的 ip 字串轉 sockaddr_in
  • Linux sokcet 的 sockaddr_in 轉 ip 字串

常見的 Socket API 函式 Overview 總覽

C/C++ Linux 的 socket 模組它提供了標準的 BSD Socket API,主要的 socket API 函式如下:
socket():建立 socket 與設定使用哪種通訊協定
bind(sock_fd, addr):將 socket 綁定到地址
listen(sock_fd, n):開始監聽 TCP 傳入連接,n 指定在拒絕連線前,操作系統可以掛起的最大連接數,該值最少為1,通常設為5就夠用了
accept(sock_fd, addr):等待連線,接受到 TCP 連線後,可以從 addr 得知連線客戶端的地址。
connect(address):連線到 address 處的 socket
recv():接收 TCP 資料
send():發送 TCP 資料
close():關閉 socket

C/C++ Linux Socket TCP Server/Client 通訊流程

以下 ShengYu 講解 C/C++ Linux TCP Server 端與 TCP Client 端的程式流程以及會如何使用這些 socket API,
TCP Server 的流程分為以下幾大步驟:

  1. 建立socket:sock_fd = socket(AF_INET, SOCK_STREAM, 0);,指定 AF_INET (Internet Protocol) family 的通訊協定,類型使用 SOCK_STREAM (Stream Socket) 也就是 TCP 傳輸方式
  2. 綁定 socket 到本地 IP 與 port:bind(sock_fd, ...)
  3. 開始監聽:listen(sock_fd, ...)
  4. 等待與接受客戶端的請求連線:new_fd = accept(sock_fd, ...)
  5. 接收客戶端傳來的資料:recv(new_fd, ...)
  6. 傳送給對方發送資料:send(new_fd, ...)
  7. 傳輸完畢後,關閉 socket:close(new_fd)

TCP Client 的流程分為以下幾大步驟:

  1. 建立 socket:sock_fd = socket(AF_INET, SOCK_STREAM, 0);
  2. 連線至遠端地址:connect(sock_fd, ...)
  3. 傳送資料:send(sock_fd, ...)
  4. 接收資料:recv(sock_fd, ...)
  5. 傳輸完畢後,關閉 socket:close(sock_fd)

以上是 TCP Server/Client 通訊的重點流程,實際的 C/C++ Linux socket API 用法與範例詳見下列章節,接下來就來看看怎麼寫 TCP Server/Client 通訊程式吧!

C/C++ Linux TCP Server 伺服器端程式 (Echo Sever)

這邊 ShengYu 就開始介紹怎麼寫 C/C++ Linux TCP Server 程式,下列範例這是一個典型的 Echo Server,Echo Server 就是收到什麼資料就回覆什麼資料,很簡單吧!
跟網路上其他範例不同的是此範例建立連線後不是傳輸一次資料就關閉連線,而是使用迴圈可以一直傳輸資料直到客戶端不想傳關閉連線為止,並且伺服器端再次地等待新的客戶端連線來服務。

如下例所示,伺服器端一開始建立 socket,用 bind() 綁定,這裡是使用 0.0.0.0, port 為 7000
使用 listen() 開始監聽,上限連線數為5,之後進入主迴圈,accept() 等待接受客戶端的連線請求,
一旦有客戶端連線的話,就會從 accept() 繼續往下執行,
之後是另一個迴圈來服務這個連線,不斷地從這個連線 recv 接收資料與 send 傳送資料,
如果 recv() 的回傳值為0,表示客戶端已斷開連線,此時我們也關閉這個連線,
之後回到 accept() 等待新的客戶端連線,等到新的客戶端連線連上便跟之前的流程一樣,這樣便是一個完整的 C/C++ Linux TCP 伺服器程式。

cpp-linux-tcp-socket-server.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
// g++ cpp-linux-tcp-socket-server.cpp -o server
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

const char* host = "0.0.0.0";
int port = 7000;

int main()
{
int sock_fd, new_fd;
socklen_t addrlen;
struct sockaddr_in my_addr, client_addr;
int status;
char indata[1024] = {0}, outdata[1024] = {0};
int on = 1;

// create a socket
sock_fd = socket(AF_INET, SOCK_STREAM, 0);
if (sock_fd == -1) {
perror("Socket creation error");
exit(1);
}

// for "Address already in use" error message
if (setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(int)) == -1) {
perror("Setsockopt error");
exit(1);
}

// server address
my_addr.sin_family = AF_INET;
inet_aton(host, &my_addr.sin_addr);
my_addr.sin_port = htons(port);

status = bind(sock_fd, (struct sockaddr *)&my_addr, sizeof(my_addr));
if (status == -1) {
perror("Binding error");
exit(1);
}
printf("server start at: %s:%d\n", inet_ntoa(my_addr.sin_addr), port);

status = listen(sock_fd, 5);
if (status == -1) {
perror("Listening error");
exit(1);
}
printf("wait for connection...\n");

addrlen = sizeof(client_addr);

while (1) {
new_fd = accept(sock_fd, (struct sockaddr *)&client_addr, &addrlen);
printf("connected by %s:%d\n", inet_ntoa(client_addr.sin_addr),
ntohs(client_addr.sin_port));

while (1) {
int nbytes = recv(new_fd, indata, sizeof(indata), 0);
if (nbytes <= 0) {
close(new_fd);
printf("client closed connection.\n");
break;
}
printf("recv: %s\n", indata);

sprintf(outdata, "echo %s", indata);
send(new_fd, outdata, strlen(outdata), 0);
}
}
close(sock_fd);

return 0;
}

如果 Server 伺服器端不正常關閉後再次啟動時可能會遇到 Binding error: Address already in use 這種錯誤訊息的話,那麼你可以在 bind() 之前設定 REUSEADDR 可以解決這個問題,

1
2
int on = 1;
setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(int));

C/C++ Linux TCP Client 客戶端程式 (傳送使用者的輸入)

先用一個終端機來啟動前述的 TCP 伺服器端的程式,接著再用另一個終端機執行 TCP 客戶端的程式。C/C++ Linux TCP Client 範例如下,這邊要示範的是傳送使用者的輸入訊息,將使用者的輸入訊息傳送給伺服器端,通常應用於一般聊天軟體上,學習之後就可以寫一個簡單的聊天軟體了。

如下例所示,客戶端一開始建立 socket,之後 connect() 連線伺服器主機的 host 與 port,
之後進入主迴圈,不斷地傳送使用者的輸入,這邊是使用 gets() 取得使用者輸入的資料,也可以使用 scanf()fgets() 等函式,
使用者輸入完後按下 Enter 便會將資料發送給伺服器端,接著等待伺服器端傳送資料,接收到來自伺服器端的資料就把它印出來,

cpp-linux-tcp-socket-client.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
// g++ cpp-linux-tcp-socket-client.cpp -o client
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

const char* host = "0.0.0.0";
int port = 7000;

int main()
{
int sock_fd;
struct sockaddr_in serv_name;
int status;
char indata[1024] = {0}, outdata[1024] = {0};

// create a socket
sock_fd = socket(AF_INET, SOCK_STREAM, 0);
if (sock_fd == -1) {
perror("Socket creation error");
exit(1);
}

// server address
serv_name.sin_family = AF_INET;
inet_aton(host, &serv_name.sin_addr);
serv_name.sin_port = htons(port);

status = connect(sock_fd, (struct sockaddr *)&serv_name, sizeof(serv_name));
if (status == -1) {
perror("Connection error");
exit(1);
}

while (1) {
printf("please input message: ");
gets(outdata);
printf("send: %s\n", outdata);
send(sock_fd, outdata, strlen(outdata), 0);

int nbytes = recv(sock_fd, indata, sizeof(indata), 0);
if (nbytes <= 0) {
close(sock_fd);
printf("server closed connection.\n");
break;
}
printf("recv: %s\n", indata);
}

return 0;
}

以下示範一下程式的啟動過程,過程中我在客戶端輸入了兩次的訊息,最後按 ctrl+c 結束了程式,
客戶端輸出結果如下,

client
1
2
3
4
5
6
7
8
$ ./client
please input message: hello
send: hello
recv: echo hello
please input message: hello tcp
send: hello tcp
recv: echo hello tcp
please input message: ^C

伺服器端輸出結果如下,

server
1
2
3
4
5
6
7
$ ./server
server start at: 0.0.0.0:7000
wait for connection...
connected by 127.0.0.1:59362
recv: hello
recv: hello tcp
client closed connection.

C/C++ Linux TCP Client 客戶端程式 (定時傳送資料)

前一章節示範了 Echo Sever 與 Client 通訊程式,這時可以打鐵趁熱,除了前一章節 TCP Client 使用者手動輸入的情形之外,這邊也介紹另一種客戶端會定時地傳送資料給伺服器端,同時這也適用於各種通訊情形。

步驟跟前一章節 TCP Client 幾乎相同,傳輸字串為 'heartbeat',這邊傳送後使用 sleep(1) 來讓程式睡眠1秒,之後再繼續傳送資料,進而達成定時傳送的功能,

cpp-linux-tcp-socket-client-heartbeat.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
// g++ cpp-linux-tcp-socket-client-heartbeat.cpp -o client
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

const char* host = "0.0.0.0";
int port = 7000;

int main()
{
int sock_fd;
struct sockaddr_in serv_name;
int status;
char indata[1024] = {0}, outdata[1024] = {0};

// create a socket
sock_fd = socket(AF_INET, SOCK_STREAM, 0);
if (sock_fd == -1) {
perror("Socket creation error");
exit(1);
}

// server address
serv_name.sin_family = AF_INET;
inet_aton(host, &serv_name.sin_addr);
serv_name.sin_port = htons(port);

status = connect(sock_fd, (struct sockaddr *)&serv_name, sizeof(serv_name));
if (status == -1) {
perror("Connection error");
exit(1);
}

while (1) {
strcpy(outdata, "heartbeat");
printf("send: %s\n", outdata);
send(sock_fd, outdata, strlen(outdata), 0);

int nbytes = recv(sock_fd, indata, sizeof(indata), 0);
if (nbytes <= 0) {
close(sock_fd);
printf("server closed connection.\n");
break;
}
printf("recv: %s\n", indata);

sleep(1);
}

return 0;
}

客戶端輸出結果如下,

client
1
2
3
4
5
6
7
8
9
10
11
12
$ ./client
send: heartbeat
recv: echo heartbeat
send: heartbeat
recv: echo heartbeat
send: heartbeat
recv: echo heartbeat
send: heartbeat
recv: echo heartbeat
send: heartbeat
recv: echo heartbeat
^C

伺服器端輸出結果如下,

server
1
2
3
4
5
6
7
8
9
10
$ ./server
server start at: 0.0.0.0:7000
wait for connection...
connected by 127.0.0.1:33862
recv: heartbeat
recv: heartbeat
recv: heartbeat
recv: heartbeat
recv: heartbeat
client closed connection.

C/C++ Linux TCP 常見問題

在 TCP 的傳輸裡,為什麼伺服器還要回傳給客戶端?
因為這只是個示範用的通訊程式,讓你了解通訊的過程,就像打電話或者跟別人對話一樣,你一句我一句的來回互動,你可以根據實際的需求而修改程式,你也可以改成一直傳,例如客戶端一直傳送,伺服器一直接收。

為什麼 recv 還沒收到資料前會卡住一直等?
因為預設是 blocking 非阻塞模式,recv 還沒收到資料前會卡住一直等,沒法做其他事情,直到 recv 接收到資料才會從 recv 函式返回,解決辦法是改用 Non-blocking 非阻塞模式,Non-blocking 模式是這次沒接收到資料就會從 recv 函式返回,接著繼續往下執行;另一個解決方式是另外建立執行緒去做其他事情。

Linux sokcet 的 ip 字串轉 sockaddr_in

在 Linux 中 ip 字串轉換成 sockaddr_in 結構可用 inet_aton (ipv4 only)跟 inet_pton (ipv4 & ipv6) 兩個 API 達成,但在 windows 中沒有 inet_aton,而是 inet_addr (ipv4) 可用,所以用 inet_aton 的缺點是跨平台的話還要在修改這部分的程式碼,

inet_aton 用法如下,

1
2
3
const char* host = "0.0.0.0";
struct sockaddr_in my_addr;
inet_aton(host, &my_addr.sin_addr);

所以使用 inet_pton 的話,好處就是 linux 跟 windows 都可以通用,所以基本上在 linux 中是建議使用 inet_pton,

inet_pton 使用方法如下,

1
2
3
const char* host = "0.0.0.0";
struct sockaddr_in my_addr;
inet_pton(AF_INET, host, &my_addr.sin_addr); // AF_INET -> ipv4

Linux sokcet 的 sockaddr_in 轉 ip 字串

在 Linux 中 要將 sockaddr_in 結構轉換成 ip 字串的話可用 inet_ntoa (ipv4 only)跟 inet_ntop (ipv4 & ipv6) 兩個 API 達成,

inet_ntoa 用法如下,

1
2
3
struct sockaddr_in client_addr;
char *ip;
ip = inet_ntoa(client_addr.sin_addr)

inet_ntop 用法如下,

1
2
3
struct sockaddr_in client_addr;
char ip[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &client_addr.sin_addr, ip, sizeof(ip));

以上就是 C/C++ Linux TCP Socket Server/Client 網路通訊教學,
如果你覺得我的文章寫得不錯、對你有幫助的話記得 Facebook 按讚支持一下!